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)]
37pub 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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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#[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}