Skip to main content

diffsol_c/
ode_c.rs

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