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