Skip to main content

diffsol_c/
ode_c.rs

1use crate::c_api_utils::{valid_f64_ptr, DIFFSOL_BAD_ARG, DIFFSOL_ERR, DIFFSOL_OK};
2use crate::linear_solver_type_c::{linear_solver_from_i32, linear_solver_to_i32};
3use crate::matrix_type_c::matrix_type_from_i32;
4use crate::matrix_type_c::matrix_type_to_i32;
5use crate::ode::OdeWrapper;
6use crate::ode_solver_type_c::{ode_solver_from_i32, ode_solver_to_i32};
7use crate::scalar_type::ScalarType;
8use crate::solution_wrapper::SolutionWrapper;
9use crate::tensor::{Tensor, TensorView};
10use crate::vjp_checkpoint::VjpCheckpointWrapper;
11use crate::{c_error, c_invalid_arg};
12
13fn boxed_tensor(tensor: Tensor) -> *mut Tensor {
14    Box::into_raw(Box::new(tensor))
15}
16
17/// Return true if `ptr` can point to `len * nlanes` `f64` values: the count does not overflow,
18/// and `ptr` is not null unless the count is zero.
19fn valid_lanes_ptr(ptr: *const f64, len: usize, nlanes: usize) -> bool {
20    nlanes
21        .checked_mul(len)
22        .is_some_and(|count| valid_f64_ptr(ptr, count))
23}
24
25/// View `len * nlanes` `f64` values at `ptr` as a column-major `[len, nlanes]` array: lane `b`,
26/// value `k` is at index `b * len + k`.
27///
28/// # Safety
29/// `ptr` must point to `len * nlanes` valid `f64` values (see `valid_lanes_ptr`), which stay
30/// valid and do not change during `'a`.
31unsafe fn lanes_view<'a>(ptr: *const f64, len: usize, nlanes: usize) -> TensorView<'a> {
32    // SAFETY: the caller guarantees the values
33    unsafe { TensorView::new_col_major(ptr as *const u8, &[len, nlanes], ScalarType::F64) }
34}
35
36/// View `len` `f64` values at `ptr` as a `[len]` array.
37///
38/// # Safety
39/// `ptr` must point to `len` valid `f64` values (see `valid_f64_ptr`), which stay valid and do
40/// not change during `'a`.
41unsafe fn times_view<'a>(ptr: *const f64, len: usize) -> TensorView<'a> {
42    // SAFETY: the caller guarantees the values
43    unsafe { TensorView::new_col_major(ptr as *const u8, &[len], ScalarType::F64) }
44}
45
46pub(crate) fn parse_ode_new_common_args(
47    matrix_type: i32,
48    linear_solver: i32,
49    ode_solver: i32,
50) -> Option<(
51    crate::matrix_type::MatrixType,
52    crate::linear_solver_type::LinearSolverType,
53    crate::ode_solver_type::OdeSolverType,
54)> {
55    let matrix_type = match matrix_type_from_i32(matrix_type) {
56        Some(value) => value,
57        None => {
58            c_invalid_arg!("invalid matrix_type");
59            return None;
60        }
61    };
62    let linear_solver = match linear_solver_from_i32(linear_solver) {
63        Some(value) => value,
64        None => {
65            c_invalid_arg!("invalid linear_solver");
66            return None;
67        }
68    };
69    let ode_solver = match ode_solver_from_i32(ode_solver) {
70        Some(value) => value,
71        None => {
72            c_invalid_arg!("invalid ode_solver");
73            return None;
74        }
75    };
76    Some((matrix_type, linear_solver, ode_solver))
77}
78
79/// Free a list of arrays previously returned by this library.
80///
81/// # Safety
82/// `list` must be either null or a pointer returned by this library for a list
83/// of length `len`. Each pointed-to element remains owned separately.
84#[unsafe(no_mangle)]
85pub unsafe extern "C" fn diffsol_tensor_list_free(list: *mut *mut Tensor, len: usize) {
86    if list.is_null() {
87        c_invalid_arg!("tensor list is null");
88        return;
89    }
90    unsafe {
91        drop(Box::from_raw(std::ptr::slice_from_raw_parts_mut(list, len)));
92    }
93}
94
95/// Free an ODE wrapper previously returned by this library.
96///
97/// # Safety
98/// `ode` must be either null or a pointer returned by this library that has not
99/// already been freed.
100#[unsafe(no_mangle)]
101pub unsafe extern "C" fn diffsol_ode_free(ode: *mut OdeWrapper) {
102    if ode.is_null() {
103        c_invalid_arg!("ode is null");
104        return;
105    }
106    unsafe {
107        drop(Box::from_raw(ode));
108    }
109}
110
111/// Return a handle to the initial-condition solver options for an ODE.
112///
113/// # Safety
114/// `ode` must be a valid pointer created by this library. `out_options` must be
115/// a valid, writable pointer to receive ownership of the returned options
116/// object.
117#[unsafe(no_mangle)]
118pub unsafe extern "C" fn diffsol_ode_get_ic_options(
119    ode: *const OdeWrapper,
120    out_options: *mut *mut crate::initial_condition_options::InitialConditionSolverOptions,
121) -> i32 {
122    if ode.is_null() || out_options.is_null() {
123        return c_invalid_arg!("invalid arguments to diffsol_ode_get_ic_options");
124    }
125    let ode = unsafe { &*ode };
126    let options = ode.get_ic_options();
127    let boxed = Box::new(options);
128    unsafe {
129        *out_options = Box::into_raw(boxed);
130    }
131    DIFFSOL_OK
132}
133
134/// Return a handle to the ODE solver options for an ODE.
135///
136/// # Safety
137/// `ode` must be a valid pointer created by this library. `out_options` must be
138/// a valid, writable pointer to receive ownership of the returned options
139/// object.
140#[unsafe(no_mangle)]
141pub unsafe extern "C" fn diffsol_ode_get_options(
142    ode: *const OdeWrapper,
143    out_options: *mut *mut crate::ode_options::OdeSolverOptions,
144) -> i32 {
145    if ode.is_null() || out_options.is_null() {
146        return c_invalid_arg!("invalid arguments to diffsol_ode_get_options");
147    }
148    let ode = unsafe { &*ode };
149    let options = ode.get_options();
150    let boxed = Box::new(options);
151    unsafe {
152        *out_options = Box::into_raw(boxed);
153    }
154    DIFFSOL_OK
155}
156
157/// Evaluate the initial condition for an ODE.
158///
159/// `params_ptr` holds a column-major `[params_len, params_lanes]` array: `params_lanes` lanes of
160/// `params_len` values each, with lane `b`, parameter `k` at `b * params_len + k`.
161/// `params_lanes` must be the number of lanes of the ODE (`diffsol_ode_get_nbatch`) or 1. One
162/// lane of parameters is used for all the lanes. All the functions that take lanes of values use
163/// this layout.
164///
165/// The result is a `[nstates, nbatch]` array.
166///
167/// # Safety
168/// `ode` must be a valid mutable pointer created by this library. `params_ptr`
169/// must be either null with `params_len * params_lanes == 0` or point to
170/// `params_len * params_lanes` readable `f64` values. `out_tensor` must be a valid,
171/// writable pointer.
172#[unsafe(no_mangle)]
173pub unsafe extern "C" fn diffsol_ode_y0(
174    ode: *mut OdeWrapper,
175    params_ptr: *const f64,
176    params_len: usize,
177    params_lanes: usize,
178    out_tensor: *mut *mut Tensor,
179) -> i32 {
180    if ode.is_null()
181        || out_tensor.is_null()
182        || !valid_lanes_ptr(params_ptr, params_len, params_lanes)
183    {
184        c_invalid_arg!("invalid arguments to diffsol_ode_y0");
185        return DIFFSOL_BAD_ARG;
186    }
187    // SAFETY: the C caller guarantees the values, and the view lives only during the call
188    let params = unsafe { lanes_view(params_ptr, params_len, params_lanes) };
189    let ode = unsafe { &mut *ode };
190    match ode.y0(params) {
191        Ok(tensor) => {
192            let boxed = boxed_tensor(tensor);
193            unsafe {
194                *out_tensor = boxed;
195            }
196            DIFFSOL_OK
197        }
198        Err(err) => {
199            c_error!(&format!("{}", err));
200            DIFFSOL_ERR
201        }
202    }
203}
204
205/// Evaluate the ODE right-hand side at a given time and state.
206///
207/// `params_ptr` and `y_ptr` hold lanes of values, as for `diffsol_ode_y0`. The result is a
208/// `[nstates, nbatch]` array.
209///
210/// # Safety
211/// `ode` must be a valid mutable pointer created by this library. `params_ptr`
212/// and `y_ptr` must point to readable `f64` buffers of `len * lanes` values,
213/// unless that count is zero. `out_tensor` must be writable.
214#[unsafe(no_mangle)]
215pub unsafe extern "C" fn diffsol_ode_rhs(
216    ode: *mut OdeWrapper,
217    params_ptr: *const f64,
218    params_len: usize,
219    params_lanes: usize,
220    t: f64,
221    y_ptr: *const f64,
222    y_len: usize,
223    y_lanes: usize,
224    out_tensor: *mut *mut Tensor,
225) -> i32 {
226    if ode.is_null()
227        || out_tensor.is_null()
228        || !valid_lanes_ptr(params_ptr, params_len, params_lanes)
229        || !valid_lanes_ptr(y_ptr, y_len, y_lanes)
230    {
231        c_invalid_arg!("invalid arguments to diffsol_ode_rhs");
232        return DIFFSOL_BAD_ARG;
233    }
234    // SAFETY: the C caller guarantees the values of each input, and the views live only during
235    // the call
236    let (params, y) = unsafe {
237        (
238            lanes_view(params_ptr, params_len, params_lanes),
239            lanes_view(y_ptr, y_len, y_lanes),
240        )
241    };
242    let ode = unsafe { &mut *ode };
243    match ode.rhs(params, t, y) {
244        Ok(tensor) => {
245            let boxed = boxed_tensor(tensor);
246            unsafe {
247                *out_tensor = boxed;
248            }
249            DIFFSOL_OK
250        }
251        Err(err) => {
252            c_error!(&format!("{}", err));
253            DIFFSOL_ERR
254        }
255    }
256}
257
258/// Evaluate the ODE Jacobian-vector product at a given time and state.
259///
260/// `params_ptr`, `y_ptr` and `v_ptr` hold lanes of values, as for `diffsol_ode_y0`. The result
261/// is a `[nstates, nbatch]` array.
262///
263/// # Safety
264/// `ode` must be a valid mutable pointer created by this library. `params_ptr`,
265/// `y_ptr`, and `v_ptr` must point to readable `f64` buffers of `len * lanes`
266/// values, unless that count is zero. `out_tensor` must be writable.
267#[unsafe(no_mangle)]
268pub unsafe extern "C" fn diffsol_ode_rhs_jac_mul(
269    ode: *mut OdeWrapper,
270    params_ptr: *const f64,
271    params_len: usize,
272    params_lanes: usize,
273    t: f64,
274    y_ptr: *const f64,
275    y_len: usize,
276    y_lanes: usize,
277    v_ptr: *const f64,
278    v_len: usize,
279    v_lanes: usize,
280    out_tensor: *mut *mut Tensor,
281) -> i32 {
282    if ode.is_null()
283        || out_tensor.is_null()
284        || !valid_lanes_ptr(params_ptr, params_len, params_lanes)
285        || !valid_lanes_ptr(y_ptr, y_len, y_lanes)
286        || !valid_lanes_ptr(v_ptr, v_len, v_lanes)
287    {
288        c_invalid_arg!("invalid arguments to diffsol_ode_rhs_jac_mul");
289        return DIFFSOL_BAD_ARG;
290    }
291    // SAFETY: the C caller guarantees the values of each input, and the views live only during
292    // the call
293    let (params, y, v) = unsafe {
294        (
295            lanes_view(params_ptr, params_len, params_lanes),
296            lanes_view(y_ptr, y_len, y_lanes),
297            lanes_view(v_ptr, v_len, v_lanes),
298        )
299    };
300    let ode = unsafe { &mut *ode };
301    match ode.rhs_jac_mul(params, t, y, v) {
302        Ok(tensor) => {
303            let boxed = boxed_tensor(tensor);
304            unsafe {
305                *out_tensor = boxed;
306            }
307            DIFFSOL_OK
308        }
309        Err(err) => {
310            c_error!(&format!("{}", err));
311            DIFFSOL_ERR
312        }
313    }
314}
315
316/// Solve an ODE up to a final time.
317///
318/// `params_ptr` holds lanes of parameters, as for `diffsol_ode_y0`.
319///
320/// # Safety
321/// `ode` must be a valid mutable pointer created by this library. `params_ptr`
322/// must point to `params_len * params_lanes` readable `f64` values unless that
323/// count is zero. `out_solution` must be a valid, writable pointer.
324#[unsafe(no_mangle)]
325pub unsafe extern "C" fn diffsol_ode_solve(
326    ode: *mut OdeWrapper,
327    params_ptr: *const f64,
328    params_len: usize,
329    params_lanes: usize,
330    final_time: f64,
331    out_solution: *mut *mut SolutionWrapper,
332) -> i32 {
333    if ode.is_null()
334        || out_solution.is_null()
335        || !valid_lanes_ptr(params_ptr, params_len, params_lanes)
336    {
337        c_invalid_arg!("invalid arguments to diffsol_ode_solve");
338        return DIFFSOL_BAD_ARG;
339    }
340    // SAFETY: the C caller guarantees the values, and the view lives only during the call
341    let params = unsafe { lanes_view(params_ptr, params_len, params_lanes) };
342    let ode = unsafe { &mut *ode };
343    match ode.solve(params, final_time) {
344        Ok(new_solution) => {
345            unsafe {
346                *out_solution = Box::into_raw(Box::new(new_solution));
347            }
348            DIFFSOL_OK
349        }
350        Err(err) => {
351            c_error!(&format!("{}", err));
352            DIFFSOL_ERR
353        }
354    }
355}
356
357/// Solve an ODE and sample the solution at requested times.
358///
359/// `params_ptr` holds lanes of parameters, as for `diffsol_ode_y0`. All the lanes use the same
360/// `t_eval` times.
361///
362/// # Safety
363/// `ode` must be a valid mutable pointer created by this library. `params_ptr`
364/// must point to `params_len * params_lanes` readable `f64` values and `t_eval_ptr`
365/// to `t_eval_len` readable `f64` values, unless the count is zero. `out_solution`
366/// must be writable.
367#[unsafe(no_mangle)]
368pub unsafe extern "C" fn diffsol_ode_solve_dense(
369    ode: *mut OdeWrapper,
370    params_ptr: *const f64,
371    params_len: usize,
372    params_lanes: usize,
373    t_eval_ptr: *const f64,
374    t_eval_len: usize,
375    out_solution: *mut *mut SolutionWrapper,
376) -> i32 {
377    if ode.is_null()
378        || out_solution.is_null()
379        || !valid_lanes_ptr(params_ptr, params_len, params_lanes)
380        || !valid_f64_ptr(t_eval_ptr, t_eval_len)
381    {
382        c_invalid_arg!("invalid arguments to diffsol_ode_solve_dense");
383        return DIFFSOL_BAD_ARG;
384    }
385    // SAFETY: the C caller guarantees the values of each input, and the views live only during
386    // the call
387    let (params, t_eval) = unsafe {
388        (
389            lanes_view(params_ptr, params_len, params_lanes),
390            times_view(t_eval_ptr, t_eval_len),
391        )
392    };
393    let ode = unsafe { &mut *ode };
394    match ode.solve_dense(params, t_eval) {
395        Ok(new_solution) => {
396            unsafe {
397                *out_solution = Box::into_raw(Box::new(new_solution));
398            }
399            DIFFSOL_OK
400        }
401        Err(err) => {
402            c_error!(&format!("{}", err));
403            DIFFSOL_ERR
404        }
405    }
406}
407
408/// Solve an ODE and sample forward sensitivities at requested times.
409///
410/// `params_ptr` holds lanes of parameters, as for `diffsol_ode_y0`. All the lanes use the same
411/// `t_eval` times.
412///
413/// # Safety
414/// `ode` must be a valid mutable pointer created by this library. `params_ptr`
415/// must point to `params_len * params_lanes` readable `f64` values and `t_eval_ptr`
416/// to `t_eval_len` readable `f64` values, unless the count is zero. `out_solution`
417/// must be writable.
418#[unsafe(no_mangle)]
419pub unsafe extern "C" fn diffsol_ode_solve_fwd_sens(
420    ode: *mut OdeWrapper,
421    params_ptr: *const f64,
422    params_len: usize,
423    params_lanes: usize,
424    t_eval_ptr: *const f64,
425    t_eval_len: usize,
426    out_solution: *mut *mut SolutionWrapper,
427) -> i32 {
428    if ode.is_null()
429        || out_solution.is_null()
430        || !valid_lanes_ptr(params_ptr, params_len, params_lanes)
431        || !valid_f64_ptr(t_eval_ptr, t_eval_len)
432    {
433        c_invalid_arg!("invalid arguments to diffsol_ode_solve_fwd_sens");
434        return DIFFSOL_BAD_ARG;
435    }
436    // SAFETY: the C caller guarantees the values of each input, and the views live only during
437    // the call
438    let (params, t_eval) = unsafe {
439        (
440            lanes_view(params_ptr, params_len, params_lanes),
441            times_view(t_eval_ptr, t_eval_len),
442        )
443    };
444    let ode = unsafe { &mut *ode };
445    match ode.solve_fwd_sens(params, t_eval) {
446        Ok(new_solution) => {
447            unsafe {
448                *out_solution = Box::into_raw(Box::new(new_solution));
449            }
450            DIFFSOL_OK
451        }
452        Err(err) => {
453            c_error!(&format!("{}", err));
454            DIFFSOL_ERR
455        }
456    }
457}
458
459/// Solve an ODE at requested times, and keep the solver state after each step for a later
460/// reverse-mode pass with `diffsol_ode_solve_vjp_bkwd`.
461///
462/// `params_ptr` holds lanes of parameters, as for `diffsol_ode_y0`. All the lanes use the same
463/// `t_eval` times. On success, `out_solution` receives a solution (free it with
464/// `diffsol_solution_wrapper_free`) and `out_checkpoint` a checkpoint (free it with
465/// `diffsol_vjp_checkpoint_free`).
466///
467/// # Safety
468/// `ode` must be a valid mutable pointer created by this library. `params_ptr`
469/// must point to `params_len * params_lanes` readable `f64` values and `t_eval_ptr`
470/// to `t_eval_len` readable `f64` values, unless the count is zero. `out_solution` and
471/// `out_checkpoint` must be writable.
472#[unsafe(no_mangle)]
473pub unsafe extern "C" fn diffsol_ode_solve_vjp_fwd(
474    ode: *mut OdeWrapper,
475    params_ptr: *const f64,
476    params_len: usize,
477    params_lanes: usize,
478    t_eval_ptr: *const f64,
479    t_eval_len: usize,
480    out_solution: *mut *mut SolutionWrapper,
481    out_checkpoint: *mut *mut VjpCheckpointWrapper,
482) -> i32 {
483    if ode.is_null()
484        || out_solution.is_null()
485        || out_checkpoint.is_null()
486        || !valid_lanes_ptr(params_ptr, params_len, params_lanes)
487        || !valid_f64_ptr(t_eval_ptr, t_eval_len)
488    {
489        c_invalid_arg!("invalid arguments to diffsol_ode_solve_vjp_fwd");
490        return DIFFSOL_BAD_ARG;
491    }
492    // SAFETY: the C caller guarantees the values of each input, and the views live only during
493    // the call
494    let (params, t_eval) = unsafe {
495        (
496            lanes_view(params_ptr, params_len, params_lanes),
497            times_view(t_eval_ptr, t_eval_len),
498        )
499    };
500    let ode = unsafe { &mut *ode };
501    match ode.solve_vjp_fwd(params, t_eval) {
502        Ok((solution, checkpoint)) => {
503            unsafe {
504                *out_solution = Box::into_raw(Box::new(solution));
505                *out_checkpoint = Box::into_raw(Box::new(checkpoint));
506            }
507            DIFFSOL_OK
508        }
509        Err(err) => {
510            c_error!(&format!("{}", err));
511            DIFFSOL_ERR
512        }
513    }
514}
515
516/// Pull the gradient of a scalar objective with respect to the model outputs back through the
517/// steps of a forward pass of `diffsol_ode_solve_vjp_fwd`, to the parameters.
518///
519/// `dgdu_ptr` holds a column-major `[nout, nt, dgdu_lanes]` array: lane `l`, time `i`, output
520/// `k` is at index `(l * nt + i) * nout + k`, with `nt` the number of times of the forward
521/// pass. `dgdu_lanes` is `ndirections * nbatch`, for `ndirections` objectives (directions) in
522/// one backward pass: lane `l = b * ndirections + d` is direction `d` of batch lane `b`.
523/// `dgdu_lanes` can also be 1, for one direction with the same values in all the batch lanes.
524/// On success, `out_tensor` receives a `[nparams, ndirections, nbatch]` gradient array (free it
525/// with `diffsol_tensor_free`).
526///
527/// The backward pass uses the method and the linear solver of the forward pass. The checkpoint
528/// does not change, so it can be used again.
529///
530/// # Safety
531/// `ode` must be a valid mutable pointer created by this library and `checkpoint` a valid
532/// pointer returned by `diffsol_ode_solve_vjp_fwd` for this ODE. `dgdu_ptr` must point to
533/// `nout * nt * dgdu_lanes` readable `f64` values, unless the count is zero. `out_tensor` must
534/// be writable.
535#[unsafe(no_mangle)]
536pub unsafe extern "C" fn diffsol_ode_solve_vjp_bkwd(
537    ode: *mut OdeWrapper,
538    checkpoint: *const VjpCheckpointWrapper,
539    dgdu_ptr: *const f64,
540    nout: usize,
541    nt: usize,
542    dgdu_lanes: usize,
543    out_tensor: *mut *mut Tensor,
544) -> i32 {
545    if ode.is_null()
546        || checkpoint.is_null()
547        || out_tensor.is_null()
548        || !nout
549            .checked_mul(nt)
550            .is_some_and(|len| valid_lanes_ptr(dgdu_ptr, len, dgdu_lanes))
551    {
552        c_invalid_arg!("invalid arguments to diffsol_ode_solve_vjp_bkwd");
553        return DIFFSOL_BAD_ARG;
554    }
555    // SAFETY: the C caller guarantees the values, and the view lives only during the call
556    let dgdu_eval = unsafe {
557        TensorView::new_col_major(
558            dgdu_ptr as *const u8,
559            &[nout, nt, dgdu_lanes],
560            ScalarType::F64,
561        )
562    };
563    let (ode, checkpoint) = unsafe { (&mut *ode, &*checkpoint) };
564    match ode.solve_vjp_bkwd(checkpoint, dgdu_eval) {
565        Ok(gradient) => {
566            unsafe {
567                *out_tensor = boxed_tensor(gradient);
568            }
569            DIFFSOL_OK
570        }
571        Err(err) => {
572            c_error!(&format!("{}", err));
573            DIFFSOL_ERR
574        }
575    }
576}
577
578/// Return the number of lanes (independent copies of the system) of an ODE in `out_value`.
579///
580/// # Safety
581/// `ode` must be a valid pointer created by this library. `out_value` must be writable.
582#[unsafe(no_mangle)]
583pub unsafe extern "C" fn diffsol_ode_get_nbatch(
584    ode: *const OdeWrapper,
585    out_value: *mut usize,
586) -> i32 {
587    if ode.is_null() || out_value.is_null() {
588        c_invalid_arg!("invalid arguments to diffsol_ode_get_nbatch");
589        return DIFFSOL_BAD_ARG;
590    }
591    let ode = unsafe { &*ode };
592    match ode.get_nbatch() {
593        Ok(value) => {
594            unsafe {
595                *out_value = value;
596            }
597            DIFFSOL_OK
598        }
599        Err(err) => {
600            c_error!(&format!("{}", err));
601            DIFFSOL_ERR
602        }
603    }
604}
605
606/// Return the matrix type configured for an ODE.
607///
608/// # Safety
609/// `ode` must be a valid pointer created by this library.
610#[unsafe(no_mangle)]
611pub unsafe extern "C" fn diffsol_ode_get_matrix_type(ode: *const OdeWrapper) -> i32 {
612    if ode.is_null() {
613        c_invalid_arg!("ode is null");
614        return -1;
615    }
616    let ode = unsafe { &*ode };
617    match ode.get_matrix_type() {
618        Ok(value) => matrix_type_to_i32(value),
619        Err(err) => {
620            c_error!(&format!("{}", err));
621            -1
622        }
623    }
624}
625
626/// Return the ODE solver enum configured for an ODE.
627///
628/// # Safety
629/// `ode` must be a valid pointer created by this library.
630#[unsafe(no_mangle)]
631pub unsafe extern "C" fn diffsol_ode_get_ode_solver(ode: *const OdeWrapper) -> i32 {
632    if ode.is_null() {
633        c_invalid_arg!("ode is null");
634        return -1;
635    }
636    let ode = unsafe { &*ode };
637    match ode.get_ode_solver() {
638        Ok(value) => ode_solver_to_i32(value),
639        Err(err) => {
640            c_error!(&format!("{}", err));
641            -1
642        }
643    }
644}
645
646/// Set the ODE solver enum for an ODE.
647///
648/// # Safety
649/// `ode` must be a valid mutable pointer created by this library.
650#[unsafe(no_mangle)]
651pub unsafe extern "C" fn diffsol_ode_set_ode_solver(ode: *mut OdeWrapper, value: i32) -> i32 {
652    if ode.is_null() {
653        c_invalid_arg!("ode is null");
654        return DIFFSOL_BAD_ARG;
655    }
656    let value = match ode_solver_from_i32(value) {
657        Some(v) => v,
658        None => {
659            c_invalid_arg!("invalid ode_solver");
660            return DIFFSOL_BAD_ARG;
661        }
662    };
663    let ode = unsafe { &mut *ode };
664    match ode.set_ode_solver(value) {
665        Ok(()) => DIFFSOL_OK,
666        Err(err) => c_error!(&format!("{}", err)),
667    }
668}
669
670/// Return the linear solver enum configured for an ODE.
671///
672/// # Safety
673/// `ode` must be a valid pointer created by this library.
674#[unsafe(no_mangle)]
675pub unsafe extern "C" fn diffsol_ode_get_linear_solver(ode: *const OdeWrapper) -> i32 {
676    if ode.is_null() {
677        c_invalid_arg!("ode is null");
678        return -1;
679    }
680    let ode = unsafe { &*ode };
681    match ode.get_linear_solver() {
682        Ok(value) => linear_solver_to_i32(value),
683        Err(err) => {
684            c_error!(&format!("{}", err));
685            -1
686        }
687    }
688}
689
690/// Set the linear solver enum for an ODE.
691///
692/// # Safety
693/// `ode` must be a valid mutable pointer created by this library.
694#[unsafe(no_mangle)]
695pub unsafe extern "C" fn diffsol_ode_set_linear_solver(ode: *mut OdeWrapper, value: i32) -> i32 {
696    if ode.is_null() {
697        c_invalid_arg!("ode is null");
698        return DIFFSOL_BAD_ARG;
699    }
700    let value = match linear_solver_from_i32(value) {
701        Some(v) => v,
702        None => {
703            c_invalid_arg!("invalid linear_solver");
704            return DIFFSOL_BAD_ARG;
705        }
706    };
707    let ode = unsafe { &mut *ode };
708    match ode.set_linear_solver(value) {
709        Ok(()) => DIFFSOL_OK,
710        Err(err) => c_error!(&format!("{}", err)),
711    }
712}
713
714/// Return the relative tolerance configured for an ODE.
715///
716/// # Safety
717/// `ode` must be a valid pointer created by this library. `out_value` must be a
718/// valid, writable pointer.
719#[unsafe(no_mangle)]
720pub unsafe extern "C" fn diffsol_ode_get_rtol(ode: *const OdeWrapper, out_value: *mut f64) -> i32 {
721    if ode.is_null() || out_value.is_null() {
722        c_invalid_arg!("invalid arguments to diffsol_ode_get_rtol");
723        return DIFFSOL_BAD_ARG;
724    }
725    let ode = unsafe { &*ode };
726    match ode.get_rtol() {
727        Ok(value) => {
728            unsafe {
729                *out_value = value;
730            }
731            DIFFSOL_OK
732        }
733        Err(err) => c_error!(&format!("{}", err)),
734    }
735}
736
737/// Set the relative tolerance for an ODE.
738///
739/// # Safety
740/// `ode` must be a valid mutable pointer created by this library.
741#[unsafe(no_mangle)]
742pub unsafe extern "C" fn diffsol_ode_set_rtol(ode: *mut OdeWrapper, value: f64) -> i32 {
743    if ode.is_null() {
744        c_invalid_arg!("ode is null");
745        return DIFFSOL_BAD_ARG;
746    }
747    let ode = unsafe { &mut *ode };
748    match ode.set_rtol(value) {
749        Ok(()) => DIFFSOL_OK,
750        Err(err) => c_error!(&format!("{}", err)),
751    }
752}
753
754/// Return the absolute tolerance configured for an ODE.
755///
756/// # Safety
757/// `ode` must be a valid pointer created by this library. `out_value` must be a
758/// valid, writable pointer.
759#[unsafe(no_mangle)]
760pub unsafe extern "C" fn diffsol_ode_get_atol(ode: *const OdeWrapper, out_value: *mut f64) -> i32 {
761    if ode.is_null() || out_value.is_null() {
762        c_invalid_arg!("invalid arguments to diffsol_ode_get_atol");
763        return DIFFSOL_BAD_ARG;
764    }
765    let ode = unsafe { &*ode };
766    match ode.get_atol() {
767        Ok(value) => {
768            unsafe {
769                *out_value = value;
770            }
771            DIFFSOL_OK
772        }
773        Err(err) => c_error!(&format!("{}", err)),
774    }
775}
776
777/// Set the absolute tolerance for an ODE.
778///
779/// # Safety
780/// `ode` must be a valid mutable pointer created by this library.
781#[unsafe(no_mangle)]
782pub unsafe extern "C" fn diffsol_ode_set_atol(ode: *mut OdeWrapper, value: f64) -> i32 {
783    if ode.is_null() {
784        c_invalid_arg!("ode is null");
785        return DIFFSOL_BAD_ARG;
786    }
787    let ode = unsafe { &mut *ode };
788    match ode.set_atol(value) {
789        Ok(()) => DIFFSOL_OK,
790        Err(err) => c_error!(&format!("{}", err)),
791    }
792}
793
794/// Return the initial time configured for an ODE.
795///
796/// # Safety
797/// `ode` must be a valid pointer created by this library. `out_value` must be a
798/// valid, writable pointer.
799#[unsafe(no_mangle)]
800pub unsafe extern "C" fn diffsol_ode_get_t0(ode: *const OdeWrapper, out_value: *mut f64) -> i32 {
801    if ode.is_null() || out_value.is_null() {
802        c_invalid_arg!("invalid arguments to diffsol_ode_get_t0");
803        return DIFFSOL_BAD_ARG;
804    }
805    let ode = unsafe { &*ode };
806    match ode.get_t0() {
807        Ok(value) => {
808            unsafe {
809                *out_value = value;
810            }
811            DIFFSOL_OK
812        }
813        Err(err) => c_error!(&format!("{}", err)),
814    }
815}
816
817/// Set the initial time for an ODE.
818///
819/// # Safety
820/// `ode` must be a valid mutable pointer created by this library.
821#[unsafe(no_mangle)]
822pub unsafe extern "C" fn diffsol_ode_set_t0(ode: *mut OdeWrapper, value: f64) -> i32 {
823    if ode.is_null() {
824        c_invalid_arg!("ode is null");
825        return DIFFSOL_BAD_ARG;
826    }
827    let ode = unsafe { &mut *ode };
828    match ode.set_t0(value) {
829        Ok(()) => DIFFSOL_OK,
830        Err(err) => c_error!(&format!("{}", err)),
831    }
832}
833
834/// Return the initial step size configured for an ODE.
835///
836/// # Safety
837/// `ode` must be a valid pointer created by this library. `out_value` must be a
838/// valid, writable pointer.
839#[unsafe(no_mangle)]
840pub unsafe extern "C" fn diffsol_ode_get_h0(ode: *const OdeWrapper, out_value: *mut f64) -> i32 {
841    if ode.is_null() || out_value.is_null() {
842        c_invalid_arg!("invalid arguments to diffsol_ode_get_h0");
843        return DIFFSOL_BAD_ARG;
844    }
845    let ode = unsafe { &*ode };
846    match ode.get_h0() {
847        Ok(value) => {
848            unsafe {
849                *out_value = value;
850            }
851            DIFFSOL_OK
852        }
853        Err(err) => c_error!(&format!("{}", err)),
854    }
855}
856
857/// Set the initial step size for an ODE.
858///
859/// # Safety
860/// `ode` must be a valid mutable pointer created by this library.
861#[unsafe(no_mangle)]
862pub unsafe extern "C" fn diffsol_ode_set_h0(ode: *mut OdeWrapper, value: f64) -> i32 {
863    if ode.is_null() {
864        c_invalid_arg!("ode is null");
865        return DIFFSOL_BAD_ARG;
866    }
867    let ode = unsafe { &mut *ode };
868    match ode.set_h0(value) {
869        Ok(()) => DIFFSOL_OK,
870        Err(err) => c_error!(&format!("{}", err)),
871    }
872}
873
874/// Return whether output equations are integrated alongside state equations.
875///
876/// # Safety
877/// `ode` must be a valid pointer created by this library. `out_value` must be a
878/// valid, writable pointer. The value is written as 0 for false and 1 for true.
879#[unsafe(no_mangle)]
880pub unsafe extern "C" fn diffsol_ode_get_integrate_out(
881    ode: *const OdeWrapper,
882    out_value: *mut i32,
883) -> i32 {
884    if ode.is_null() || out_value.is_null() {
885        c_invalid_arg!("invalid arguments to diffsol_ode_get_integrate_out");
886        return DIFFSOL_BAD_ARG;
887    }
888    let ode = unsafe { &*ode };
889    match ode.get_integrate_out() {
890        Ok(value) => {
891            unsafe {
892                *out_value = i32::from(value);
893            }
894            DIFFSOL_OK
895        }
896        Err(err) => c_error!(&format!("{}", err)),
897    }
898}
899
900/// Set whether output equations are integrated alongside state equations.
901///
902/// # Safety
903/// `ode` must be a valid mutable pointer created by this library. Any non-zero
904/// `value` is treated as true.
905#[unsafe(no_mangle)]
906pub unsafe extern "C" fn diffsol_ode_set_integrate_out(ode: *mut OdeWrapper, value: i32) -> i32 {
907    if ode.is_null() {
908        c_invalid_arg!("ode is null");
909        return DIFFSOL_BAD_ARG;
910    }
911    let ode = unsafe { &mut *ode };
912    match ode.set_integrate_out(value != 0) {
913        Ok(()) => DIFFSOL_OK,
914        Err(err) => c_error!(&format!("{}", err)),
915    }
916}
917
918unsafe fn write_optional_f64(value: Option<f64>, out_is_some: *mut i32, out_value: *mut f64) {
919    match value {
920        Some(value) => unsafe {
921            *out_is_some = 1;
922            *out_value = value;
923        },
924        None => unsafe {
925            *out_is_some = 0;
926            *out_value = 0.0;
927        },
928    }
929}
930
931/// Return the optional sensitivity relative tolerance.
932///
933/// # Safety
934/// All pointers must be valid and writable where applicable.
935#[unsafe(no_mangle)]
936pub unsafe extern "C" fn diffsol_ode_get_sens_rtol(
937    ode: *const OdeWrapper,
938    out_is_some: *mut i32,
939    out_value: *mut f64,
940) -> i32 {
941    if ode.is_null() || out_is_some.is_null() || out_value.is_null() {
942        c_invalid_arg!("invalid arguments to diffsol_ode_get_sens_rtol");
943        return DIFFSOL_BAD_ARG;
944    }
945    let ode = unsafe { &*ode };
946    match ode.get_sens_rtol() {
947        Ok(value) => {
948            unsafe {
949                write_optional_f64(value, out_is_some, out_value);
950            }
951            DIFFSOL_OK
952        }
953        Err(err) => c_error!(&format!("{}", err)),
954    }
955}
956
957/// Set the optional sensitivity relative tolerance.
958///
959/// # Safety
960/// `ode` must be a valid mutable pointer created by this library.
961#[unsafe(no_mangle)]
962pub unsafe extern "C" fn diffsol_ode_set_sens_rtol(
963    ode: *mut OdeWrapper,
964    value_is_some: i32,
965    value: f64,
966) -> i32 {
967    if ode.is_null() {
968        c_invalid_arg!("ode is null");
969        return DIFFSOL_BAD_ARG;
970    }
971    let ode = unsafe { &mut *ode };
972    match ode.set_sens_rtol((value_is_some != 0).then_some(value)) {
973        Ok(()) => DIFFSOL_OK,
974        Err(err) => c_error!(&format!("{}", err)),
975    }
976}
977
978/// Return the optional sensitivity absolute tolerance.
979///
980/// # Safety
981/// All pointers must be valid and writable where applicable.
982#[unsafe(no_mangle)]
983pub unsafe extern "C" fn diffsol_ode_get_sens_atol(
984    ode: *const OdeWrapper,
985    out_is_some: *mut i32,
986    out_value: *mut f64,
987) -> i32 {
988    if ode.is_null() || out_is_some.is_null() || out_value.is_null() {
989        c_invalid_arg!("invalid arguments to diffsol_ode_get_sens_atol");
990        return DIFFSOL_BAD_ARG;
991    }
992    let ode = unsafe { &*ode };
993    match ode.get_sens_atol() {
994        Ok(value) => {
995            unsafe {
996                write_optional_f64(value, out_is_some, out_value);
997            }
998            DIFFSOL_OK
999        }
1000        Err(err) => c_error!(&format!("{}", err)),
1001    }
1002}
1003
1004/// Set the optional sensitivity absolute tolerance.
1005///
1006/// # Safety
1007/// `ode` must be a valid mutable pointer created by this library.
1008#[unsafe(no_mangle)]
1009pub unsafe extern "C" fn diffsol_ode_set_sens_atol(
1010    ode: *mut OdeWrapper,
1011    value_is_some: i32,
1012    value: f64,
1013) -> i32 {
1014    if ode.is_null() {
1015        c_invalid_arg!("ode is null");
1016        return DIFFSOL_BAD_ARG;
1017    }
1018    let ode = unsafe { &mut *ode };
1019    match ode.set_sens_atol((value_is_some != 0).then_some(value)) {
1020        Ok(()) => DIFFSOL_OK,
1021        Err(err) => c_error!(&format!("{}", err)),
1022    }
1023}
1024
1025/// Return the optional output relative tolerance.
1026///
1027/// # Safety
1028/// All pointers must be valid and writable where applicable.
1029#[unsafe(no_mangle)]
1030pub unsafe extern "C" fn diffsol_ode_get_out_rtol(
1031    ode: *const OdeWrapper,
1032    out_is_some: *mut i32,
1033    out_value: *mut f64,
1034) -> i32 {
1035    if ode.is_null() || out_is_some.is_null() || out_value.is_null() {
1036        c_invalid_arg!("invalid arguments to diffsol_ode_get_out_rtol");
1037        return DIFFSOL_BAD_ARG;
1038    }
1039    let ode = unsafe { &*ode };
1040    match ode.get_out_rtol() {
1041        Ok(value) => {
1042            unsafe {
1043                write_optional_f64(value, out_is_some, out_value);
1044            }
1045            DIFFSOL_OK
1046        }
1047        Err(err) => c_error!(&format!("{}", err)),
1048    }
1049}
1050
1051/// Set the optional output relative tolerance.
1052///
1053/// # Safety
1054/// `ode` must be a valid mutable pointer created by this library.
1055#[unsafe(no_mangle)]
1056pub unsafe extern "C" fn diffsol_ode_set_out_rtol(
1057    ode: *mut OdeWrapper,
1058    value_is_some: i32,
1059    value: f64,
1060) -> i32 {
1061    if ode.is_null() {
1062        c_invalid_arg!("ode is null");
1063        return DIFFSOL_BAD_ARG;
1064    }
1065    let ode = unsafe { &mut *ode };
1066    match ode.set_out_rtol((value_is_some != 0).then_some(value)) {
1067        Ok(()) => DIFFSOL_OK,
1068        Err(err) => c_error!(&format!("{}", err)),
1069    }
1070}
1071
1072/// Return the optional output absolute tolerance.
1073///
1074/// # Safety
1075/// All pointers must be valid and writable where applicable.
1076#[unsafe(no_mangle)]
1077pub unsafe extern "C" fn diffsol_ode_get_out_atol(
1078    ode: *const OdeWrapper,
1079    out_is_some: *mut i32,
1080    out_value: *mut f64,
1081) -> i32 {
1082    if ode.is_null() || out_is_some.is_null() || out_value.is_null() {
1083        c_invalid_arg!("invalid arguments to diffsol_ode_get_out_atol");
1084        return DIFFSOL_BAD_ARG;
1085    }
1086    let ode = unsafe { &*ode };
1087    match ode.get_out_atol() {
1088        Ok(value) => {
1089            unsafe {
1090                write_optional_f64(value, out_is_some, out_value);
1091            }
1092            DIFFSOL_OK
1093        }
1094        Err(err) => c_error!(&format!("{}", err)),
1095    }
1096}
1097
1098/// Set the optional output absolute tolerance.
1099///
1100/// # Safety
1101/// `ode` must be a valid mutable pointer created by this library.
1102#[unsafe(no_mangle)]
1103pub unsafe extern "C" fn diffsol_ode_set_out_atol(
1104    ode: *mut OdeWrapper,
1105    value_is_some: i32,
1106    value: f64,
1107) -> i32 {
1108    if ode.is_null() {
1109        c_invalid_arg!("ode is null");
1110        return DIFFSOL_BAD_ARG;
1111    }
1112    let ode = unsafe { &mut *ode };
1113    match ode.set_out_atol((value_is_some != 0).then_some(value)) {
1114        Ok(()) => DIFFSOL_OK,
1115        Err(err) => c_error!(&format!("{}", err)),
1116    }
1117}
1118
1119/// Return the optional adjoint parameter relative tolerance.
1120///
1121/// # Safety
1122/// All pointers must be valid and writable where applicable.
1123#[unsafe(no_mangle)]
1124pub unsafe extern "C" fn diffsol_ode_get_param_rtol(
1125    ode: *const OdeWrapper,
1126    out_is_some: *mut i32,
1127    out_value: *mut f64,
1128) -> i32 {
1129    if ode.is_null() || out_is_some.is_null() || out_value.is_null() {
1130        c_invalid_arg!("invalid arguments to diffsol_ode_get_param_rtol");
1131        return DIFFSOL_BAD_ARG;
1132    }
1133    let ode = unsafe { &*ode };
1134    match ode.get_param_rtol() {
1135        Ok(value) => {
1136            unsafe {
1137                write_optional_f64(value, out_is_some, out_value);
1138            }
1139            DIFFSOL_OK
1140        }
1141        Err(err) => c_error!(&format!("{}", err)),
1142    }
1143}
1144
1145/// Set the optional adjoint parameter relative tolerance.
1146///
1147/// # Safety
1148/// `ode` must be a valid mutable pointer created by this library.
1149#[unsafe(no_mangle)]
1150pub unsafe extern "C" fn diffsol_ode_set_param_rtol(
1151    ode: *mut OdeWrapper,
1152    value_is_some: i32,
1153    value: f64,
1154) -> i32 {
1155    if ode.is_null() {
1156        c_invalid_arg!("ode is null");
1157        return DIFFSOL_BAD_ARG;
1158    }
1159    let ode = unsafe { &mut *ode };
1160    match ode.set_param_rtol((value_is_some != 0).then_some(value)) {
1161        Ok(()) => DIFFSOL_OK,
1162        Err(err) => c_error!(&format!("{}", err)),
1163    }
1164}
1165
1166/// Return the optional adjoint parameter absolute tolerance.
1167///
1168/// # Safety
1169/// All pointers must be valid and writable where applicable.
1170#[unsafe(no_mangle)]
1171pub unsafe extern "C" fn diffsol_ode_get_param_atol(
1172    ode: *const OdeWrapper,
1173    out_is_some: *mut i32,
1174    out_value: *mut f64,
1175) -> i32 {
1176    if ode.is_null() || out_is_some.is_null() || out_value.is_null() {
1177        c_invalid_arg!("invalid arguments to diffsol_ode_get_param_atol");
1178        return DIFFSOL_BAD_ARG;
1179    }
1180    let ode = unsafe { &*ode };
1181    match ode.get_param_atol() {
1182        Ok(value) => {
1183            unsafe {
1184                write_optional_f64(value, out_is_some, out_value);
1185            }
1186            DIFFSOL_OK
1187        }
1188        Err(err) => c_error!(&format!("{}", err)),
1189    }
1190}
1191
1192/// Set the optional adjoint parameter absolute tolerance.
1193///
1194/// # Safety
1195/// `ode` must be a valid mutable pointer created by this library.
1196#[unsafe(no_mangle)]
1197pub unsafe extern "C" fn diffsol_ode_set_param_atol(
1198    ode: *mut OdeWrapper,
1199    value_is_some: i32,
1200    value: f64,
1201) -> i32 {
1202    if ode.is_null() {
1203        c_invalid_arg!("ode is null");
1204        return DIFFSOL_BAD_ARG;
1205    }
1206    let ode = unsafe { &mut *ode };
1207    match ode.set_param_atol((value_is_some != 0).then_some(value)) {
1208        Ok(()) => DIFFSOL_OK,
1209        Err(err) => c_error!(&format!("{}", err)),
1210    }
1211}
1212
1213#[cfg(test)]
1214mod tests {
1215    use std::ffi::CStr;
1216    use std::ptr;
1217
1218    use crate::error_c::{
1219        diffsol_error_code, diffsol_last_error_file, diffsol_last_error_line,
1220        diffsol_last_error_message,
1221    };
1222    #[cfg(feature = "diffsl-external-f64")]
1223    use crate::initial_condition_options::InitialConditionSolverOptions;
1224    #[cfg(feature = "diffsl-external-f64")]
1225    use crate::initial_condition_options_c::{
1226        diffsol_ic_options_free, diffsol_ic_options_get_max_linesearch_iterations,
1227        diffsol_ic_options_get_use_linesearch, diffsol_ic_options_set_max_linesearch_iterations,
1228        diffsol_ic_options_set_use_linesearch,
1229    };
1230    use crate::linear_solver_type::LinearSolverType;
1231    use crate::linear_solver_type_c::{
1232        diffsol_linear_solver_type_count, diffsol_linear_solver_type_is_valid,
1233        diffsol_linear_solver_type_name, linear_solver_to_i32,
1234    };
1235    use crate::matrix_type::MatrixType;
1236    use crate::matrix_type_c::{
1237        diffsol_matrix_type_count, diffsol_matrix_type_is_valid, diffsol_matrix_type_name,
1238        matrix_type_to_i32,
1239    };
1240    #[cfg(feature = "diffsl-external-f64")]
1241    use crate::ode_options::OdeSolverOptions;
1242    #[cfg(feature = "diffsl-external-f64")]
1243    use crate::ode_options_c::{
1244        diffsol_ode_options_free, diffsol_ode_options_get_max_nonlinear_solver_failures,
1245        diffsol_ode_options_get_max_nonlinear_solver_iterations,
1246        diffsol_ode_options_get_min_timestep,
1247        diffsol_ode_options_set_max_nonlinear_solver_failures,
1248        diffsol_ode_options_set_max_nonlinear_solver_iterations,
1249        diffsol_ode_options_set_min_timestep,
1250    };
1251    use crate::ode_solver_type::OdeSolverType;
1252    use crate::ode_solver_type_c::{
1253        diffsol_ode_solver_type_count, diffsol_ode_solver_type_is_valid,
1254        diffsol_ode_solver_type_name, ode_solver_to_i32,
1255    };
1256    use crate::scalar_type::ScalarType;
1257    use crate::scalar_type_c::{
1258        diffsol_scalar_type_count, diffsol_scalar_type_is_valid, diffsol_scalar_type_name,
1259        scalar_type_to_i32,
1260    };
1261    #[cfg(feature = "diffsl-external-f64")]
1262    use crate::solution_wrapper_c::{
1263        diffsol_solution_wrapper_get_sens, diffsol_solution_wrapper_get_ts,
1264        diffsol_solution_wrapper_get_ys,
1265    };
1266    use crate::test_support::clear_last_error;
1267    #[cfg(feature = "diffsl-external-f64")]
1268    use crate::test_support::{
1269        assert_close, ffi_free_solution, ffi_read_tensor_1d, ffi_read_tensor_list_matrices,
1270        ffi_read_tensor_matrix, ffi_read_tensor_vector, find_time_window, logistic_state,
1271        logistic_state_dr, mass_state_deps, rhs_input_deps, rhs_state_deps, ASSERT_TOL,
1272        LOGISTIC_X0,
1273    };
1274
1275    use super::*;
1276    #[cfg(any(
1277        feature = "external",
1278        feature = "diffsl-external-dynamic",
1279        feature = "diffsl-cranelift",
1280        feature = "diffsl-llvm",
1281    ))]
1282    #[allow(unused_imports)]
1283    use crate::ode_diffsl_c::*;
1284
1285    unsafe fn c_string(ptr: *const std::os::raw::c_char) -> String {
1286        assert!(!ptr.is_null(), "expected non-null C string");
1287        unsafe { CStr::from_ptr(ptr) }.to_str().unwrap().to_owned()
1288    }
1289
1290    unsafe fn assert_last_error_set() {
1291        assert_eq!(
1292            unsafe { diffsol_error_code() },
1293            1,
1294            "expected last error to be set"
1295        );
1296        let message_ptr = unsafe { diffsol_last_error_message() };
1297        assert!(
1298            !message_ptr.is_null(),
1299            "expected last error message to be set"
1300        );
1301        let message = unsafe { c_string(message_ptr) };
1302        assert!(
1303            !message.is_empty(),
1304            "expected last error message to be non-empty"
1305        );
1306        let file_ptr = unsafe { diffsol_last_error_file() };
1307        assert!(!file_ptr.is_null(), "expected last error file to be set");
1308        assert!(
1309            unsafe { diffsol_last_error_line() } > 0,
1310            "expected last error line to be > 0"
1311        );
1312    }
1313
1314    unsafe fn assert_last_error_contains(expected_substring: &str) {
1315        unsafe { assert_last_error_set() };
1316        let message_ptr = unsafe { diffsol_last_error_message() };
1317        let message = unsafe { c_string(message_ptr) };
1318        assert!(
1319            message.contains(expected_substring),
1320            "expected last error message to contain {expected_substring:?}, got {message:?}"
1321        );
1322        let file_ptr = unsafe { diffsol_last_error_file() };
1323        assert!(!file_ptr.is_null(), "expected last error file to be set");
1324        assert!(
1325            unsafe { diffsol_last_error_line() } > 0,
1326            "expected last error line to be > 0"
1327        );
1328    }
1329
1330    #[cfg(feature = "diffsl-external-f64")]
1331    fn to_dep_pairs(values: &[(usize, usize)]) -> Vec<DiffsolDepPair> {
1332        values
1333            .iter()
1334            .map(|&(row, col)| DiffsolDepPair { row, col })
1335            .collect()
1336    }
1337
1338    #[cfg(feature = "diffsl-external-f64")]
1339    unsafe fn make_ode_ptr(
1340        matrix_type: i32,
1341        linear_solver: i32,
1342        ode_solver: i32,
1343    ) -> *mut OdeWrapper {
1344        let rhs_state_deps = to_dep_pairs(&rhs_state_deps());
1345        let rhs_input_deps = to_dep_pairs(&rhs_input_deps());
1346        let mass_state_deps = to_dep_pairs(&mass_state_deps());
1347        unsafe {
1348            diffsol_ode_new_external(
1349                matrix_type,
1350                linear_solver,
1351                ode_solver,
1352                rhs_state_deps.as_ptr(),
1353                rhs_state_deps.len(),
1354                rhs_input_deps.as_ptr(),
1355                rhs_input_deps.len(),
1356                mass_state_deps.as_ptr(),
1357                mass_state_deps.len(),
1358            )
1359        }
1360    }
1361
1362    #[test]
1363    fn c_api_reports_enum_metadata() {
1364        clear_last_error();
1365        unsafe {
1366            assert_eq!(diffsol_matrix_type_count(), 3);
1367            assert_eq!(diffsol_ode_solver_type_count(), 4);
1368            assert_eq!(diffsol_linear_solver_type_count(), 3);
1369            assert_eq!(diffsol_scalar_type_count(), 2);
1370
1371            assert_eq!(
1372                c_string(diffsol_matrix_type_name(matrix_type_to_i32(
1373                    MatrixType::NalgebraDense
1374                ))),
1375                "nalgebra_dense"
1376            );
1377            assert_eq!(
1378                c_string(diffsol_ode_solver_type_name(ode_solver_to_i32(
1379                    OdeSolverType::Bdf
1380                ))),
1381                "bdf"
1382            );
1383            assert_eq!(
1384                c_string(diffsol_linear_solver_type_name(linear_solver_to_i32(
1385                    LinearSolverType::Default
1386                ))),
1387                "default"
1388            );
1389            assert_eq!(
1390                c_string(diffsol_scalar_type_name(scalar_type_to_i32(
1391                    ScalarType::F64
1392                ))),
1393                "f64"
1394            );
1395        }
1396    }
1397
1398    #[test]
1399    fn c_api_invalid_enums_set_last_error() {
1400        clear_last_error();
1401        unsafe {
1402            assert_eq!(diffsol_matrix_type_is_valid(99), 0);
1403            assert_last_error_contains("invalid matrix_type");
1404            clear_last_error();
1405
1406            assert_eq!(diffsol_ode_solver_type_is_valid(99), 0);
1407            assert_last_error_contains("invalid ode_solver_type");
1408            clear_last_error();
1409
1410            assert_eq!(diffsol_linear_solver_type_is_valid(99), 0);
1411            assert_last_error_contains("invalid linear_solver_type");
1412            clear_last_error();
1413
1414            assert_eq!(diffsol_scalar_type_is_valid(99), 0);
1415            assert_last_error_contains("invalid scalar_type");
1416        }
1417    }
1418
1419    #[test]
1420    fn c_api_rejects_invalid_ode_arguments() {
1421        clear_last_error();
1422        unsafe {
1423            let mut out_tensor = ptr::null_mut();
1424            let status = diffsol_ode_y0(ptr::null_mut(), ptr::null(), 0, 1, &mut out_tensor);
1425            assert_eq!(status, DIFFSOL_BAD_ARG);
1426            assert!(out_tensor.is_null());
1427            assert_last_error_contains("invalid arguments to diffsol_ode_y0");
1428        }
1429    }
1430
1431    #[cfg(feature = "diffsl-external-f64")]
1432    #[test]
1433    fn c_api_rejects_invalid_external_ode_arguments() {
1434        clear_last_error();
1435        unsafe {
1436            let ode = make_ode_ptr(
1437                99,
1438                linear_solver_to_i32(LinearSolverType::Default),
1439                ode_solver_to_i32(OdeSolverType::Bdf),
1440            );
1441            assert!(ode.is_null());
1442            assert_last_error_contains("invalid matrix_type");
1443        }
1444    }
1445
1446    #[cfg(feature = "diffsl-external-f64")]
1447    #[test]
1448    fn c_api_full_lifecycle_matches_external_logistic_model() {
1449        clear_last_error();
1450        unsafe {
1451            let ode = make_ode_ptr(
1452                matrix_type_to_i32(MatrixType::NalgebraDense),
1453                linear_solver_to_i32(LinearSolverType::Default),
1454                ode_solver_to_i32(OdeSolverType::Bdf),
1455            );
1456            assert!(!ode.is_null());
1457
1458            assert_eq!(
1459                diffsol_ode_get_matrix_type(ode),
1460                matrix_type_to_i32(MatrixType::NalgebraDense)
1461            );
1462            assert_eq!(
1463                diffsol_ode_get_ode_solver(ode),
1464                ode_solver_to_i32(OdeSolverType::Bdf)
1465            );
1466            assert_eq!(
1467                diffsol_ode_get_linear_solver(ode),
1468                linear_solver_to_i32(LinearSolverType::Default)
1469            );
1470
1471            assert_eq!(
1472                diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
1473                DIFFSOL_OK
1474            );
1475            assert_eq!(
1476                diffsol_ode_get_ode_solver(ode),
1477                ode_solver_to_i32(OdeSolverType::Tsit45)
1478            );
1479            assert_eq!(
1480                diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Bdf)),
1481                DIFFSOL_OK
1482            );
1483
1484            let mut ic_options: *mut InitialConditionSolverOptions = ptr::null_mut();
1485            assert_eq!(diffsol_ode_get_ic_options(ode, &mut ic_options), DIFFSOL_OK);
1486            assert!(!ic_options.is_null());
1487            let mut use_linesearch = 0;
1488            let mut max_linesearch_iterations = 0usize;
1489            assert_eq!(
1490                diffsol_ic_options_get_use_linesearch(ic_options, &mut use_linesearch),
1491                DIFFSOL_OK
1492            );
1493            assert_eq!(
1494                diffsol_ic_options_set_use_linesearch(ic_options, 1),
1495                DIFFSOL_OK
1496            );
1497            assert_eq!(
1498                diffsol_ic_options_get_use_linesearch(ic_options, &mut use_linesearch),
1499                DIFFSOL_OK
1500            );
1501            assert_eq!(use_linesearch, 1);
1502            assert_eq!(
1503                diffsol_ic_options_set_max_linesearch_iterations(ic_options, 23),
1504                DIFFSOL_OK
1505            );
1506            assert_eq!(
1507                diffsol_ic_options_get_max_linesearch_iterations(
1508                    ic_options,
1509                    &mut max_linesearch_iterations
1510                ),
1511                DIFFSOL_OK
1512            );
1513            assert_eq!(max_linesearch_iterations, 23);
1514            diffsol_ic_options_free(ic_options);
1515
1516            let mut ode_options: *mut OdeSolverOptions = ptr::null_mut();
1517            assert_eq!(diffsol_ode_get_options(ode, &mut ode_options), DIFFSOL_OK);
1518            assert!(!ode_options.is_null());
1519            let mut max_nonlinear_iterations = 0usize;
1520            let mut min_timestep = 0.0;
1521            assert_eq!(
1522                diffsol_ode_options_set_max_nonlinear_solver_iterations(ode_options, 17),
1523                DIFFSOL_OK
1524            );
1525            assert_eq!(
1526                diffsol_ode_options_get_max_nonlinear_solver_iterations(
1527                    ode_options,
1528                    &mut max_nonlinear_iterations
1529                ),
1530                DIFFSOL_OK
1531            );
1532            assert_eq!(max_nonlinear_iterations, 17);
1533            let mut max_nonlinear_failures = 0usize;
1534            assert_eq!(
1535                diffsol_ode_options_set_max_nonlinear_solver_failures(ode_options, 7),
1536                DIFFSOL_OK
1537            );
1538            assert_eq!(
1539                diffsol_ode_options_get_max_nonlinear_solver_failures(
1540                    ode_options,
1541                    &mut max_nonlinear_failures
1542                ),
1543                DIFFSOL_OK
1544            );
1545            assert_eq!(max_nonlinear_failures, 7);
1546            assert_eq!(
1547                diffsol_ode_options_set_min_timestep(ode_options, 1e-4),
1548                DIFFSOL_OK
1549            );
1550            assert_eq!(
1551                diffsol_ode_options_get_min_timestep(ode_options, &mut min_timestep),
1552                DIFFSOL_OK
1553            );
1554            assert_close(min_timestep, 1e-4, ASSERT_TOL, "min_timestep roundtrip");
1555            diffsol_ode_options_free(ode_options);
1556
1557            let params = [2.0f64];
1558            let y = [0.25f64];
1559            let v = [3.0f64];
1560
1561            let mut y0_ptr = ptr::null_mut();
1562            assert_eq!(
1563                diffsol_ode_y0(ode, params.as_ptr(), params.len(), 1, &mut y0_ptr),
1564                DIFFSOL_OK
1565            );
1566            assert_eq!(ffi_read_tensor_vector(y0_ptr), vec![LOGISTIC_X0]);
1567
1568            let mut rhs_ptr = ptr::null_mut();
1569            assert_eq!(
1570                diffsol_ode_rhs(
1571                    ode,
1572                    params.as_ptr(),
1573                    params.len(),
1574                    1,
1575                    0.0,
1576                    y.as_ptr(),
1577                    y.len(),
1578                    1,
1579                    &mut rhs_ptr,
1580                ),
1581                DIFFSOL_OK
1582            );
1583            assert_close(
1584                ffi_read_tensor_vector(rhs_ptr)[0],
1585                0.375,
1586                ASSERT_TOL,
1587                "ffi rhs",
1588            );
1589
1590            let mut rhs_jac_mul_ptr = ptr::null_mut();
1591            assert_eq!(
1592                diffsol_ode_rhs_jac_mul(
1593                    ode,
1594                    params.as_ptr(),
1595                    params.len(),
1596                    1,
1597                    0.0,
1598                    y.as_ptr(),
1599                    y.len(),
1600                    1,
1601                    v.as_ptr(),
1602                    v.len(),
1603                    1,
1604                    &mut rhs_jac_mul_ptr,
1605                ),
1606                DIFFSOL_OK
1607            );
1608            assert_close(
1609                ffi_read_tensor_vector(rhs_jac_mul_ptr)[0],
1610                3.0,
1611                ASSERT_TOL,
1612                "ffi rhs_jac_mul",
1613            );
1614
1615            let mut solve_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1616            assert_eq!(
1617                diffsol_ode_solve(
1618                    ode,
1619                    params.as_ptr(),
1620                    params.len(),
1621                    1,
1622                    1e-9,
1623                    &mut solve_solution_ptr
1624                ),
1625                DIFFSOL_OK
1626            );
1627            assert!(!solve_solution_ptr.is_null());
1628
1629            let mut solve_ys_ptr = ptr::null_mut();
1630            let mut solve_ts_ptr = ptr::null_mut();
1631            assert_eq!(
1632                diffsol_solution_wrapper_get_ys(solve_solution_ptr, &mut solve_ys_ptr),
1633                DIFFSOL_OK
1634            );
1635            assert_eq!(
1636                diffsol_solution_wrapper_get_ts(solve_solution_ptr, &mut solve_ts_ptr),
1637                DIFFSOL_OK
1638            );
1639            let (solve_rows, solve_cols, solve_ys) = ffi_read_tensor_matrix(solve_ys_ptr);
1640            let solve_ts = ffi_read_tensor_1d(solve_ts_ptr);
1641            assert_eq!(solve_rows, 1);
1642            assert_eq!(solve_cols, solve_ts.len());
1643            assert!(!solve_ts.is_empty());
1644            assert_close(
1645                *solve_ts.last().unwrap(),
1646                1e-9,
1647                ASSERT_TOL,
1648                "ffi solve final time",
1649            );
1650            assert_close(
1651                *solve_ys.last().unwrap(),
1652                logistic_state(LOGISTIC_X0, 2.0, 1e-9),
1653                ASSERT_TOL,
1654                "ffi solve final value",
1655            );
1656            ffi_free_solution(solve_solution_ptr);
1657
1658            let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1659            assert_eq!(
1660                diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
1661                DIFFSOL_OK
1662            );
1663
1664            let t_eval = [0.25f64, 0.5f64, 1.0f64];
1665            assert_eq!(
1666                diffsol_ode_solve_dense(
1667                    ode,
1668                    params.as_ptr(),
1669                    params.len(),
1670                    1,
1671                    t_eval.as_ptr(),
1672                    t_eval.len(),
1673                    &mut solution_ptr,
1674                ),
1675                DIFFSOL_OK
1676            );
1677            let mut ys_ptr = ptr::null_mut();
1678            let mut ts_ptr = ptr::null_mut();
1679            assert_eq!(
1680                diffsol_solution_wrapper_get_ys(solution_ptr, &mut ys_ptr),
1681                DIFFSOL_OK
1682            );
1683            assert_eq!(
1684                diffsol_solution_wrapper_get_ts(solution_ptr, &mut ts_ptr),
1685                DIFFSOL_OK
1686            );
1687            let (rows, cols, ys) = ffi_read_tensor_matrix(ys_ptr);
1688            let ts = ffi_read_tensor_1d(ts_ptr);
1689            assert_eq!(rows, 1);
1690            assert_eq!(cols, ts.len());
1691            let start = find_time_window(&ts, &t_eval, ASSERT_TOL);
1692            for (i, &t) in t_eval.iter().enumerate() {
1693                assert_close(ts[start + i], t, ASSERT_TOL, "ffi solution time");
1694                assert_close(
1695                    ys[start + i],
1696                    logistic_state(0.1, 2.0, t),
1697                    5e-4,
1698                    "ffi solution value",
1699                );
1700            }
1701            assert_eq!(
1702                diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Bdf)),
1703                DIFFSOL_OK
1704            );
1705
1706            let hybrid_t_eval = [0.5f64, 1.0, 1.25, 1.5, 2.0];
1707            let hybrid_ode = make_ode_ptr(
1708                matrix_type_to_i32(MatrixType::NalgebraDense),
1709                linear_solver_to_i32(LinearSolverType::Default),
1710                ode_solver_to_i32(OdeSolverType::Bdf),
1711            );
1712            assert!(!hybrid_ode.is_null());
1713            let mut hybrid_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1714            assert_eq!(
1715                diffsol_ode_solve_dense(
1716                    hybrid_ode,
1717                    params.as_ptr(),
1718                    params.len(),
1719                    1,
1720                    hybrid_t_eval.as_ptr(),
1721                    hybrid_t_eval.len(),
1722                    &mut hybrid_solution_ptr,
1723                ),
1724                DIFFSOL_OK
1725            );
1726            let mut hybrid_ys_ptr = ptr::null_mut();
1727            let mut hybrid_ts_ptr = ptr::null_mut();
1728            assert_eq!(
1729                diffsol_solution_wrapper_get_ys(hybrid_solution_ptr, &mut hybrid_ys_ptr),
1730                DIFFSOL_OK
1731            );
1732            assert_eq!(
1733                diffsol_solution_wrapper_get_ts(hybrid_solution_ptr, &mut hybrid_ts_ptr),
1734                DIFFSOL_OK
1735            );
1736            let (hybrid_rows, hybrid_cols, hybrid_ys) = ffi_read_tensor_matrix(hybrid_ys_ptr);
1737            let hybrid_ts = ffi_read_tensor_1d(hybrid_ts_ptr);
1738            assert_eq!(hybrid_rows, 1);
1739            assert_eq!(hybrid_cols, hybrid_t_eval.len());
1740            assert_eq!(hybrid_ts, hybrid_t_eval);
1741            assert_close(
1742                hybrid_ys[0],
1743                logistic_state(LOGISTIC_X0, 2.0, hybrid_t_eval[0]),
1744                5e-4,
1745                "ffi hybrid dense pre-root value",
1746            );
1747            assert_close(
1748                hybrid_ys[1],
1749                logistic_state(LOGISTIC_X0, 2.0, hybrid_t_eval[1]),
1750                5e-4,
1751                "ffi hybrid dense near-root value",
1752            );
1753            for (i, value) in hybrid_ys.iter().enumerate().skip(2) {
1754                assert_close(
1755                    *value,
1756                    1.0,
1757                    5e-4,
1758                    &format!("ffi hybrid dense post-root value[{i}]"),
1759                );
1760            }
1761            ffi_free_solution(hybrid_solution_ptr);
1762            diffsol_ode_free(hybrid_ode);
1763
1764            let analysis_ode = make_ode_ptr(
1765                matrix_type_to_i32(MatrixType::NalgebraDense),
1766                linear_solver_to_i32(LinearSolverType::Default),
1767                ode_solver_to_i32(OdeSolverType::Bdf),
1768            );
1769            assert!(!analysis_ode.is_null());
1770
1771            assert_eq!(diffsol_ode_set_sens_rtol(analysis_ode, 1, 1e-6), DIFFSOL_OK);
1772            assert_eq!(diffsol_ode_set_sens_atol(analysis_ode, 1, 1e-6), DIFFSOL_OK);
1773
1774            let mut sens_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1775            assert_eq!(
1776                diffsol_ode_solve_fwd_sens(
1777                    analysis_ode,
1778                    params.as_ptr(),
1779                    params.len(),
1780                    1,
1781                    t_eval.as_ptr(),
1782                    t_eval.len(),
1783                    &mut sens_solution_ptr,
1784                ),
1785                DIFFSOL_OK
1786            );
1787            let mut sens_list = ptr::null_mut();
1788            let mut sens_len = 0usize;
1789            assert_eq!(
1790                diffsol_solution_wrapper_get_sens(sens_solution_ptr, &mut sens_list, &mut sens_len),
1791                DIFFSOL_OK
1792            );
1793            let sens_values = ffi_read_tensor_list_matrices(sens_list, sens_len);
1794            assert_eq!(sens_values.len(), 1);
1795            assert_eq!(sens_values[0].0, 1);
1796            assert_eq!(sens_values[0].1, t_eval.len());
1797            for (i, (&value, &t)) in sens_values[0].2.iter().zip(t_eval.iter()).enumerate() {
1798                assert_close(
1799                    value,
1800                    logistic_state_dr(LOGISTIC_X0, 2.0, t),
1801                    ASSERT_TOL,
1802                    &format!("ffi sensitivity[{i}]"),
1803                );
1804            }
1805
1806            ffi_free_solution(sens_solution_ptr);
1807            diffsol_ode_free(analysis_ode);
1808            ffi_free_solution(solution_ptr);
1809            diffsol_ode_free(ode);
1810        }
1811    }
1812}
1813
1814#[cfg(all(test, feature = "diffsl-external-dynamic"))]
1815mod external_dynamic_tests {
1816    use std::ffi::CString;
1817    use std::os::raw::c_char;
1818    use std::ptr;
1819
1820    use crate::linear_solver_type::LinearSolverType;
1821    use crate::linear_solver_type_c::linear_solver_to_i32;
1822    use crate::matrix_type::MatrixType;
1823    use crate::matrix_type_c::matrix_type_to_i32;
1824    use crate::ode_solver_type::OdeSolverType;
1825    use crate::ode_solver_type_c::ode_solver_to_i32;
1826    use crate::test_support::{
1827        assert_last_error_contains, assert_last_error_set, clear_last_error,
1828        external_dynamic_fixture_path, ffi_read_tensor_vector, mass_state_deps, rhs_input_deps,
1829        rhs_state_deps, LOGISTIC_X0,
1830    };
1831
1832    use super::*;
1833    use crate::ode_diffsl_c::*;
1834
1835    fn to_dep_pairs(values: &[(usize, usize)]) -> Vec<DiffsolDepPair> {
1836        values
1837            .iter()
1838            .map(|&(row, col)| DiffsolDepPair { row, col })
1839            .collect()
1840    }
1841
1842    unsafe fn make_ode_ptr(
1843        matrix_type: i32,
1844        linear_solver: i32,
1845        ode_solver: i32,
1846    ) -> *mut OdeWrapper {
1847        let path = CString::new(
1848            external_dynamic_fixture_path()
1849                .to_string_lossy()
1850                .into_owned(),
1851        )
1852        .unwrap();
1853        let rhs_state_deps = to_dep_pairs(&rhs_state_deps());
1854        let rhs_input_deps = to_dep_pairs(&rhs_input_deps());
1855        let mass_state_deps = to_dep_pairs(&mass_state_deps());
1856        unsafe {
1857            diffsol_ode_new_external_dynamic(
1858                path.as_ptr(),
1859                matrix_type,
1860                linear_solver,
1861                ode_solver,
1862                rhs_state_deps.as_ptr(),
1863                rhs_state_deps.len(),
1864                rhs_input_deps.as_ptr(),
1865                rhs_input_deps.len(),
1866                mass_state_deps.as_ptr(),
1867                mass_state_deps.len(),
1868            )
1869        }
1870    }
1871
1872    #[test]
1873    fn c_api_constructs_dynamic_external_ode() {
1874        clear_last_error();
1875        unsafe {
1876            let ode = make_ode_ptr(
1877                matrix_type_to_i32(MatrixType::NalgebraDense),
1878                linear_solver_to_i32(LinearSolverType::Default),
1879                ode_solver_to_i32(OdeSolverType::Bdf),
1880            );
1881            assert!(!ode.is_null());
1882            assert_eq!(
1883                diffsol_ode_get_matrix_type(ode),
1884                matrix_type_to_i32(MatrixType::NalgebraDense)
1885            );
1886            let params = [2.0_f64];
1887            let mut y0_ptr = ptr::null_mut();
1888            assert_eq!(
1889                diffsol_ode_y0(ode, params.as_ptr(), params.len(), 1, &mut y0_ptr),
1890                DIFFSOL_OK
1891            );
1892            assert_eq!(ffi_read_tensor_vector(y0_ptr), vec![LOGISTIC_X0]);
1893            diffsol_ode_free(ode);
1894        }
1895    }
1896
1897    #[test]
1898    fn c_api_rejects_null_dynamic_library_path() {
1899        clear_last_error();
1900        unsafe {
1901            let ode = diffsol_ode_new_external_dynamic(
1902                ptr::null(),
1903                matrix_type_to_i32(MatrixType::NalgebraDense),
1904                linear_solver_to_i32(LinearSolverType::Default),
1905                ode_solver_to_i32(OdeSolverType::Bdf),
1906                ptr::null(),
1907                0,
1908                ptr::null(),
1909                0,
1910                ptr::null(),
1911                0,
1912            );
1913            assert!(ode.is_null());
1914            assert_last_error_contains("path is null");
1915        }
1916    }
1917
1918    #[test]
1919    fn c_api_rejects_non_utf8_dynamic_library_path() {
1920        clear_last_error();
1921        unsafe {
1922            let invalid_utf8 = [0xff_u8, 0];
1923            let ode = diffsol_ode_new_external_dynamic(
1924                invalid_utf8.as_ptr() as *const c_char,
1925                matrix_type_to_i32(MatrixType::NalgebraDense),
1926                linear_solver_to_i32(LinearSolverType::Default),
1927                ode_solver_to_i32(OdeSolverType::Bdf),
1928                ptr::null(),
1929                0,
1930                ptr::null(),
1931                0,
1932                ptr::null(),
1933                0,
1934            );
1935            assert!(ode.is_null());
1936            assert_last_error_contains("path is not valid UTF-8");
1937        }
1938    }
1939
1940    #[test]
1941    fn c_api_reports_missing_dynamic_library_path() {
1942        clear_last_error();
1943        unsafe {
1944            let missing_path = external_dynamic_fixture_path().with_file_name("does-not-exist");
1945            let missing_path = CString::new(missing_path.to_string_lossy().into_owned()).unwrap();
1946            let ode = diffsol_ode_new_external_dynamic(
1947                missing_path.as_ptr(),
1948                matrix_type_to_i32(MatrixType::NalgebraDense),
1949                linear_solver_to_i32(LinearSolverType::Default),
1950                ode_solver_to_i32(OdeSolverType::Bdf),
1951                ptr::null(),
1952                0,
1953                ptr::null(),
1954                0,
1955                ptr::null(),
1956                0,
1957            );
1958            assert!(ode.is_null());
1959            assert_last_error_set();
1960        }
1961    }
1962}
1963
1964#[cfg(all(test, any(feature = "diffsl-cranelift", feature = "diffsl-llvm")))]
1965mod jit_tests {
1966    use std::ffi::{CStr, CString};
1967    use std::ptr;
1968
1969    use crate::error_c::{diffsol_error_code, diffsol_last_error_message};
1970    use crate::initial_condition_options_c::diffsol_ic_options_free;
1971    use crate::jit::JitBackendType;
1972    use crate::jit_c::jit_backend_to_i32;
1973    use crate::linear_solver_type::LinearSolverType;
1974    use crate::linear_solver_type_c::linear_solver_to_i32;
1975    use crate::matrix_type::MatrixType;
1976    use crate::matrix_type_c::matrix_type_to_i32;
1977    use crate::ode_options_c::diffsol_ode_options_free;
1978    use crate::ode_solver_type::OdeSolverType;
1979    use crate::ode_solver_type_c::ode_solver_to_i32;
1980    #[cfg(feature = "diffsl-llvm")]
1981    use crate::solution_wrapper_c::diffsol_solution_wrapper_get_sens;
1982    use crate::solution_wrapper_c::{
1983        diffsol_solution_wrapper_get_ts, diffsol_solution_wrapper_get_ys,
1984    };
1985    #[cfg(feature = "diffsl-llvm")]
1986    use crate::test_support::ffi_read_tensor_list_matrices;
1987    use crate::test_support::{
1988        assert_close, available_jit_backends, clear_last_error, ffi_free_solution,
1989        ffi_read_tensor_1d, ffi_read_tensor_matrix, ffi_read_tensor_vector, find_time_window,
1990        hybrid_logistic_diffsl_code, hybrid_logistic_state, logistic_diffsl_code_cstring,
1991        logistic_state, ASSERT_TOL, LOGISTIC_X0,
1992    };
1993    #[cfg(feature = "diffsl-llvm")]
1994    use crate::test_support::{hybrid_logistic_state_dr, logistic_state_dr};
1995
1996    use super::*;
1997    use crate::ode_diffsl_c::*;
1998
1999    unsafe fn make_ode_ptr(
2000        jit_backend: JitBackendType,
2001        matrix_type: i32,
2002        linear_solver: i32,
2003        ode_solver: i32,
2004    ) -> *mut OdeWrapper {
2005        let code = logistic_diffsl_code_cstring();
2006        unsafe {
2007            make_ode_ptr_with_code(
2008                jit_backend,
2009                code.as_ptr(),
2010                matrix_type,
2011                linear_solver,
2012                ode_solver,
2013            )
2014        }
2015    }
2016
2017    unsafe fn make_ode_ptr_with_code(
2018        jit_backend: JitBackendType,
2019        code: *const std::os::raw::c_char,
2020        matrix_type: i32,
2021        linear_solver: i32,
2022        ode_solver: i32,
2023    ) -> *mut OdeWrapper {
2024        unsafe {
2025            diffsol_ode_new_jit(
2026                code,
2027                jit_backend_to_i32(jit_backend),
2028                matrix_type,
2029                linear_solver,
2030                ode_solver,
2031            )
2032        }
2033    }
2034
2035    unsafe fn last_error_message() -> String {
2036        let ptr = unsafe { diffsol_last_error_message() };
2037        assert_eq!(unsafe { diffsol_error_code() }, 1);
2038        assert!(!ptr.is_null());
2039        unsafe { CStr::from_ptr(ptr) }.to_str().unwrap().to_owned()
2040    }
2041
2042    unsafe fn assert_optional_f64(
2043        getter: unsafe extern "C" fn(*const OdeWrapper, *mut i32, *mut f64) -> i32,
2044        ode: *const OdeWrapper,
2045        expected: Option<f64>,
2046        name: &str,
2047    ) {
2048        let mut is_some = -1;
2049        let mut value = -1.0;
2050        assert_eq!(unsafe { getter(ode, &mut is_some, &mut value) }, DIFFSOL_OK);
2051        match expected {
2052            Some(expected) => {
2053                assert_eq!(is_some, 1, "{name} is_some");
2054                assert_close(value, expected, ASSERT_TOL, name);
2055            }
2056            None => {
2057                assert_eq!(is_some, 0, "{name} is_some");
2058                assert_close(value, 0.0, ASSERT_TOL, name);
2059            }
2060        }
2061    }
2062
2063    #[test]
2064    fn c_api_full_lifecycle_matches_jit_logistic_model() {
2065        clear_last_error();
2066        for jit_backend in available_jit_backends() {
2067            unsafe {
2068                let ode = make_ode_ptr(
2069                    jit_backend,
2070                    matrix_type_to_i32(MatrixType::NalgebraDense),
2071                    linear_solver_to_i32(LinearSolverType::Default),
2072                    ode_solver_to_i32(OdeSolverType::Bdf),
2073                );
2074                assert!(!ode.is_null());
2075
2076                assert_eq!(
2077                    diffsol_ode_get_matrix_type(ode),
2078                    matrix_type_to_i32(MatrixType::NalgebraDense)
2079                );
2080                assert_eq!(
2081                    diffsol_ode_get_ode_solver(ode),
2082                    ode_solver_to_i32(OdeSolverType::Bdf)
2083                );
2084                assert_eq!(
2085                    diffsol_ode_get_linear_solver(ode),
2086                    linear_solver_to_i32(LinearSolverType::Default)
2087                );
2088
2089                assert_eq!(diffsol_ode_set_rtol(ode, 1e-8), DIFFSOL_OK);
2090                assert_eq!(diffsol_ode_set_atol(ode, 1e-8), DIFFSOL_OK);
2091                let mut rtol = 0.0;
2092                let mut atol = 0.0;
2093                assert_eq!(diffsol_ode_get_rtol(ode, &mut rtol), DIFFSOL_OK);
2094                assert_eq!(diffsol_ode_get_atol(ode, &mut atol), DIFFSOL_OK);
2095                assert_close(rtol, 1e-8, ASSERT_TOL, "jit rtol roundtrip");
2096                assert_close(atol, 1e-8, ASSERT_TOL, "jit atol roundtrip");
2097
2098                assert_eq!(diffsol_ode_set_t0(ode, 0.125), DIFFSOL_OK);
2099                assert_eq!(diffsol_ode_set_h0(ode, 0.25), DIFFSOL_OK);
2100                let mut t0 = 0.0;
2101                let mut h0 = 0.0;
2102                assert_eq!(diffsol_ode_get_t0(ode, &mut t0), DIFFSOL_OK);
2103                assert_eq!(diffsol_ode_get_h0(ode, &mut h0), DIFFSOL_OK);
2104                assert_close(t0, 0.125, ASSERT_TOL, "jit t0 roundtrip");
2105                assert_close(h0, 0.25, ASSERT_TOL, "jit h0 roundtrip");
2106
2107                assert_eq!(diffsol_ode_set_integrate_out(ode, 1), DIFFSOL_OK);
2108                let mut integrate_out = 0;
2109                assert_eq!(
2110                    diffsol_ode_get_integrate_out(ode, &mut integrate_out),
2111                    DIFFSOL_OK
2112                );
2113                assert_eq!(integrate_out, 1);
2114                assert_eq!(diffsol_ode_set_integrate_out(ode, 0), DIFFSOL_OK);
2115                assert_eq!(
2116                    diffsol_ode_get_integrate_out(ode, &mut integrate_out),
2117                    DIFFSOL_OK
2118                );
2119                assert_eq!(integrate_out, 0);
2120
2121                assert_eq!(diffsol_ode_set_sens_rtol(ode, 1, 1e-6), DIFFSOL_OK);
2122                assert_eq!(diffsol_ode_set_sens_atol(ode, 1, 1e-6), DIFFSOL_OK);
2123                assert_eq!(diffsol_ode_set_out_rtol(ode, 1, 2e-3), DIFFSOL_OK);
2124                assert_eq!(diffsol_ode_set_out_atol(ode, 1, 2e-4), DIFFSOL_OK);
2125                assert_eq!(diffsol_ode_set_param_rtol(ode, 1, 3e-3), DIFFSOL_OK);
2126                assert_eq!(diffsol_ode_set_param_atol(ode, 1, 3e-4), DIFFSOL_OK);
2127                assert_optional_f64(diffsol_ode_get_sens_rtol, ode, Some(1e-6), "jit sens rtol");
2128                assert_optional_f64(diffsol_ode_get_sens_atol, ode, Some(1e-6), "jit sens atol");
2129                assert_optional_f64(diffsol_ode_get_out_rtol, ode, Some(2e-3), "jit out rtol");
2130                assert_optional_f64(diffsol_ode_get_out_atol, ode, Some(2e-4), "jit out atol");
2131                assert_optional_f64(
2132                    diffsol_ode_get_param_rtol,
2133                    ode,
2134                    Some(3e-3),
2135                    "jit param rtol",
2136                );
2137                assert_optional_f64(
2138                    diffsol_ode_get_param_atol,
2139                    ode,
2140                    Some(3e-4),
2141                    "jit param atol",
2142                );
2143
2144                assert_eq!(diffsol_ode_set_sens_rtol(ode, 0, 1e-3), DIFFSOL_OK);
2145                assert_eq!(diffsol_ode_set_sens_atol(ode, 0, 1e-4), DIFFSOL_OK);
2146                assert_eq!(diffsol_ode_set_out_rtol(ode, 0, 2e-3), DIFFSOL_OK);
2147                assert_eq!(diffsol_ode_set_out_atol(ode, 0, 2e-4), DIFFSOL_OK);
2148                assert_eq!(diffsol_ode_set_param_rtol(ode, 0, 3e-3), DIFFSOL_OK);
2149                assert_eq!(diffsol_ode_set_param_atol(ode, 0, 3e-4), DIFFSOL_OK);
2150                assert_optional_f64(diffsol_ode_get_sens_rtol, ode, None, "jit sens rtol none");
2151                assert_optional_f64(diffsol_ode_get_sens_atol, ode, None, "jit sens atol none");
2152                assert_optional_f64(diffsol_ode_get_out_rtol, ode, None, "jit out rtol none");
2153                assert_optional_f64(diffsol_ode_get_out_atol, ode, None, "jit out atol none");
2154                assert_optional_f64(diffsol_ode_get_param_rtol, ode, None, "jit param rtol none");
2155                assert_optional_f64(diffsol_ode_get_param_atol, ode, None, "jit param atol none");
2156
2157                assert_eq!(diffsol_ode_set_t0(ode, 0.0), DIFFSOL_OK);
2158                assert_eq!(diffsol_ode_set_h0(ode, 1.0), DIFFSOL_OK);
2159
2160                let params = [2.0f64];
2161                let y = [0.25f64];
2162                let v = [3.0f64];
2163
2164                let mut y0_ptr = ptr::null_mut();
2165                assert_eq!(
2166                    diffsol_ode_y0(ode, params.as_ptr(), params.len(), 1, &mut y0_ptr),
2167                    DIFFSOL_OK
2168                );
2169                assert_eq!(ffi_read_tensor_vector(y0_ptr), vec![LOGISTIC_X0]);
2170
2171                let mut rhs_ptr = ptr::null_mut();
2172                assert_eq!(
2173                    diffsol_ode_rhs(
2174                        ode,
2175                        params.as_ptr(),
2176                        params.len(),
2177                        1,
2178                        0.0,
2179                        y.as_ptr(),
2180                        y.len(),
2181                        1,
2182                        &mut rhs_ptr,
2183                    ),
2184                    DIFFSOL_OK
2185                );
2186                assert_close(
2187                    ffi_read_tensor_vector(rhs_ptr)[0],
2188                    0.375,
2189                    ASSERT_TOL,
2190                    "jit ffi rhs",
2191                );
2192
2193                let mut rhs_jac_mul_ptr = ptr::null_mut();
2194                assert_eq!(
2195                    diffsol_ode_rhs_jac_mul(
2196                        ode,
2197                        params.as_ptr(),
2198                        params.len(),
2199                        1,
2200                        0.0,
2201                        y.as_ptr(),
2202                        y.len(),
2203                        1,
2204                        v.as_ptr(),
2205                        v.len(),
2206                        1,
2207                        &mut rhs_jac_mul_ptr,
2208                    ),
2209                    DIFFSOL_OK
2210                );
2211                assert_close(
2212                    ffi_read_tensor_vector(rhs_jac_mul_ptr)[0],
2213                    3.0,
2214                    ASSERT_TOL,
2215                    "jit ffi rhs_jac_mul",
2216                );
2217
2218                let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2219                let t_eval = [0.25f64, 0.5f64, 1.0f64];
2220                assert_eq!(
2221                    diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
2222                    DIFFSOL_OK
2223                );
2224                assert_eq!(
2225                    diffsol_ode_solve_dense(
2226                        ode,
2227                        params.as_ptr(),
2228                        params.len(),
2229                        1,
2230                        t_eval.as_ptr(),
2231                        t_eval.len(),
2232                        &mut solution_ptr,
2233                    ),
2234                    DIFFSOL_OK
2235                );
2236                let mut ys_ptr = ptr::null_mut();
2237                let mut ts_ptr = ptr::null_mut();
2238                assert_eq!(
2239                    diffsol_solution_wrapper_get_ys(solution_ptr, &mut ys_ptr),
2240                    DIFFSOL_OK
2241                );
2242                assert_eq!(
2243                    diffsol_solution_wrapper_get_ts(solution_ptr, &mut ts_ptr),
2244                    DIFFSOL_OK
2245                );
2246                let (rows, cols, ys) = ffi_read_tensor_matrix(ys_ptr);
2247                let ts = ffi_read_tensor_1d(ts_ptr);
2248                assert_eq!(rows, 1);
2249                assert_eq!(cols, ts.len());
2250                let start = find_time_window(&ts, &t_eval, ASSERT_TOL);
2251                for (i, &t) in t_eval.iter().enumerate() {
2252                    assert_close(ts[start + i], t, ASSERT_TOL, "jit ffi solution time");
2253                    assert_close(
2254                        ys[start + i],
2255                        logistic_state(LOGISTIC_X0, 2.0, t),
2256                        5e-4,
2257                        "jit ffi solution value",
2258                    );
2259                }
2260                assert_eq!(
2261                    diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Bdf)),
2262                    DIFFSOL_OK
2263                );
2264
2265                #[cfg(feature = "diffsl-llvm")]
2266                {
2267                    let analysis_code = logistic_diffsl_code_cstring();
2268                    let analysis_ode = make_ode_ptr_with_code(
2269                        JitBackendType::Llvm,
2270                        analysis_code.as_ptr(),
2271                        matrix_type_to_i32(MatrixType::NalgebraDense),
2272                        linear_solver_to_i32(LinearSolverType::Default),
2273                        ode_solver_to_i32(OdeSolverType::Bdf),
2274                    );
2275                    assert!(!analysis_ode.is_null());
2276
2277                    assert_eq!(diffsol_ode_set_sens_rtol(analysis_ode, 1, 1e-6), DIFFSOL_OK);
2278                    assert_eq!(diffsol_ode_set_sens_atol(analysis_ode, 1, 1e-6), DIFFSOL_OK);
2279
2280                    let mut sens_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2281                    assert_eq!(
2282                        diffsol_ode_solve_fwd_sens(
2283                            analysis_ode,
2284                            params.as_ptr(),
2285                            params.len(),
2286                            1,
2287                            t_eval.as_ptr(),
2288                            t_eval.len(),
2289                            &mut sens_solution_ptr,
2290                        ),
2291                        DIFFSOL_OK
2292                    );
2293                    let mut sens_list = ptr::null_mut();
2294                    let mut sens_len = 0usize;
2295                    assert_eq!(
2296                        diffsol_solution_wrapper_get_sens(
2297                            sens_solution_ptr,
2298                            &mut sens_list,
2299                            &mut sens_len
2300                        ),
2301                        DIFFSOL_OK
2302                    );
2303                    let sens_values = ffi_read_tensor_list_matrices(sens_list, sens_len);
2304                    assert_eq!(sens_values.len(), 1);
2305                    assert_eq!(sens_values[0].0, 1);
2306                    assert_eq!(sens_values[0].1, t_eval.len());
2307                    for (i, (&value, &t)) in sens_values[0].2.iter().zip(t_eval.iter()).enumerate()
2308                    {
2309                        assert_close(
2310                            value,
2311                            logistic_state_dr(LOGISTIC_X0, 2.0, t),
2312                            ASSERT_TOL,
2313                            &format!("jit ffi sensitivity[{i}]"),
2314                        );
2315                    }
2316
2317                    ffi_free_solution(sens_solution_ptr);
2318                    diffsol_ode_free(analysis_ode);
2319                }
2320                ffi_free_solution(solution_ptr);
2321                diffsol_ode_free(ode);
2322            }
2323        }
2324    }
2325
2326    #[test]
2327    fn c_api_rejects_invalid_jit_arguments() {
2328        unsafe {
2329            clear_last_error();
2330            assert!(diffsol_ode_new_jit(
2331                ptr::null(),
2332                jit_backend_to_i32(available_jit_backends()[0]),
2333                matrix_type_to_i32(MatrixType::NalgebraDense),
2334                linear_solver_to_i32(LinearSolverType::Default),
2335                ode_solver_to_i32(OdeSolverType::Bdf),
2336            )
2337            .is_null());
2338            assert!(last_error_message().contains("code is null"));
2339
2340            clear_last_error();
2341            let invalid_utf8 = CString::from_vec_with_nul(vec![0xff, 0]).unwrap();
2342            assert!(diffsol_ode_new_jit(
2343                invalid_utf8.as_ptr(),
2344                jit_backend_to_i32(available_jit_backends()[0]),
2345                matrix_type_to_i32(MatrixType::NalgebraDense),
2346                linear_solver_to_i32(LinearSolverType::Default),
2347                ode_solver_to_i32(OdeSolverType::Bdf),
2348            )
2349            .is_null());
2350            assert!(last_error_message().contains("valid UTF-8"));
2351
2352            clear_last_error();
2353            let code = logistic_diffsl_code_cstring();
2354            assert!(diffsol_ode_new_jit(
2355                code.as_ptr(),
2356                99,
2357                matrix_type_to_i32(MatrixType::NalgebraDense),
2358                linear_solver_to_i32(LinearSolverType::Default),
2359                ode_solver_to_i32(OdeSolverType::Bdf),
2360            )
2361            .is_null());
2362            assert!(last_error_message().contains("invalid jit_backend_type"));
2363
2364            clear_last_error();
2365            assert!(diffsol_ode_new_jit(
2366                code.as_ptr(),
2367                jit_backend_to_i32(available_jit_backends()[0]),
2368                99,
2369                linear_solver_to_i32(LinearSolverType::Default),
2370                ode_solver_to_i32(OdeSolverType::Bdf),
2371            )
2372            .is_null());
2373            assert!(last_error_message().contains("invalid matrix_type"));
2374
2375            clear_last_error();
2376            assert!(diffsol_ode_new_jit(
2377                code.as_ptr(),
2378                jit_backend_to_i32(available_jit_backends()[0]),
2379                matrix_type_to_i32(MatrixType::NalgebraDense),
2380                99,
2381                ode_solver_to_i32(OdeSolverType::Bdf),
2382            )
2383            .is_null());
2384            assert!(last_error_message().contains("invalid linear_solver"));
2385
2386            clear_last_error();
2387            assert!(diffsol_ode_new_jit(
2388                code.as_ptr(),
2389                jit_backend_to_i32(available_jit_backends()[0]),
2390                matrix_type_to_i32(MatrixType::NalgebraDense),
2391                linear_solver_to_i32(LinearSolverType::Default),
2392                99,
2393            )
2394            .is_null());
2395            assert!(last_error_message().contains("invalid ode_solver"));
2396
2397            clear_last_error();
2398            let invalid_code = CString::new("not valid diffsl").unwrap();
2399            assert!(diffsol_ode_new_jit(
2400                invalid_code.as_ptr(),
2401                jit_backend_to_i32(available_jit_backends()[0]),
2402                matrix_type_to_i32(MatrixType::NalgebraDense),
2403                linear_solver_to_i32(LinearSolverType::Default),
2404                ode_solver_to_i32(OdeSolverType::Bdf),
2405            )
2406            .is_null());
2407            assert!(diffsol_error_code() != 0);
2408
2409            let mut ic_options = ptr::null_mut();
2410            assert_eq!(
2411                diffsol_ode_get_ic_options(ptr::null_mut(), &mut ic_options),
2412                DIFFSOL_BAD_ARG
2413            );
2414            let mut ode_options = ptr::null_mut();
2415            assert_eq!(
2416                diffsol_ode_get_options(ptr::null_mut(), &mut ode_options),
2417                DIFFSOL_BAD_ARG
2418            );
2419
2420            let mut out_tensor = ptr::null_mut();
2421            assert_eq!(
2422                diffsol_ode_y0(ptr::null_mut(), ptr::null(), 0, 1, &mut out_tensor),
2423                DIFFSOL_BAD_ARG
2424            );
2425            assert_eq!(
2426                diffsol_ode_rhs(
2427                    ptr::null_mut(),
2428                    ptr::null(),
2429                    0,
2430                    1,
2431                    0.0,
2432                    ptr::null(),
2433                    0,
2434                    1,
2435                    &mut out_tensor,
2436                ),
2437                DIFFSOL_BAD_ARG
2438            );
2439            assert_eq!(
2440                diffsol_ode_rhs_jac_mul(
2441                    ptr::null_mut(),
2442                    ptr::null(),
2443                    0,
2444                    1,
2445                    0.0,
2446                    ptr::null(),
2447                    0,
2448                    1,
2449                    ptr::null(),
2450                    0,
2451                    1,
2452                    &mut out_tensor,
2453                ),
2454                DIFFSOL_BAD_ARG
2455            );
2456
2457            clear_last_error();
2458            diffsol_ode_free(ptr::null_mut());
2459            assert!(last_error_message().contains("ode is null"));
2460
2461            clear_last_error();
2462            diffsol_tensor_list_free(ptr::null_mut(), 0);
2463            assert!(last_error_message().contains("tensor list is null"));
2464        }
2465    }
2466
2467    #[test]
2468    fn c_api_jit_wrapper_branches_cover_runtime_success_and_errors() {
2469        for jit_backend in available_jit_backends() {
2470            unsafe {
2471                let ode = make_ode_ptr(
2472                    jit_backend,
2473                    matrix_type_to_i32(MatrixType::NalgebraDense),
2474                    linear_solver_to_i32(LinearSolverType::Default),
2475                    ode_solver_to_i32(OdeSolverType::Bdf),
2476                );
2477                assert!(!ode.is_null());
2478
2479                let mut ic_options = ptr::null_mut();
2480                let mut ode_options = ptr::null_mut();
2481                assert_eq!(diffsol_ode_get_ic_options(ode, &mut ic_options), DIFFSOL_OK);
2482                assert_eq!(diffsol_ode_get_options(ode, &mut ode_options), DIFFSOL_OK);
2483                diffsol_ic_options_free(ic_options);
2484                diffsol_ode_options_free(ode_options);
2485
2486                let mut out_value = 0.0;
2487                assert_eq!(diffsol_ode_get_rtol(ode, &mut out_value), DIFFSOL_OK);
2488                assert_close(out_value, 1e-6, ASSERT_TOL, "jit ffi default rtol");
2489                assert_eq!(diffsol_ode_set_rtol(ode, 1e-4), DIFFSOL_OK);
2490                assert_eq!(diffsol_ode_get_rtol(ode, &mut out_value), DIFFSOL_OK);
2491                assert_close(out_value, 1e-4, ASSERT_TOL, "jit ffi updated rtol");
2492
2493                assert_eq!(diffsol_ode_get_atol(ode, &mut out_value), DIFFSOL_OK);
2494                assert_close(out_value, 1e-6, ASSERT_TOL, "jit ffi default atol");
2495                assert_eq!(diffsol_ode_set_atol(ode, 1e-5), DIFFSOL_OK);
2496                assert_eq!(diffsol_ode_get_atol(ode, &mut out_value), DIFFSOL_OK);
2497                assert_close(out_value, 1e-5, ASSERT_TOL, "jit ffi updated atol");
2498
2499                assert_eq!(
2500                    diffsol_ode_set_linear_solver(ode, linear_solver_to_i32(LinearSolverType::Lu)),
2501                    DIFFSOL_OK
2502                );
2503                assert_eq!(
2504                    diffsol_ode_get_linear_solver(ode),
2505                    linear_solver_to_i32(LinearSolverType::Lu)
2506                );
2507                assert_eq!(
2508                    diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
2509                    DIFFSOL_OK
2510                );
2511                assert_eq!(
2512                    diffsol_ode_get_ode_solver(ode),
2513                    ode_solver_to_i32(OdeSolverType::Tsit45)
2514                );
2515                assert_eq!(
2516                    diffsol_ode_get_matrix_type(ode),
2517                    matrix_type_to_i32(MatrixType::NalgebraDense)
2518                );
2519
2520                let params = [2.0f64];
2521                let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2522                assert_eq!(
2523                    diffsol_ode_solve(
2524                        ode,
2525                        params.as_ptr(),
2526                        params.len(),
2527                        1,
2528                        1.0,
2529                        &mut solution_ptr
2530                    ),
2531                    DIFFSOL_OK
2532                );
2533                ffi_free_solution(solution_ptr);
2534
2535                let t_eval = [0.25f64, 0.5f64, 1.0f64];
2536                let mut dense_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2537                assert_eq!(
2538                    diffsol_ode_solve_dense(
2539                        ode,
2540                        params.as_ptr(),
2541                        params.len(),
2542                        1,
2543                        t_eval.as_ptr(),
2544                        t_eval.len(),
2545                        &mut dense_solution_ptr,
2546                    ),
2547                    DIFFSOL_OK
2548                );
2549                ffi_free_solution(dense_solution_ptr);
2550
2551                let no_params: [f64; 0] = [];
2552                let y = [0.25f64];
2553                let v = [3.0f64];
2554                let mut out_tensor = ptr::null_mut();
2555                assert_eq!(
2556                    diffsol_ode_y0(ode, no_params.as_ptr(), no_params.len(), 1, &mut out_tensor),
2557                    DIFFSOL_ERR
2558                );
2559                assert_eq!(
2560                    diffsol_ode_rhs(
2561                        ode,
2562                        no_params.as_ptr(),
2563                        no_params.len(),
2564                        1,
2565                        0.0,
2566                        y.as_ptr(),
2567                        y.len(),
2568                        1,
2569                        &mut out_tensor,
2570                    ),
2571                    DIFFSOL_ERR
2572                );
2573                assert_eq!(
2574                    diffsol_ode_rhs_jac_mul(
2575                        ode,
2576                        no_params.as_ptr(),
2577                        no_params.len(),
2578                        1,
2579                        0.0,
2580                        y.as_ptr(),
2581                        y.len(),
2582                        1,
2583                        v.as_ptr(),
2584                        v.len(),
2585                        1,
2586                        &mut out_tensor,
2587                    ),
2588                    DIFFSOL_ERR
2589                );
2590
2591                let mut err_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2592                assert_eq!(
2593                    diffsol_ode_solve(
2594                        ode,
2595                        no_params.as_ptr(),
2596                        no_params.len(),
2597                        1,
2598                        1.0,
2599                        &mut err_solution_ptr,
2600                    ),
2601                    DIFFSOL_ERR
2602                );
2603                assert_eq!(
2604                    diffsol_ode_solve(
2605                        ode,
2606                        no_params.as_ptr(),
2607                        no_params.len(),
2608                        1,
2609                        1.0,
2610                        &mut err_solution_ptr,
2611                    ),
2612                    DIFFSOL_ERR
2613                );
2614                assert_eq!(
2615                    diffsol_ode_solve_dense(
2616                        ode,
2617                        no_params.as_ptr(),
2618                        no_params.len(),
2619                        1,
2620                        t_eval.as_ptr(),
2621                        t_eval.len(),
2622                        &mut err_solution_ptr,
2623                    ),
2624                    DIFFSOL_ERR
2625                );
2626                assert_eq!(
2627                    diffsol_ode_solve_dense(
2628                        ode,
2629                        no_params.as_ptr(),
2630                        no_params.len(),
2631                        1,
2632                        t_eval.as_ptr(),
2633                        t_eval.len(),
2634                        &mut err_solution_ptr,
2635                    ),
2636                    DIFFSOL_ERR
2637                );
2638
2639                #[cfg(feature = "diffsl-llvm")]
2640                if matches!(jit_backend, JitBackendType::Llvm) {
2641                    assert_eq!(
2642                        diffsol_ode_solve_fwd_sens(
2643                            ode,
2644                            no_params.as_ptr(),
2645                            no_params.len(),
2646                            1,
2647                            t_eval.as_ptr(),
2648                            t_eval.len(),
2649                            &mut err_solution_ptr,
2650                        ),
2651                        DIFFSOL_ERR
2652                    );
2653                    assert_eq!(
2654                        diffsol_ode_solve_fwd_sens(
2655                            ode,
2656                            no_params.as_ptr(),
2657                            no_params.len(),
2658                            1,
2659                            t_eval.as_ptr(),
2660                            t_eval.len(),
2661                            &mut err_solution_ptr,
2662                        ),
2663                        DIFFSOL_ERR
2664                    );
2665                }
2666
2667                assert_eq!(diffsol_ode_get_matrix_type(ptr::null()), -1);
2668                assert_eq!(diffsol_ode_get_ode_solver(ptr::null()), -1);
2669                assert_eq!(diffsol_ode_get_linear_solver(ptr::null()), -1);
2670                assert_eq!(
2671                    diffsol_ode_set_ode_solver(ptr::null_mut(), 0),
2672                    DIFFSOL_BAD_ARG
2673                );
2674                assert_eq!(
2675                    diffsol_ode_set_linear_solver(ptr::null_mut(), 0),
2676                    DIFFSOL_BAD_ARG
2677                );
2678                assert_eq!(diffsol_ode_set_ode_solver(ode, 99), DIFFSOL_BAD_ARG);
2679                assert_eq!(diffsol_ode_set_linear_solver(ode, 99), DIFFSOL_BAD_ARG);
2680                assert_eq!(
2681                    diffsol_ode_get_rtol(ptr::null(), &mut out_value),
2682                    DIFFSOL_BAD_ARG
2683                );
2684                assert_eq!(diffsol_ode_get_rtol(ode, ptr::null_mut()), DIFFSOL_BAD_ARG);
2685                assert_eq!(diffsol_ode_set_rtol(ptr::null_mut(), 1e-3), DIFFSOL_BAD_ARG);
2686                assert_eq!(
2687                    diffsol_ode_get_atol(ptr::null(), &mut out_value),
2688                    DIFFSOL_BAD_ARG
2689                );
2690                assert_eq!(diffsol_ode_get_atol(ode, ptr::null_mut()), DIFFSOL_BAD_ARG);
2691                assert_eq!(diffsol_ode_set_atol(ptr::null_mut(), 1e-3), DIFFSOL_BAD_ARG);
2692                assert_eq!(
2693                    diffsol_ode_get_t0(ptr::null(), &mut out_value),
2694                    DIFFSOL_BAD_ARG
2695                );
2696                assert_eq!(diffsol_ode_get_t0(ode, ptr::null_mut()), DIFFSOL_BAD_ARG);
2697                assert_eq!(diffsol_ode_set_t0(ptr::null_mut(), 0.0), DIFFSOL_BAD_ARG);
2698                assert_eq!(
2699                    diffsol_ode_get_h0(ptr::null(), &mut out_value),
2700                    DIFFSOL_BAD_ARG
2701                );
2702                assert_eq!(diffsol_ode_get_h0(ode, ptr::null_mut()), DIFFSOL_BAD_ARG);
2703                assert_eq!(diffsol_ode_set_h0(ptr::null_mut(), 1.0), DIFFSOL_BAD_ARG);
2704                let mut out_bool = 0;
2705                assert_eq!(
2706                    diffsol_ode_get_integrate_out(ptr::null(), &mut out_bool),
2707                    DIFFSOL_BAD_ARG
2708                );
2709                assert_eq!(
2710                    diffsol_ode_get_integrate_out(ode, ptr::null_mut()),
2711                    DIFFSOL_BAD_ARG
2712                );
2713                assert_eq!(
2714                    diffsol_ode_set_integrate_out(ptr::null_mut(), 1),
2715                    DIFFSOL_BAD_ARG
2716                );
2717                let mut out_is_some = 0;
2718                assert_eq!(
2719                    diffsol_ode_get_sens_rtol(ptr::null(), &mut out_is_some, &mut out_value),
2720                    DIFFSOL_BAD_ARG
2721                );
2722                assert_eq!(
2723                    diffsol_ode_get_sens_rtol(ode, ptr::null_mut(), &mut out_value),
2724                    DIFFSOL_BAD_ARG
2725                );
2726                assert_eq!(
2727                    diffsol_ode_get_sens_rtol(ode, &mut out_is_some, ptr::null_mut()),
2728                    DIFFSOL_BAD_ARG
2729                );
2730                assert_eq!(
2731                    diffsol_ode_set_sens_rtol(ptr::null_mut(), 1, 1e-3),
2732                    DIFFSOL_BAD_ARG
2733                );
2734                assert_eq!(
2735                    diffsol_ode_get_sens_atol(ptr::null(), &mut out_is_some, &mut out_value),
2736                    DIFFSOL_BAD_ARG
2737                );
2738                assert_eq!(
2739                    diffsol_ode_set_sens_atol(ptr::null_mut(), 1, 1e-3),
2740                    DIFFSOL_BAD_ARG
2741                );
2742                assert_eq!(
2743                    diffsol_ode_get_out_rtol(ptr::null(), &mut out_is_some, &mut out_value),
2744                    DIFFSOL_BAD_ARG
2745                );
2746                assert_eq!(
2747                    diffsol_ode_set_out_rtol(ptr::null_mut(), 1, 1e-3),
2748                    DIFFSOL_BAD_ARG
2749                );
2750                assert_eq!(
2751                    diffsol_ode_get_out_atol(ptr::null(), &mut out_is_some, &mut out_value),
2752                    DIFFSOL_BAD_ARG
2753                );
2754                assert_eq!(
2755                    diffsol_ode_set_out_atol(ptr::null_mut(), 1, 1e-3),
2756                    DIFFSOL_BAD_ARG
2757                );
2758                assert_eq!(
2759                    diffsol_ode_get_param_rtol(ptr::null(), &mut out_is_some, &mut out_value),
2760                    DIFFSOL_BAD_ARG
2761                );
2762                assert_eq!(
2763                    diffsol_ode_set_param_rtol(ptr::null_mut(), 1, 1e-3),
2764                    DIFFSOL_BAD_ARG
2765                );
2766                assert_eq!(
2767                    diffsol_ode_get_param_atol(ptr::null(), &mut out_is_some, &mut out_value),
2768                    DIFFSOL_BAD_ARG
2769                );
2770                assert_eq!(
2771                    diffsol_ode_set_param_atol(ptr::null_mut(), 1, 1e-3),
2772                    DIFFSOL_BAD_ARG
2773                );
2774                assert_eq!(
2775                    diffsol_ode_solve(ode, params.as_ptr(), params.len(), 1, 1.0, ptr::null_mut()),
2776                    DIFFSOL_BAD_ARG
2777                );
2778                assert_eq!(
2779                    diffsol_ode_solve(ode, params.as_ptr(), params.len(), 1, 1.0, ptr::null_mut()),
2780                    DIFFSOL_BAD_ARG
2781                );
2782                assert_eq!(
2783                    diffsol_ode_solve_dense(
2784                        ode,
2785                        params.as_ptr(),
2786                        params.len(),
2787                        1,
2788                        t_eval.as_ptr(),
2789                        t_eval.len(),
2790                        ptr::null_mut(),
2791                    ),
2792                    DIFFSOL_BAD_ARG
2793                );
2794                assert_eq!(
2795                    diffsol_ode_solve_dense(
2796                        ode,
2797                        params.as_ptr(),
2798                        params.len(),
2799                        1,
2800                        t_eval.as_ptr(),
2801                        t_eval.len(),
2802                        ptr::null_mut(),
2803                    ),
2804                    DIFFSOL_BAD_ARG
2805                );
2806                #[cfg(feature = "diffsl-llvm")]
2807                if matches!(jit_backend, JitBackendType::Llvm) {
2808                    assert_eq!(
2809                        diffsol_ode_solve_fwd_sens(
2810                            ode,
2811                            params.as_ptr(),
2812                            params.len(),
2813                            1,
2814                            t_eval.as_ptr(),
2815                            t_eval.len(),
2816                            ptr::null_mut(),
2817                        ),
2818                        DIFFSOL_BAD_ARG
2819                    );
2820                    assert_eq!(
2821                        diffsol_ode_solve_fwd_sens(
2822                            ode,
2823                            params.as_ptr(),
2824                            params.len(),
2825                            1,
2826                            t_eval.as_ptr(),
2827                            t_eval.len(),
2828                            ptr::null_mut(),
2829                        ),
2830                        DIFFSOL_BAD_ARG
2831                    );
2832                }
2833
2834                diffsol_ode_free(ode);
2835            }
2836        }
2837    }
2838
2839    #[test]
2840    fn c_api_hybrid_jit_solver_paths_match_expected_values() {
2841        for jit_backend in available_jit_backends() {
2842            unsafe {
2843                let code = CString::new(hybrid_logistic_diffsl_code()).unwrap();
2844                let ode = make_ode_ptr_with_code(
2845                    jit_backend,
2846                    code.as_ptr(),
2847                    matrix_type_to_i32(MatrixType::NalgebraDense),
2848                    linear_solver_to_i32(LinearSolverType::Default),
2849                    ode_solver_to_i32(OdeSolverType::Bdf),
2850                );
2851                assert!(!ode.is_null());
2852
2853                let params = [2.0f64];
2854                let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2855                assert_eq!(
2856                    diffsol_ode_solve(
2857                        ode,
2858                        params.as_ptr(),
2859                        params.len(),
2860                        1,
2861                        2.0,
2862                        &mut solution_ptr
2863                    ),
2864                    DIFFSOL_OK
2865                );
2866                let mut ys_ptr = ptr::null_mut();
2867                let mut ts_ptr = ptr::null_mut();
2868                assert_eq!(
2869                    diffsol_solution_wrapper_get_ys(solution_ptr, &mut ys_ptr),
2870                    DIFFSOL_OK
2871                );
2872                assert_eq!(
2873                    diffsol_solution_wrapper_get_ts(solution_ptr, &mut ts_ptr),
2874                    DIFFSOL_OK
2875                );
2876                let (_rows, cols, ys) = ffi_read_tensor_matrix(ys_ptr);
2877                let ts = ffi_read_tensor_1d(ts_ptr);
2878                assert!(cols >= 1);
2879                assert_close(*ts.last().unwrap(), 2.0, 5e-4, "jit hybrid solve time");
2880                assert_close(
2881                    *ys.last().unwrap(),
2882                    hybrid_logistic_state(2.0, 2.0),
2883                    5e-4,
2884                    "jit hybrid solve value",
2885                );
2886                ffi_free_solution(solution_ptr);
2887
2888                #[cfg(feature = "diffsl-llvm")]
2889                if matches!(jit_backend, JitBackendType::Llvm) {
2890                    let t_eval = [0.25f64, 0.5f64, 1.0f64];
2891                    let mut sens_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2892                    assert_eq!(
2893                        diffsol_ode_solve_fwd_sens(
2894                            ode,
2895                            params.as_ptr(),
2896                            params.len(),
2897                            1,
2898                            t_eval.as_ptr(),
2899                            t_eval.len(),
2900                            &mut sens_solution_ptr,
2901                        ),
2902                        DIFFSOL_OK
2903                    );
2904                    let mut sens_list = ptr::null_mut();
2905                    let mut sens_len = 0usize;
2906                    assert_eq!(
2907                        diffsol_solution_wrapper_get_sens(
2908                            sens_solution_ptr,
2909                            &mut sens_list,
2910                            &mut sens_len
2911                        ),
2912                        DIFFSOL_OK
2913                    );
2914                    let sens_values = ffi_read_tensor_list_matrices(sens_list, sens_len);
2915                    for (i, (&value, &t)) in sens_values[0].2.iter().zip(t_eval.iter()).enumerate()
2916                    {
2917                        assert_close(
2918                            value,
2919                            hybrid_logistic_state_dr(2.0, t),
2920                            5e-4,
2921                            &format!("jit hybrid sensitivity[{i}]"),
2922                        );
2923                    }
2924                    ffi_free_solution(sens_solution_ptr);
2925                }
2926
2927                diffsol_ode_free(ode);
2928            }
2929        }
2930    }
2931}