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_hybrid(
547 ode: *mut OdeWrapper,
548 params_ptr: *const f64,
549 params_len: usize,
550 final_time: f64,
551 out_solution: *mut *mut SolutionWrapper,
552) -> i32 {
553 if ode.is_null() || out_solution.is_null() || !valid_f64_ptr(params_ptr, params_len) {
554 c_invalid_arg!("invalid arguments to diffsol_ode_solve_hybrid");
555 return DIFFSOL_BAD_ARG;
556 }
557 let params = HostArray::new_vector(params_ptr as *mut u8, params_len, ScalarType::F64);
558 let ode = unsafe { &mut *ode };
559 match ode.solve_hybrid(params, final_time) {
560 Ok(new_solution) => {
561 unsafe {
562 *out_solution = Box::into_raw(Box::new(new_solution));
563 }
564 DIFFSOL_OK
565 }
566 Err(err) => {
567 c_error!(&format!("{}", err));
568 DIFFSOL_ERR
569 }
570 }
571}
572
573#[unsafe(no_mangle)]
580pub unsafe extern "C" fn diffsol_ode_solve_dense(
581 ode: *mut OdeWrapper,
582 params_ptr: *const f64,
583 params_len: usize,
584 t_eval_ptr: *const f64,
585 t_eval_len: usize,
586 out_solution: *mut *mut SolutionWrapper,
587) -> i32 {
588 if ode.is_null()
589 || out_solution.is_null()
590 || !valid_f64_ptr(params_ptr, params_len)
591 || !valid_f64_ptr(t_eval_ptr, t_eval_len)
592 {
593 c_invalid_arg!("invalid arguments to diffsol_ode_solve_dense");
594 return DIFFSOL_BAD_ARG;
595 }
596 let params = HostArray::new_vector(params_ptr as *mut u8, params_len, ScalarType::F64);
597 let t_eval = HostArray::new_vector(t_eval_ptr as *mut u8, t_eval_len, ScalarType::F64);
598 let ode = unsafe { &mut *ode };
599 match ode.solve_dense(params, t_eval) {
600 Ok(new_solution) => {
601 unsafe {
602 *out_solution = Box::into_raw(Box::new(new_solution));
603 }
604 DIFFSOL_OK
605 }
606 Err(err) => {
607 c_error!(&format!("{}", err));
608 DIFFSOL_ERR
609 }
610 }
611}
612
613#[unsafe(no_mangle)]
620pub unsafe extern "C" fn diffsol_ode_solve_hybrid_dense(
621 ode: *mut OdeWrapper,
622 params_ptr: *const f64,
623 params_len: usize,
624 t_eval_ptr: *const f64,
625 t_eval_len: usize,
626 out_solution: *mut *mut SolutionWrapper,
627) -> i32 {
628 if ode.is_null()
629 || out_solution.is_null()
630 || !valid_f64_ptr(params_ptr, params_len)
631 || !valid_f64_ptr(t_eval_ptr, t_eval_len)
632 {
633 c_invalid_arg!("invalid arguments to diffsol_ode_solve_hybrid_dense");
634 return DIFFSOL_BAD_ARG;
635 }
636 let params = HostArray::new_vector(params_ptr as *mut u8, params_len, ScalarType::F64);
637 let t_eval = HostArray::new_vector(t_eval_ptr as *mut u8, t_eval_len, ScalarType::F64);
638 let ode = unsafe { &mut *ode };
639 match ode.solve_hybrid_dense(params, t_eval) {
640 Ok(new_solution) => {
641 unsafe {
642 *out_solution = Box::into_raw(Box::new(new_solution));
643 }
644 DIFFSOL_OK
645 }
646 Err(err) => {
647 c_error!(&format!("{}", err));
648 DIFFSOL_ERR
649 }
650 }
651}
652
653#[unsafe(no_mangle)]
660pub unsafe extern "C" fn diffsol_ode_solve_fwd_sens(
661 ode: *mut OdeWrapper,
662 params_ptr: *const f64,
663 params_len: usize,
664 t_eval_ptr: *const f64,
665 t_eval_len: usize,
666 out_solution: *mut *mut SolutionWrapper,
667) -> i32 {
668 if ode.is_null()
669 || out_solution.is_null()
670 || !valid_f64_ptr(params_ptr, params_len)
671 || !valid_f64_ptr(t_eval_ptr, t_eval_len)
672 {
673 c_invalid_arg!("invalid arguments to diffsol_ode_solve_fwd_sens");
674 return DIFFSOL_BAD_ARG;
675 }
676 let params = HostArray::new_vector(params_ptr as *mut u8, params_len, ScalarType::F64);
677 let t_eval = HostArray::new_vector(t_eval_ptr as *mut u8, t_eval_len, ScalarType::F64);
678 let ode = unsafe { &mut *ode };
679 match ode.solve_fwd_sens(params, t_eval) {
680 Ok(new_solution) => {
681 unsafe {
682 *out_solution = Box::into_raw(Box::new(new_solution));
683 }
684 DIFFSOL_OK
685 }
686 Err(err) => {
687 c_error!(&format!("{}", err));
688 DIFFSOL_ERR
689 }
690 }
691}
692
693#[unsafe(no_mangle)]
700pub unsafe extern "C" fn diffsol_ode_solve_hybrid_fwd_sens(
701 ode: *mut OdeWrapper,
702 params_ptr: *const f64,
703 params_len: usize,
704 t_eval_ptr: *const f64,
705 t_eval_len: usize,
706 out_solution: *mut *mut SolutionWrapper,
707) -> i32 {
708 if ode.is_null()
709 || out_solution.is_null()
710 || !valid_f64_ptr(params_ptr, params_len)
711 || !valid_f64_ptr(t_eval_ptr, t_eval_len)
712 {
713 c_invalid_arg!("invalid arguments to diffsol_ode_solve_hybrid_fwd_sens");
714 return DIFFSOL_BAD_ARG;
715 }
716 let params = HostArray::new_vector(params_ptr as *mut u8, params_len, ScalarType::F64);
717 let t_eval = HostArray::new_vector(t_eval_ptr as *mut u8, t_eval_len, ScalarType::F64);
718 let ode = unsafe { &mut *ode };
719 match ode.solve_hybrid_fwd_sens(params, t_eval) {
720 Ok(new_solution) => {
721 unsafe {
722 *out_solution = Box::into_raw(Box::new(new_solution));
723 }
724 DIFFSOL_OK
725 }
726 Err(err) => {
727 c_error!(&format!("{}", err));
728 DIFFSOL_ERR
729 }
730 }
731}
732
733#[unsafe(no_mangle)]
741pub unsafe extern "C" fn diffsol_ode_solve_sum_squares_adj(
742 ode: *mut OdeWrapper,
743 params_ptr: *const f64,
744 params_len: usize,
745 data_ptr: *const f64,
746 data_rows: usize,
747 data_cols: usize,
748 data_row_stride: usize,
749 data_col_stride: usize,
750 t_eval_ptr: *const f64,
751 t_eval_len: usize,
752 out_value: *mut f64,
753 out_sens: *mut *mut HostArray,
754) -> i32 {
755 if ode.is_null()
756 || out_value.is_null()
757 || out_sens.is_null()
758 || data_ptr.is_null()
759 || !valid_f64_ptr(params_ptr, params_len)
760 || !valid_f64_ptr(t_eval_ptr, t_eval_len)
761 {
762 c_invalid_arg!("invalid arguments to diffsol_ode_solve_sum_squares_adj");
763 return DIFFSOL_BAD_ARG;
764 }
765 let params = HostArray::new_vector(params_ptr as *mut u8, params_len, ScalarType::F64);
766 let t_eval = HostArray::new_vector(t_eval_ptr as *mut u8, t_eval_len, ScalarType::F64);
767 let data = HostArray::new_col_major(
768 data_ptr as *mut u8,
769 data_rows,
770 data_cols,
771 data_row_stride as isize,
772 data_col_stride as isize,
773 ScalarType::F64,
774 );
775 let ode = unsafe { &mut *ode };
776 match ode.solve_sum_squares_adj(params, data, t_eval) {
777 Ok((value, sens)) => {
778 let sens_boxed = boxed_host_array(sens);
779 unsafe {
780 *out_value = value;
781 *out_sens = sens_boxed;
782 }
783 DIFFSOL_OK
784 }
785 Err(err) => {
786 c_error!(&format!("{}", err));
787 DIFFSOL_ERR
788 }
789 }
790}
791
792#[unsafe(no_mangle)]
797pub unsafe extern "C" fn diffsol_ode_get_matrix_type(ode: *const OdeWrapper) -> i32 {
798 if ode.is_null() {
799 c_invalid_arg!("ode is null");
800 return -1;
801 }
802 let ode = unsafe { &*ode };
803 match ode.get_matrix_type() {
804 Ok(value) => matrix_type_to_i32(value),
805 Err(err) => {
806 c_error!(&format!("{}", err));
807 -1
808 }
809 }
810}
811
812#[unsafe(no_mangle)]
817pub unsafe extern "C" fn diffsol_ode_get_ode_solver(ode: *const OdeWrapper) -> i32 {
818 if ode.is_null() {
819 c_invalid_arg!("ode is null");
820 return -1;
821 }
822 let ode = unsafe { &*ode };
823 match ode.get_ode_solver() {
824 Ok(value) => ode_solver_to_i32(value),
825 Err(err) => {
826 c_error!(&format!("{}", err));
827 -1
828 }
829 }
830}
831
832#[unsafe(no_mangle)]
837pub unsafe extern "C" fn diffsol_ode_set_ode_solver(ode: *mut OdeWrapper, value: i32) -> i32 {
838 if ode.is_null() {
839 c_invalid_arg!("ode is null");
840 return DIFFSOL_BAD_ARG;
841 }
842 let value = match ode_solver_from_i32(value) {
843 Some(v) => v,
844 None => {
845 c_invalid_arg!("invalid ode_solver");
846 return DIFFSOL_BAD_ARG;
847 }
848 };
849 let ode = unsafe { &mut *ode };
850 match ode.set_ode_solver(value) {
851 Ok(()) => DIFFSOL_OK,
852 Err(err) => c_error!(&format!("{}", err)),
853 }
854}
855
856#[unsafe(no_mangle)]
861pub unsafe extern "C" fn diffsol_ode_get_linear_solver(ode: *const OdeWrapper) -> i32 {
862 if ode.is_null() {
863 c_invalid_arg!("ode is null");
864 return -1;
865 }
866 let ode = unsafe { &*ode };
867 match ode.get_linear_solver() {
868 Ok(value) => linear_solver_to_i32(value),
869 Err(err) => {
870 c_error!(&format!("{}", err));
871 -1
872 }
873 }
874}
875
876#[unsafe(no_mangle)]
881pub unsafe extern "C" fn diffsol_ode_set_linear_solver(ode: *mut OdeWrapper, value: i32) -> i32 {
882 if ode.is_null() {
883 c_invalid_arg!("ode is null");
884 return DIFFSOL_BAD_ARG;
885 }
886 let value = match linear_solver_from_i32(value) {
887 Some(v) => v,
888 None => {
889 c_invalid_arg!("invalid linear_solver");
890 return DIFFSOL_BAD_ARG;
891 }
892 };
893 let ode = unsafe { &mut *ode };
894 match ode.set_linear_solver(value) {
895 Ok(()) => DIFFSOL_OK,
896 Err(err) => c_error!(&format!("{}", err)),
897 }
898}
899
900#[unsafe(no_mangle)]
906pub unsafe extern "C" fn diffsol_ode_get_rtol(ode: *const OdeWrapper, out_value: *mut f64) -> i32 {
907 if ode.is_null() || out_value.is_null() {
908 c_invalid_arg!("invalid arguments to diffsol_ode_get_rtol");
909 return DIFFSOL_BAD_ARG;
910 }
911 let ode = unsafe { &*ode };
912 match ode.get_rtol() {
913 Ok(value) => {
914 unsafe {
915 *out_value = value;
916 }
917 DIFFSOL_OK
918 }
919 Err(err) => c_error!(&format!("{}", err)),
920 }
921}
922
923#[unsafe(no_mangle)]
928pub unsafe extern "C" fn diffsol_ode_set_rtol(ode: *mut OdeWrapper, value: f64) -> i32 {
929 if ode.is_null() {
930 c_invalid_arg!("ode is null");
931 return DIFFSOL_BAD_ARG;
932 }
933 let ode = unsafe { &mut *ode };
934 match ode.set_rtol(value) {
935 Ok(()) => DIFFSOL_OK,
936 Err(err) => c_error!(&format!("{}", err)),
937 }
938}
939
940#[unsafe(no_mangle)]
946pub unsafe extern "C" fn diffsol_ode_get_atol(ode: *const OdeWrapper, out_value: *mut f64) -> i32 {
947 if ode.is_null() || out_value.is_null() {
948 c_invalid_arg!("invalid arguments to diffsol_ode_get_atol");
949 return DIFFSOL_BAD_ARG;
950 }
951 let ode = unsafe { &*ode };
952 match ode.get_atol() {
953 Ok(value) => {
954 unsafe {
955 *out_value = value;
956 }
957 DIFFSOL_OK
958 }
959 Err(err) => c_error!(&format!("{}", err)),
960 }
961}
962
963#[unsafe(no_mangle)]
968pub unsafe extern "C" fn diffsol_ode_set_atol(ode: *mut OdeWrapper, value: f64) -> i32 {
969 if ode.is_null() {
970 c_invalid_arg!("ode is null");
971 return DIFFSOL_BAD_ARG;
972 }
973 let ode = unsafe { &mut *ode };
974 match ode.set_atol(value) {
975 Ok(()) => DIFFSOL_OK,
976 Err(err) => c_error!(&format!("{}", err)),
977 }
978}
979
980#[cfg(all(test, feature = "diffsl-external-f64"))]
981mod tests {
982 use std::ptr;
983
984 use crate::initial_condition_options::InitialConditionSolverOptions;
985 use crate::linear_solver_type::LinearSolverType;
986 use crate::linear_solver_type_c::{
987 diffsol_linear_solver_type_count, diffsol_linear_solver_type_is_valid,
988 diffsol_linear_solver_type_name, linear_solver_to_i32,
989 };
990 use crate::matrix_type::MatrixType;
991 use crate::ode_options::OdeSolverOptions;
992 use crate::ode_options_c::{
993 diffsol_ode_options_free, diffsol_ode_options_get_max_nonlinear_solver_iterations,
994 diffsol_ode_options_get_min_timestep,
995 diffsol_ode_options_set_max_nonlinear_solver_iterations,
996 diffsol_ode_options_set_min_timestep,
997 };
998 use crate::ode_solver_type::OdeSolverType;
999 use crate::ode_solver_type_c::{
1000 diffsol_ode_solver_type_count, diffsol_ode_solver_type_is_valid,
1001 diffsol_ode_solver_type_name, ode_solver_to_i32,
1002 };
1003 use crate::scalar_type::ScalarType;
1004 use crate::scalar_type_c::{
1005 diffsol_scalar_type_count, diffsol_scalar_type_is_valid, diffsol_scalar_type_name,
1006 scalar_type_to_i32,
1007 };
1008 use crate::solution_wrapper_c::{
1009 diffsol_solution_wrapper_get_sens, diffsol_solution_wrapper_get_ts,
1010 diffsol_solution_wrapper_get_ys,
1011 };
1012 use crate::test_support::{
1013 assert_close, assert_last_error_contains, c_string, clear_last_error, ffi_free_solution,
1014 ffi_read_host_array_list_matrices, ffi_read_host_array_matrix, ffi_read_host_array_vector,
1015 find_time_window, logistic_state, logistic_state_dr, mass_state_deps, rhs_input_deps,
1016 rhs_state_deps, ASSERT_TOL, LOGISTIC_X0,
1017 };
1018 use crate::{
1019 initial_condition_options_c::{
1020 diffsol_ic_options_free, diffsol_ic_options_get_max_linesearch_iterations,
1021 diffsol_ic_options_get_use_linesearch,
1022 diffsol_ic_options_set_max_linesearch_iterations,
1023 diffsol_ic_options_set_use_linesearch,
1024 },
1025 matrix_type_c::{
1026 diffsol_matrix_type_count, diffsol_matrix_type_is_valid, diffsol_matrix_type_name,
1027 matrix_type_to_i32,
1028 },
1029 };
1030
1031 use super::*;
1032
1033 fn to_dep_pairs(values: &[(usize, usize)]) -> Vec<DiffsolDepPair> {
1034 values
1035 .iter()
1036 .map(|&(row, col)| DiffsolDepPair { row, col })
1037 .collect()
1038 }
1039
1040 unsafe fn make_ode_ptr(
1041 matrix_type: i32,
1042 linear_solver: i32,
1043 ode_solver: i32,
1044 ) -> *mut OdeWrapper {
1045 let rhs_state_deps = to_dep_pairs(&rhs_state_deps());
1046 let rhs_input_deps = to_dep_pairs(&rhs_input_deps());
1047 let mass_state_deps = to_dep_pairs(&mass_state_deps());
1048 unsafe {
1049 diffsol_ode_new_external(
1050 matrix_type,
1051 linear_solver,
1052 ode_solver,
1053 rhs_state_deps.as_ptr(),
1054 rhs_state_deps.len(),
1055 rhs_input_deps.as_ptr(),
1056 rhs_input_deps.len(),
1057 mass_state_deps.as_ptr(),
1058 mass_state_deps.len(),
1059 )
1060 }
1061 }
1062
1063 #[test]
1064 fn c_api_reports_enum_metadata() {
1065 clear_last_error();
1066 unsafe {
1067 assert_eq!(diffsol_matrix_type_count(), 3);
1068 assert_eq!(diffsol_ode_solver_type_count(), 4);
1069 assert_eq!(diffsol_linear_solver_type_count(), 3);
1070 assert_eq!(diffsol_scalar_type_count(), 2);
1071
1072 assert_eq!(
1073 c_string(diffsol_matrix_type_name(matrix_type_to_i32(
1074 MatrixType::NalgebraDense
1075 ))),
1076 "nalgebra_dense"
1077 );
1078 assert_eq!(
1079 c_string(diffsol_ode_solver_type_name(ode_solver_to_i32(
1080 OdeSolverType::Bdf
1081 ))),
1082 "bdf"
1083 );
1084 assert_eq!(
1085 c_string(diffsol_linear_solver_type_name(linear_solver_to_i32(
1086 LinearSolverType::Default
1087 ))),
1088 "default"
1089 );
1090 assert_eq!(
1091 c_string(diffsol_scalar_type_name(scalar_type_to_i32(
1092 ScalarType::F64
1093 ))),
1094 "f64"
1095 );
1096 }
1097 }
1098
1099 #[test]
1100 fn c_api_invalid_enums_set_last_error() {
1101 clear_last_error();
1102 unsafe {
1103 assert_eq!(diffsol_matrix_type_is_valid(99), 0);
1104 assert_last_error_contains("invalid matrix_type");
1105 clear_last_error();
1106
1107 assert_eq!(diffsol_ode_solver_type_is_valid(99), 0);
1108 assert_last_error_contains("invalid ode_solver_type");
1109 clear_last_error();
1110
1111 assert_eq!(diffsol_linear_solver_type_is_valid(99), 0);
1112 assert_last_error_contains("invalid linear_solver_type");
1113 clear_last_error();
1114
1115 assert_eq!(diffsol_scalar_type_is_valid(99), 0);
1116 assert_last_error_contains("invalid scalar_type");
1117 }
1118 }
1119
1120 #[test]
1121 fn c_api_rejects_invalid_ode_arguments() {
1122 clear_last_error();
1123 unsafe {
1124 let mut out_array = ptr::null_mut();
1125 let status = diffsol_ode_y0(ptr::null_mut(), ptr::null(), 0, &mut out_array);
1126 assert_eq!(status, DIFFSOL_BAD_ARG);
1127 assert!(out_array.is_null());
1128 assert_last_error_contains("invalid arguments to diffsol_ode_y0");
1129 clear_last_error();
1130
1131 let ode = make_ode_ptr(
1132 99,
1133 linear_solver_to_i32(LinearSolverType::Default),
1134 ode_solver_to_i32(OdeSolverType::Bdf),
1135 );
1136 assert!(ode.is_null());
1137 assert_last_error_contains("invalid matrix_type");
1138 }
1139 }
1140
1141 #[test]
1142 fn c_api_full_lifecycle_matches_external_logistic_model() {
1143 clear_last_error();
1144 unsafe {
1145 let ode = make_ode_ptr(
1146 matrix_type_to_i32(MatrixType::NalgebraDense),
1147 linear_solver_to_i32(LinearSolverType::Default),
1148 ode_solver_to_i32(OdeSolverType::Bdf),
1149 );
1150 assert!(!ode.is_null());
1151
1152 assert_eq!(
1153 diffsol_ode_get_matrix_type(ode),
1154 matrix_type_to_i32(MatrixType::NalgebraDense)
1155 );
1156 assert_eq!(
1157 diffsol_ode_get_ode_solver(ode),
1158 ode_solver_to_i32(OdeSolverType::Bdf)
1159 );
1160 assert_eq!(
1161 diffsol_ode_get_linear_solver(ode),
1162 linear_solver_to_i32(LinearSolverType::Default)
1163 );
1164
1165 assert_eq!(
1166 diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
1167 DIFFSOL_OK
1168 );
1169 assert_eq!(
1170 diffsol_ode_get_ode_solver(ode),
1171 ode_solver_to_i32(OdeSolverType::Tsit45)
1172 );
1173 assert_eq!(
1174 diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Bdf)),
1175 DIFFSOL_OK
1176 );
1177
1178 assert_eq!(diffsol_ode_set_rtol(ode, 1e-8), DIFFSOL_OK);
1179 assert_eq!(diffsol_ode_set_atol(ode, 1e-8), DIFFSOL_OK);
1180 let mut rtol = 0.0;
1181 let mut atol = 0.0;
1182 assert_eq!(diffsol_ode_get_rtol(ode, &mut rtol), DIFFSOL_OK);
1183 assert_eq!(diffsol_ode_get_atol(ode, &mut atol), DIFFSOL_OK);
1184 assert_close(rtol, 1e-8, ASSERT_TOL, "rtol roundtrip");
1185 assert_close(atol, 1e-8, ASSERT_TOL, "atol roundtrip");
1186
1187 let mut ic_options: *mut InitialConditionSolverOptions = ptr::null_mut();
1188 assert_eq!(diffsol_ode_get_ic_options(ode, &mut ic_options), DIFFSOL_OK);
1189 assert!(!ic_options.is_null());
1190 let mut use_linesearch = 0;
1191 let mut max_linesearch_iterations = 0usize;
1192 assert_eq!(
1193 diffsol_ic_options_get_use_linesearch(ic_options, &mut use_linesearch),
1194 DIFFSOL_OK
1195 );
1196 assert_eq!(
1197 diffsol_ic_options_set_use_linesearch(ic_options, 1),
1198 DIFFSOL_OK
1199 );
1200 assert_eq!(
1201 diffsol_ic_options_get_use_linesearch(ic_options, &mut use_linesearch),
1202 DIFFSOL_OK
1203 );
1204 assert_eq!(use_linesearch, 1);
1205 assert_eq!(
1206 diffsol_ic_options_set_max_linesearch_iterations(ic_options, 23),
1207 DIFFSOL_OK
1208 );
1209 assert_eq!(
1210 diffsol_ic_options_get_max_linesearch_iterations(
1211 ic_options,
1212 &mut max_linesearch_iterations
1213 ),
1214 DIFFSOL_OK
1215 );
1216 assert_eq!(max_linesearch_iterations, 23);
1217 diffsol_ic_options_free(ic_options);
1218
1219 let mut ode_options: *mut OdeSolverOptions = ptr::null_mut();
1220 assert_eq!(diffsol_ode_get_options(ode, &mut ode_options), DIFFSOL_OK);
1221 assert!(!ode_options.is_null());
1222 let mut max_nonlinear_iterations = 0usize;
1223 let mut min_timestep = 0.0;
1224 assert_eq!(
1225 diffsol_ode_options_set_max_nonlinear_solver_iterations(ode_options, 17),
1226 DIFFSOL_OK
1227 );
1228 assert_eq!(
1229 diffsol_ode_options_get_max_nonlinear_solver_iterations(
1230 ode_options,
1231 &mut max_nonlinear_iterations
1232 ),
1233 DIFFSOL_OK
1234 );
1235 assert_eq!(max_nonlinear_iterations, 17);
1236 assert_eq!(
1237 diffsol_ode_options_set_min_timestep(ode_options, 1e-4),
1238 DIFFSOL_OK
1239 );
1240 assert_eq!(
1241 diffsol_ode_options_get_min_timestep(ode_options, &mut min_timestep),
1242 DIFFSOL_OK
1243 );
1244 assert_close(min_timestep, 1e-4, ASSERT_TOL, "min_timestep roundtrip");
1245 diffsol_ode_options_free(ode_options);
1246
1247 let params = [2.0f64];
1248 let y = [0.25f64];
1249 let v = [3.0f64];
1250
1251 let mut y0_ptr = ptr::null_mut();
1252 assert_eq!(
1253 diffsol_ode_y0(ode, params.as_ptr(), params.len(), &mut y0_ptr),
1254 DIFFSOL_OK
1255 );
1256 assert_eq!(ffi_read_host_array_vector(y0_ptr), vec![LOGISTIC_X0]);
1257
1258 let mut rhs_ptr = ptr::null_mut();
1259 assert_eq!(
1260 diffsol_ode_rhs(
1261 ode,
1262 params.as_ptr(),
1263 params.len(),
1264 0.0,
1265 y.as_ptr(),
1266 y.len(),
1267 &mut rhs_ptr,
1268 ),
1269 DIFFSOL_OK
1270 );
1271 assert_close(
1272 ffi_read_host_array_vector(rhs_ptr)[0],
1273 0.375,
1274 ASSERT_TOL,
1275 "ffi rhs",
1276 );
1277
1278 let mut rhs_jac_mul_ptr = ptr::null_mut();
1279 assert_eq!(
1280 diffsol_ode_rhs_jac_mul(
1281 ode,
1282 params.as_ptr(),
1283 params.len(),
1284 0.0,
1285 y.as_ptr(),
1286 y.len(),
1287 v.as_ptr(),
1288 v.len(),
1289 &mut rhs_jac_mul_ptr,
1290 ),
1291 DIFFSOL_OK
1292 );
1293 assert_close(
1294 ffi_read_host_array_vector(rhs_jac_mul_ptr)[0],
1295 3.0,
1296 ASSERT_TOL,
1297 "ffi rhs_jac_mul",
1298 );
1299
1300 let mut solve_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1301 assert_eq!(
1302 diffsol_ode_solve(
1303 ode,
1304 params.as_ptr(),
1305 params.len(),
1306 1e-9,
1307 &mut solve_solution_ptr
1308 ),
1309 DIFFSOL_OK
1310 );
1311 assert!(!solve_solution_ptr.is_null());
1312
1313 let mut solve_ys_ptr = ptr::null_mut();
1314 let mut solve_ts_ptr = ptr::null_mut();
1315 assert_eq!(
1316 diffsol_solution_wrapper_get_ys(solve_solution_ptr, &mut solve_ys_ptr),
1317 DIFFSOL_OK
1318 );
1319 assert_eq!(
1320 diffsol_solution_wrapper_get_ts(solve_solution_ptr, &mut solve_ts_ptr),
1321 DIFFSOL_OK
1322 );
1323 let (solve_rows, solve_cols, solve_ys) = ffi_read_host_array_matrix(solve_ys_ptr);
1324 let solve_ts = ffi_read_host_array_vector(solve_ts_ptr);
1325 assert_eq!(solve_rows, 1);
1326 assert_eq!(solve_cols, solve_ts.len());
1327 assert!(!solve_ts.is_empty());
1328 assert_close(
1329 *solve_ts.last().unwrap(),
1330 1e-9,
1331 ASSERT_TOL,
1332 "ffi solve final time",
1333 );
1334 assert_close(
1335 *solve_ys.last().unwrap(),
1336 logistic_state(LOGISTIC_X0, 2.0, 1e-9),
1337 ASSERT_TOL,
1338 "ffi solve final value",
1339 );
1340 ffi_free_solution(solve_solution_ptr);
1341
1342 let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1343 assert_eq!(
1344 diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
1345 DIFFSOL_OK
1346 );
1347
1348 let t_eval = [0.25f64, 0.5f64, 1.0f64];
1349 assert_eq!(
1350 diffsol_ode_solve_dense(
1351 ode,
1352 params.as_ptr(),
1353 params.len(),
1354 t_eval.as_ptr(),
1355 t_eval.len(),
1356 &mut solution_ptr,
1357 ),
1358 DIFFSOL_OK
1359 );
1360 let mut ys_ptr = ptr::null_mut();
1361 let mut ts_ptr = ptr::null_mut();
1362 assert_eq!(
1363 diffsol_solution_wrapper_get_ys(solution_ptr, &mut ys_ptr),
1364 DIFFSOL_OK
1365 );
1366 assert_eq!(
1367 diffsol_solution_wrapper_get_ts(solution_ptr, &mut ts_ptr),
1368 DIFFSOL_OK
1369 );
1370 let (rows, cols, ys) = ffi_read_host_array_matrix(ys_ptr);
1371 let ts = ffi_read_host_array_vector(ts_ptr);
1372 assert_eq!(rows, 1);
1373 assert_eq!(cols, ts.len());
1374 let start = find_time_window(&ts, &t_eval, ASSERT_TOL);
1375 for (i, &t) in t_eval.iter().enumerate() {
1376 assert_close(ts[start + i], t, ASSERT_TOL, "ffi solution time");
1377 assert_close(
1378 ys[start + i],
1379 logistic_state(0.1, 2.0, t),
1380 5e-4,
1381 "ffi solution value",
1382 );
1383 }
1384 assert_eq!(
1385 diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Bdf)),
1386 DIFFSOL_OK
1387 );
1388
1389 let hybrid_t_eval = [0.5f64, 1.0, 1.25, 1.5, 2.0];
1390 let hybrid_ode = make_ode_ptr(
1391 matrix_type_to_i32(MatrixType::NalgebraDense),
1392 linear_solver_to_i32(LinearSolverType::Default),
1393 ode_solver_to_i32(OdeSolverType::Bdf),
1394 );
1395 assert!(!hybrid_ode.is_null());
1396 let mut hybrid_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1397 assert_eq!(
1398 diffsol_ode_solve_hybrid_dense(
1399 hybrid_ode,
1400 params.as_ptr(),
1401 params.len(),
1402 hybrid_t_eval.as_ptr(),
1403 hybrid_t_eval.len(),
1404 &mut hybrid_solution_ptr,
1405 ),
1406 DIFFSOL_OK
1407 );
1408 let mut hybrid_ys_ptr = ptr::null_mut();
1409 let mut hybrid_ts_ptr = ptr::null_mut();
1410 assert_eq!(
1411 diffsol_solution_wrapper_get_ys(hybrid_solution_ptr, &mut hybrid_ys_ptr),
1412 DIFFSOL_OK
1413 );
1414 assert_eq!(
1415 diffsol_solution_wrapper_get_ts(hybrid_solution_ptr, &mut hybrid_ts_ptr),
1416 DIFFSOL_OK
1417 );
1418 let (hybrid_rows, hybrid_cols, hybrid_ys) = ffi_read_host_array_matrix(hybrid_ys_ptr);
1419 let hybrid_ts = ffi_read_host_array_vector(hybrid_ts_ptr);
1420 assert_eq!(hybrid_rows, 1);
1421 assert_eq!(hybrid_cols, hybrid_t_eval.len());
1422 assert_eq!(hybrid_ts, hybrid_t_eval);
1423 assert_close(
1424 hybrid_ys[0],
1425 logistic_state(LOGISTIC_X0, 2.0, hybrid_t_eval[0]),
1426 5e-4,
1427 "ffi hybrid dense pre-root value",
1428 );
1429 assert_close(
1430 hybrid_ys[1],
1431 logistic_state(LOGISTIC_X0, 2.0, hybrid_t_eval[1]),
1432 5e-4,
1433 "ffi hybrid dense near-root value",
1434 );
1435 for (i, value) in hybrid_ys.iter().enumerate().skip(2) {
1436 assert_close(
1437 *value,
1438 1.0,
1439 5e-4,
1440 &format!("ffi hybrid dense post-root value[{i}]"),
1441 );
1442 }
1443 ffi_free_solution(hybrid_solution_ptr);
1444 diffsol_ode_free(hybrid_ode);
1445
1446 let analysis_ode = make_ode_ptr(
1447 matrix_type_to_i32(MatrixType::NalgebraDense),
1448 linear_solver_to_i32(LinearSolverType::Default),
1449 ode_solver_to_i32(OdeSolverType::Bdf),
1450 );
1451 assert!(!analysis_ode.is_null());
1452
1453 let mut sens_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1454 assert_eq!(
1455 diffsol_ode_solve_fwd_sens(
1456 analysis_ode,
1457 params.as_ptr(),
1458 params.len(),
1459 t_eval.as_ptr(),
1460 t_eval.len(),
1461 &mut sens_solution_ptr,
1462 ),
1463 DIFFSOL_OK
1464 );
1465 let mut sens_list = ptr::null_mut();
1466 let mut sens_len = 0usize;
1467 assert_eq!(
1468 diffsol_solution_wrapper_get_sens(sens_solution_ptr, &mut sens_list, &mut sens_len),
1469 DIFFSOL_OK
1470 );
1471 let sens_values = ffi_read_host_array_list_matrices(sens_list, sens_len);
1472 assert_eq!(sens_values.len(), 1);
1473 assert_eq!(sens_values[0].0, 1);
1474 assert_eq!(sens_values[0].1, t_eval.len());
1475 for (i, (&value, &t)) in sens_values[0].2.iter().zip(t_eval.iter()).enumerate() {
1476 assert_close(
1477 value,
1478 logistic_state_dr(LOGISTIC_X0, 2.0, t),
1479 ASSERT_TOL,
1480 &format!("ffi sensitivity[{i}]"),
1481 );
1482 }
1483
1484 let adjoint_t_eval = [0.0f64, 0.25f64, 0.5f64, 1.0f64];
1485 let adjoint_data: Vec<f64> = adjoint_t_eval
1486 .iter()
1487 .map(|&t| logistic_state(LOGISTIC_X0, 2.0, t))
1488 .collect();
1489 let mut objective = 0.0;
1490 let mut adjoint_grad_ptr = ptr::null_mut();
1491 assert_eq!(
1492 diffsol_ode_solve_sum_squares_adj(
1493 analysis_ode,
1494 params.as_ptr(),
1495 params.len(),
1496 adjoint_data.as_ptr(),
1497 1,
1498 adjoint_t_eval.len(),
1499 1,
1500 1,
1501 adjoint_t_eval.as_ptr(),
1502 adjoint_t_eval.len(),
1503 &mut objective,
1504 &mut adjoint_grad_ptr,
1505 ),
1506 DIFFSOL_OK
1507 );
1508 assert_close(objective, 0.0, ASSERT_TOL, "ffi adjoint objective");
1509 let grad = ffi_read_host_array_vector(adjoint_grad_ptr);
1510 assert_eq!(grad.len(), 1);
1511 assert_close(grad[0], 0.0, ASSERT_TOL, "ffi adjoint gradient");
1512
1513 ffi_free_solution(sens_solution_ptr);
1514 diffsol_ode_free(analysis_ode);
1515 ffi_free_solution(solution_ptr);
1516 diffsol_ode_free(ode);
1517 }
1518 }
1519}
1520
1521#[cfg(all(test, feature = "diffsl-external-dynamic"))]
1522mod external_dynamic_tests {
1523 use std::ffi::CString;
1524 use std::os::raw::c_char;
1525 use std::ptr;
1526
1527 use crate::linear_solver_type::LinearSolverType;
1528 use crate::linear_solver_type_c::linear_solver_to_i32;
1529 use crate::matrix_type::MatrixType;
1530 use crate::matrix_type_c::matrix_type_to_i32;
1531 use crate::ode_solver_type::OdeSolverType;
1532 use crate::ode_solver_type_c::ode_solver_to_i32;
1533 use crate::test_support::{
1534 assert_last_error_contains, assert_last_error_set, clear_last_error,
1535 external_dynamic_fixture_path, ffi_read_host_array_vector, mass_state_deps, rhs_input_deps,
1536 rhs_state_deps, LOGISTIC_X0,
1537 };
1538
1539 use super::*;
1540
1541 fn to_dep_pairs(values: &[(usize, usize)]) -> Vec<DiffsolDepPair> {
1542 values
1543 .iter()
1544 .map(|&(row, col)| DiffsolDepPair { row, col })
1545 .collect()
1546 }
1547
1548 unsafe fn make_ode_ptr(
1549 matrix_type: i32,
1550 linear_solver: i32,
1551 ode_solver: i32,
1552 ) -> *mut OdeWrapper {
1553 let path = CString::new(
1554 external_dynamic_fixture_path()
1555 .to_string_lossy()
1556 .into_owned(),
1557 )
1558 .unwrap();
1559 let rhs_state_deps = to_dep_pairs(&rhs_state_deps());
1560 let rhs_input_deps = to_dep_pairs(&rhs_input_deps());
1561 let mass_state_deps = to_dep_pairs(&mass_state_deps());
1562 unsafe {
1563 diffsol_ode_new_external_dynamic(
1564 path.as_ptr(),
1565 matrix_type,
1566 linear_solver,
1567 ode_solver,
1568 rhs_state_deps.as_ptr(),
1569 rhs_state_deps.len(),
1570 rhs_input_deps.as_ptr(),
1571 rhs_input_deps.len(),
1572 mass_state_deps.as_ptr(),
1573 mass_state_deps.len(),
1574 )
1575 }
1576 }
1577
1578 #[test]
1579 fn c_api_constructs_dynamic_external_ode() {
1580 clear_last_error();
1581 unsafe {
1582 let ode = make_ode_ptr(
1583 matrix_type_to_i32(MatrixType::NalgebraDense),
1584 linear_solver_to_i32(LinearSolverType::Default),
1585 ode_solver_to_i32(OdeSolverType::Bdf),
1586 );
1587 assert!(!ode.is_null());
1588 assert_eq!(
1589 diffsol_ode_get_matrix_type(ode),
1590 matrix_type_to_i32(MatrixType::NalgebraDense)
1591 );
1592 let params = [2.0_f64];
1593 let mut y0_ptr = ptr::null_mut();
1594 assert_eq!(
1595 diffsol_ode_y0(ode, params.as_ptr(), params.len(), &mut y0_ptr),
1596 DIFFSOL_OK
1597 );
1598 assert_eq!(ffi_read_host_array_vector(y0_ptr), vec![LOGISTIC_X0]);
1599 diffsol_ode_free(ode);
1600 }
1601 }
1602
1603 #[test]
1604 fn c_api_rejects_null_dynamic_library_path() {
1605 clear_last_error();
1606 unsafe {
1607 let ode = diffsol_ode_new_external_dynamic(
1608 ptr::null(),
1609 matrix_type_to_i32(MatrixType::NalgebraDense),
1610 linear_solver_to_i32(LinearSolverType::Default),
1611 ode_solver_to_i32(OdeSolverType::Bdf),
1612 ptr::null(),
1613 0,
1614 ptr::null(),
1615 0,
1616 ptr::null(),
1617 0,
1618 );
1619 assert!(ode.is_null());
1620 assert_last_error_contains("path is null");
1621 }
1622 }
1623
1624 #[test]
1625 fn c_api_rejects_non_utf8_dynamic_library_path() {
1626 clear_last_error();
1627 unsafe {
1628 let invalid_utf8 = [0xff_u8, 0];
1629 let ode = diffsol_ode_new_external_dynamic(
1630 invalid_utf8.as_ptr() as *const c_char,
1631 matrix_type_to_i32(MatrixType::NalgebraDense),
1632 linear_solver_to_i32(LinearSolverType::Default),
1633 ode_solver_to_i32(OdeSolverType::Bdf),
1634 ptr::null(),
1635 0,
1636 ptr::null(),
1637 0,
1638 ptr::null(),
1639 0,
1640 );
1641 assert!(ode.is_null());
1642 assert_last_error_contains("path is not valid UTF-8");
1643 }
1644 }
1645
1646 #[test]
1647 fn c_api_reports_missing_dynamic_library_path() {
1648 clear_last_error();
1649 unsafe {
1650 let missing_path = external_dynamic_fixture_path().with_file_name("does-not-exist");
1651 let missing_path = CString::new(missing_path.to_string_lossy().into_owned()).unwrap();
1652 let ode = diffsol_ode_new_external_dynamic(
1653 missing_path.as_ptr(),
1654 matrix_type_to_i32(MatrixType::NalgebraDense),
1655 linear_solver_to_i32(LinearSolverType::Default),
1656 ode_solver_to_i32(OdeSolverType::Bdf),
1657 ptr::null(),
1658 0,
1659 ptr::null(),
1660 0,
1661 ptr::null(),
1662 0,
1663 );
1664 assert!(ode.is_null());
1665 assert_last_error_set();
1666 }
1667 }
1668}
1669
1670#[cfg(all(test, any(feature = "diffsl-cranelift", feature = "diffsl-llvm")))]
1671mod jit_tests {
1672 use std::ffi::{CStr, CString};
1673 use std::ptr;
1674
1675 use crate::error_c::{diffsol_error_code, diffsol_last_error_message};
1676 use crate::initial_condition_options_c::diffsol_ic_options_free;
1677 use crate::jit::JitBackendType;
1678 use crate::jit_c::jit_backend_to_i32;
1679 use crate::linear_solver_type::LinearSolverType;
1680 use crate::linear_solver_type_c::linear_solver_to_i32;
1681 use crate::matrix_type::MatrixType;
1682 use crate::matrix_type_c::matrix_type_to_i32;
1683 use crate::ode_options_c::diffsol_ode_options_free;
1684 use crate::ode_solver_type::OdeSolverType;
1685 use crate::ode_solver_type_c::ode_solver_to_i32;
1686 #[cfg(feature = "diffsl-llvm")]
1687 use crate::solution_wrapper_c::diffsol_solution_wrapper_get_sens;
1688 use crate::solution_wrapper_c::{
1689 diffsol_solution_wrapper_get_ts, diffsol_solution_wrapper_get_ys,
1690 };
1691 #[cfg(feature = "diffsl-llvm")]
1692 use crate::test_support::ffi_read_host_array_list_matrices;
1693 use crate::test_support::{
1694 assert_close, available_jit_backends, clear_last_error, ffi_free_solution,
1695 ffi_read_host_array_matrix, ffi_read_host_array_vector, find_time_window,
1696 hybrid_logistic_diffsl_code, hybrid_logistic_state, logistic_diffsl_code_cstring,
1697 logistic_state, ASSERT_TOL, LOGISTIC_X0,
1698 };
1699 #[cfg(feature = "diffsl-llvm")]
1700 use crate::test_support::{hybrid_logistic_state_dr, logistic_state_dr};
1701
1702 use super::*;
1703
1704 unsafe fn make_ode_ptr(
1705 jit_backend: JitBackendType,
1706 matrix_type: i32,
1707 linear_solver: i32,
1708 ode_solver: i32,
1709 ) -> *mut OdeWrapper {
1710 let code = logistic_diffsl_code_cstring();
1711 unsafe {
1712 make_ode_ptr_with_code(
1713 jit_backend,
1714 code.as_ptr(),
1715 matrix_type,
1716 linear_solver,
1717 ode_solver,
1718 )
1719 }
1720 }
1721
1722 unsafe fn make_ode_ptr_with_code(
1723 jit_backend: JitBackendType,
1724 code: *const std::os::raw::c_char,
1725 matrix_type: i32,
1726 linear_solver: i32,
1727 ode_solver: i32,
1728 ) -> *mut OdeWrapper {
1729 unsafe {
1730 diffsol_ode_new_jit(
1731 code,
1732 jit_backend_to_i32(jit_backend),
1733 matrix_type,
1734 linear_solver,
1735 ode_solver,
1736 )
1737 }
1738 }
1739
1740 unsafe fn last_error_message() -> String {
1741 let ptr = unsafe { diffsol_last_error_message() };
1742 assert_eq!(unsafe { diffsol_error_code() }, 1);
1743 assert!(!ptr.is_null());
1744 unsafe { CStr::from_ptr(ptr) }.to_str().unwrap().to_owned()
1745 }
1746
1747 #[test]
1748 fn c_api_full_lifecycle_matches_jit_logistic_model() {
1749 clear_last_error();
1750 for jit_backend in available_jit_backends() {
1751 unsafe {
1752 let ode = make_ode_ptr(
1753 jit_backend,
1754 matrix_type_to_i32(MatrixType::NalgebraDense),
1755 linear_solver_to_i32(LinearSolverType::Default),
1756 ode_solver_to_i32(OdeSolverType::Bdf),
1757 );
1758 assert!(!ode.is_null());
1759
1760 assert_eq!(
1761 diffsol_ode_get_matrix_type(ode),
1762 matrix_type_to_i32(MatrixType::NalgebraDense)
1763 );
1764 assert_eq!(
1765 diffsol_ode_get_ode_solver(ode),
1766 ode_solver_to_i32(OdeSolverType::Bdf)
1767 );
1768 assert_eq!(
1769 diffsol_ode_get_linear_solver(ode),
1770 linear_solver_to_i32(LinearSolverType::Default)
1771 );
1772
1773 let params = [2.0f64];
1774 let y = [0.25f64];
1775 let v = [3.0f64];
1776
1777 let mut y0_ptr = ptr::null_mut();
1778 assert_eq!(
1779 diffsol_ode_y0(ode, params.as_ptr(), params.len(), &mut y0_ptr),
1780 DIFFSOL_OK
1781 );
1782 assert_eq!(ffi_read_host_array_vector(y0_ptr), vec![LOGISTIC_X0]);
1783
1784 let mut rhs_ptr = ptr::null_mut();
1785 assert_eq!(
1786 diffsol_ode_rhs(
1787 ode,
1788 params.as_ptr(),
1789 params.len(),
1790 0.0,
1791 y.as_ptr(),
1792 y.len(),
1793 &mut rhs_ptr,
1794 ),
1795 DIFFSOL_OK
1796 );
1797 assert_close(
1798 ffi_read_host_array_vector(rhs_ptr)[0],
1799 0.375,
1800 ASSERT_TOL,
1801 "jit ffi rhs",
1802 );
1803
1804 let mut rhs_jac_mul_ptr = ptr::null_mut();
1805 assert_eq!(
1806 diffsol_ode_rhs_jac_mul(
1807 ode,
1808 params.as_ptr(),
1809 params.len(),
1810 0.0,
1811 y.as_ptr(),
1812 y.len(),
1813 v.as_ptr(),
1814 v.len(),
1815 &mut rhs_jac_mul_ptr,
1816 ),
1817 DIFFSOL_OK
1818 );
1819 assert_close(
1820 ffi_read_host_array_vector(rhs_jac_mul_ptr)[0],
1821 3.0,
1822 ASSERT_TOL,
1823 "jit ffi rhs_jac_mul",
1824 );
1825
1826 let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1827 let t_eval = [0.25f64, 0.5f64, 1.0f64];
1828 assert_eq!(
1829 diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
1830 DIFFSOL_OK
1831 );
1832 assert_eq!(
1833 diffsol_ode_solve_dense(
1834 ode,
1835 params.as_ptr(),
1836 params.len(),
1837 t_eval.as_ptr(),
1838 t_eval.len(),
1839 &mut solution_ptr,
1840 ),
1841 DIFFSOL_OK
1842 );
1843 let mut ys_ptr = ptr::null_mut();
1844 let mut ts_ptr = ptr::null_mut();
1845 assert_eq!(
1846 diffsol_solution_wrapper_get_ys(solution_ptr, &mut ys_ptr),
1847 DIFFSOL_OK
1848 );
1849 assert_eq!(
1850 diffsol_solution_wrapper_get_ts(solution_ptr, &mut ts_ptr),
1851 DIFFSOL_OK
1852 );
1853 let (rows, cols, ys) = ffi_read_host_array_matrix(ys_ptr);
1854 let ts = ffi_read_host_array_vector(ts_ptr);
1855 assert_eq!(rows, 1);
1856 assert_eq!(cols, ts.len());
1857 let start = find_time_window(&ts, &t_eval, ASSERT_TOL);
1858 for (i, &t) in t_eval.iter().enumerate() {
1859 assert_close(ts[start + i], t, ASSERT_TOL, "jit ffi solution time");
1860 assert_close(
1861 ys[start + i],
1862 logistic_state(LOGISTIC_X0, 2.0, t),
1863 5e-4,
1864 "jit ffi solution value",
1865 );
1866 }
1867 assert_eq!(
1868 diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Bdf)),
1869 DIFFSOL_OK
1870 );
1871
1872 #[cfg(feature = "diffsl-llvm")]
1873 {
1874 let analysis_code = logistic_diffsl_code_cstring();
1875 let analysis_ode = make_ode_ptr_with_code(
1876 JitBackendType::Llvm,
1877 analysis_code.as_ptr(),
1878 matrix_type_to_i32(MatrixType::NalgebraDense),
1879 linear_solver_to_i32(LinearSolverType::Default),
1880 ode_solver_to_i32(OdeSolverType::Bdf),
1881 );
1882 assert!(!analysis_ode.is_null());
1883
1884 let mut sens_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
1885 assert_eq!(
1886 diffsol_ode_solve_fwd_sens(
1887 analysis_ode,
1888 params.as_ptr(),
1889 params.len(),
1890 t_eval.as_ptr(),
1891 t_eval.len(),
1892 &mut sens_solution_ptr,
1893 ),
1894 DIFFSOL_OK
1895 );
1896 let mut sens_list = ptr::null_mut();
1897 let mut sens_len = 0usize;
1898 assert_eq!(
1899 diffsol_solution_wrapper_get_sens(
1900 sens_solution_ptr,
1901 &mut sens_list,
1902 &mut sens_len
1903 ),
1904 DIFFSOL_OK
1905 );
1906 let sens_values = ffi_read_host_array_list_matrices(sens_list, sens_len);
1907 assert_eq!(sens_values.len(), 1);
1908 assert_eq!(sens_values[0].0, 1);
1909 assert_eq!(sens_values[0].1, t_eval.len());
1910 for (i, (&value, &t)) in sens_values[0].2.iter().zip(t_eval.iter()).enumerate()
1911 {
1912 assert_close(
1913 value,
1914 logistic_state_dr(LOGISTIC_X0, 2.0, t),
1915 ASSERT_TOL,
1916 &format!("jit ffi sensitivity[{i}]"),
1917 );
1918 }
1919
1920 let adjoint_t_eval = [0.0f64, 0.25f64, 0.5f64, 1.0f64];
1921 let adjoint_data: Vec<f64> = adjoint_t_eval
1922 .iter()
1923 .map(|&t| logistic_state(LOGISTIC_X0, 2.0, t))
1924 .collect();
1925 let mut objective = 0.0;
1926 let mut adjoint_grad_ptr = ptr::null_mut();
1927 assert_eq!(
1928 diffsol_ode_solve_sum_squares_adj(
1929 analysis_ode,
1930 params.as_ptr(),
1931 params.len(),
1932 adjoint_data.as_ptr(),
1933 1,
1934 adjoint_t_eval.len(),
1935 1,
1936 1,
1937 adjoint_t_eval.as_ptr(),
1938 adjoint_t_eval.len(),
1939 &mut objective,
1940 &mut adjoint_grad_ptr,
1941 ),
1942 DIFFSOL_OK
1943 );
1944 assert_close(objective, 0.0, ASSERT_TOL, "jit ffi adjoint objective");
1945 let grad = ffi_read_host_array_vector(adjoint_grad_ptr);
1946 assert_eq!(grad.len(), 1);
1947 assert!(
1948 grad[0].is_finite(),
1949 "jit ffi adjoint gradient should be finite"
1950 );
1951
1952 ffi_free_solution(sens_solution_ptr);
1953 diffsol_ode_free(analysis_ode);
1954 }
1955 ffi_free_solution(solution_ptr);
1956 diffsol_ode_free(ode);
1957 }
1958 }
1959 }
1960
1961 #[test]
1962 fn c_api_rejects_invalid_jit_arguments() {
1963 unsafe {
1964 clear_last_error();
1965 assert!(diffsol_ode_new_jit(
1966 ptr::null(),
1967 jit_backend_to_i32(available_jit_backends()[0]),
1968 matrix_type_to_i32(MatrixType::NalgebraDense),
1969 linear_solver_to_i32(LinearSolverType::Default),
1970 ode_solver_to_i32(OdeSolverType::Bdf),
1971 )
1972 .is_null());
1973 assert!(last_error_message().contains("code is null"));
1974
1975 clear_last_error();
1976 let invalid_utf8 = CString::from_vec_with_nul(vec![0xff, 0]).unwrap();
1977 assert!(diffsol_ode_new_jit(
1978 invalid_utf8.as_ptr(),
1979 jit_backend_to_i32(available_jit_backends()[0]),
1980 matrix_type_to_i32(MatrixType::NalgebraDense),
1981 linear_solver_to_i32(LinearSolverType::Default),
1982 ode_solver_to_i32(OdeSolverType::Bdf),
1983 )
1984 .is_null());
1985 assert!(last_error_message().contains("valid UTF-8"));
1986
1987 clear_last_error();
1988 let code = logistic_diffsl_code_cstring();
1989 assert!(diffsol_ode_new_jit(
1990 code.as_ptr(),
1991 99,
1992 matrix_type_to_i32(MatrixType::NalgebraDense),
1993 linear_solver_to_i32(LinearSolverType::Default),
1994 ode_solver_to_i32(OdeSolverType::Bdf),
1995 )
1996 .is_null());
1997 assert!(last_error_message().contains("invalid jit_backend_type"));
1998
1999 clear_last_error();
2000 assert!(diffsol_ode_new_jit(
2001 code.as_ptr(),
2002 jit_backend_to_i32(available_jit_backends()[0]),
2003 99,
2004 linear_solver_to_i32(LinearSolverType::Default),
2005 ode_solver_to_i32(OdeSolverType::Bdf),
2006 )
2007 .is_null());
2008 assert!(last_error_message().contains("invalid matrix_type"));
2009
2010 clear_last_error();
2011 assert!(diffsol_ode_new_jit(
2012 code.as_ptr(),
2013 jit_backend_to_i32(available_jit_backends()[0]),
2014 matrix_type_to_i32(MatrixType::NalgebraDense),
2015 99,
2016 ode_solver_to_i32(OdeSolverType::Bdf),
2017 )
2018 .is_null());
2019 assert!(last_error_message().contains("invalid linear_solver"));
2020
2021 clear_last_error();
2022 assert!(diffsol_ode_new_jit(
2023 code.as_ptr(),
2024 jit_backend_to_i32(available_jit_backends()[0]),
2025 matrix_type_to_i32(MatrixType::NalgebraDense),
2026 linear_solver_to_i32(LinearSolverType::Default),
2027 99,
2028 )
2029 .is_null());
2030 assert!(last_error_message().contains("invalid ode_solver"));
2031
2032 clear_last_error();
2033 let invalid_code = CString::new("not valid diffsl").unwrap();
2034 assert!(diffsol_ode_new_jit(
2035 invalid_code.as_ptr(),
2036 jit_backend_to_i32(available_jit_backends()[0]),
2037 matrix_type_to_i32(MatrixType::NalgebraDense),
2038 linear_solver_to_i32(LinearSolverType::Default),
2039 ode_solver_to_i32(OdeSolverType::Bdf),
2040 )
2041 .is_null());
2042 assert!(diffsol_error_code() != 0);
2043
2044 let mut ic_options = ptr::null_mut();
2045 assert_eq!(
2046 diffsol_ode_get_ic_options(ptr::null_mut(), &mut ic_options),
2047 DIFFSOL_BAD_ARG
2048 );
2049 let mut ode_options = ptr::null_mut();
2050 assert_eq!(
2051 diffsol_ode_get_options(ptr::null_mut(), &mut ode_options),
2052 DIFFSOL_BAD_ARG
2053 );
2054
2055 let mut out_array = ptr::null_mut();
2056 assert_eq!(
2057 diffsol_ode_y0(ptr::null_mut(), ptr::null(), 0, &mut out_array),
2058 DIFFSOL_BAD_ARG
2059 );
2060 assert_eq!(
2061 diffsol_ode_rhs(
2062 ptr::null_mut(),
2063 ptr::null(),
2064 0,
2065 0.0,
2066 ptr::null(),
2067 0,
2068 &mut out_array,
2069 ),
2070 DIFFSOL_BAD_ARG
2071 );
2072 assert_eq!(
2073 diffsol_ode_rhs_jac_mul(
2074 ptr::null_mut(),
2075 ptr::null(),
2076 0,
2077 0.0,
2078 ptr::null(),
2079 0,
2080 ptr::null(),
2081 0,
2082 &mut out_array,
2083 ),
2084 DIFFSOL_BAD_ARG
2085 );
2086
2087 clear_last_error();
2088 diffsol_ode_free(ptr::null_mut());
2089 assert!(last_error_message().contains("ode is null"));
2090
2091 clear_last_error();
2092 diffsol_host_array_list_free(ptr::null_mut(), 0);
2093 assert!(last_error_message().contains("host array list is null"));
2094 }
2095 }
2096
2097 #[test]
2098 fn c_api_jit_wrapper_branches_cover_runtime_success_and_errors() {
2099 for jit_backend in available_jit_backends() {
2100 unsafe {
2101 let ode = make_ode_ptr(
2102 jit_backend,
2103 matrix_type_to_i32(MatrixType::NalgebraDense),
2104 linear_solver_to_i32(LinearSolverType::Default),
2105 ode_solver_to_i32(OdeSolverType::Bdf),
2106 );
2107 assert!(!ode.is_null());
2108
2109 let mut ic_options = ptr::null_mut();
2110 let mut ode_options = ptr::null_mut();
2111 assert_eq!(diffsol_ode_get_ic_options(ode, &mut ic_options), DIFFSOL_OK);
2112 assert_eq!(diffsol_ode_get_options(ode, &mut ode_options), DIFFSOL_OK);
2113 diffsol_ic_options_free(ic_options);
2114 diffsol_ode_options_free(ode_options);
2115
2116 let mut out_value = 0.0;
2117 assert_eq!(diffsol_ode_get_rtol(ode, &mut out_value), DIFFSOL_OK);
2118 assert_close(out_value, 1e-6, ASSERT_TOL, "jit ffi default rtol");
2119 assert_eq!(diffsol_ode_set_rtol(ode, 1e-4), DIFFSOL_OK);
2120 assert_eq!(diffsol_ode_get_rtol(ode, &mut out_value), DIFFSOL_OK);
2121 assert_close(out_value, 1e-4, ASSERT_TOL, "jit ffi updated rtol");
2122
2123 assert_eq!(diffsol_ode_get_atol(ode, &mut out_value), DIFFSOL_OK);
2124 assert_close(out_value, 1e-6, ASSERT_TOL, "jit ffi default atol");
2125 assert_eq!(diffsol_ode_set_atol(ode, 1e-5), DIFFSOL_OK);
2126 assert_eq!(diffsol_ode_get_atol(ode, &mut out_value), DIFFSOL_OK);
2127 assert_close(out_value, 1e-5, ASSERT_TOL, "jit ffi updated atol");
2128
2129 assert_eq!(
2130 diffsol_ode_set_linear_solver(ode, linear_solver_to_i32(LinearSolverType::Lu)),
2131 DIFFSOL_OK
2132 );
2133 assert_eq!(
2134 diffsol_ode_get_linear_solver(ode),
2135 linear_solver_to_i32(LinearSolverType::Lu)
2136 );
2137 assert_eq!(
2138 diffsol_ode_set_ode_solver(ode, ode_solver_to_i32(OdeSolverType::Tsit45)),
2139 DIFFSOL_OK
2140 );
2141 assert_eq!(
2142 diffsol_ode_get_ode_solver(ode),
2143 ode_solver_to_i32(OdeSolverType::Tsit45)
2144 );
2145 assert_eq!(
2146 diffsol_ode_get_matrix_type(ode),
2147 matrix_type_to_i32(MatrixType::NalgebraDense)
2148 );
2149
2150 let params = [2.0f64];
2151 let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2152 assert_eq!(
2153 diffsol_ode_solve(ode, params.as_ptr(), params.len(), 1.0, &mut solution_ptr),
2154 DIFFSOL_OK
2155 );
2156 ffi_free_solution(solution_ptr);
2157
2158 let t_eval = [0.25f64, 0.5f64, 1.0f64];
2159 let mut dense_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2160 assert_eq!(
2161 diffsol_ode_solve_dense(
2162 ode,
2163 params.as_ptr(),
2164 params.len(),
2165 t_eval.as_ptr(),
2166 t_eval.len(),
2167 &mut dense_solution_ptr,
2168 ),
2169 DIFFSOL_OK
2170 );
2171 ffi_free_solution(dense_solution_ptr);
2172
2173 let no_params: [f64; 0] = [];
2174 let y = [0.25f64];
2175 let v = [3.0f64];
2176 let mut out_array = ptr::null_mut();
2177 assert_eq!(
2178 diffsol_ode_y0(ode, no_params.as_ptr(), no_params.len(), &mut out_array),
2179 DIFFSOL_ERR
2180 );
2181 assert_eq!(
2182 diffsol_ode_rhs(
2183 ode,
2184 no_params.as_ptr(),
2185 no_params.len(),
2186 0.0,
2187 y.as_ptr(),
2188 y.len(),
2189 &mut out_array,
2190 ),
2191 DIFFSOL_ERR
2192 );
2193 assert_eq!(
2194 diffsol_ode_rhs_jac_mul(
2195 ode,
2196 no_params.as_ptr(),
2197 no_params.len(),
2198 0.0,
2199 y.as_ptr(),
2200 y.len(),
2201 v.as_ptr(),
2202 v.len(),
2203 &mut out_array,
2204 ),
2205 DIFFSOL_ERR
2206 );
2207
2208 let mut err_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2209 assert_eq!(
2210 diffsol_ode_solve(
2211 ode,
2212 no_params.as_ptr(),
2213 no_params.len(),
2214 1.0,
2215 &mut err_solution_ptr,
2216 ),
2217 DIFFSOL_ERR
2218 );
2219 assert_eq!(
2220 diffsol_ode_solve_hybrid(
2221 ode,
2222 no_params.as_ptr(),
2223 no_params.len(),
2224 1.0,
2225 &mut err_solution_ptr,
2226 ),
2227 DIFFSOL_ERR
2228 );
2229 assert_eq!(
2230 diffsol_ode_solve_dense(
2231 ode,
2232 no_params.as_ptr(),
2233 no_params.len(),
2234 t_eval.as_ptr(),
2235 t_eval.len(),
2236 &mut err_solution_ptr,
2237 ),
2238 DIFFSOL_ERR
2239 );
2240 assert_eq!(
2241 diffsol_ode_solve_hybrid_dense(
2242 ode,
2243 no_params.as_ptr(),
2244 no_params.len(),
2245 t_eval.as_ptr(),
2246 t_eval.len(),
2247 &mut err_solution_ptr,
2248 ),
2249 DIFFSOL_ERR
2250 );
2251
2252 #[cfg(feature = "diffsl-llvm")]
2253 if matches!(jit_backend, JitBackendType::Llvm) {
2254 assert_eq!(
2255 diffsol_ode_solve_fwd_sens(
2256 ode,
2257 no_params.as_ptr(),
2258 no_params.len(),
2259 t_eval.as_ptr(),
2260 t_eval.len(),
2261 &mut err_solution_ptr,
2262 ),
2263 DIFFSOL_ERR
2264 );
2265 assert_eq!(
2266 diffsol_ode_solve_hybrid_fwd_sens(
2267 ode,
2268 no_params.as_ptr(),
2269 no_params.len(),
2270 t_eval.as_ptr(),
2271 t_eval.len(),
2272 &mut err_solution_ptr,
2273 ),
2274 DIFFSOL_ERR
2275 );
2276
2277 let adjoint_data: Vec<f64> = t_eval
2278 .iter()
2279 .map(|&t| logistic_state(LOGISTIC_X0, 2.0, t))
2280 .collect();
2281 let mut objective = 0.0;
2282 let mut sens_ptr = ptr::null_mut();
2283 assert_eq!(
2284 diffsol_ode_solve_sum_squares_adj(
2285 ode,
2286 no_params.as_ptr(),
2287 no_params.len(),
2288 adjoint_data.as_ptr(),
2289 1,
2290 t_eval.len(),
2291 1,
2292 1,
2293 t_eval.as_ptr(),
2294 t_eval.len(),
2295 &mut objective,
2296 &mut sens_ptr,
2297 ),
2298 DIFFSOL_ERR
2299 );
2300 }
2301
2302 assert_eq!(diffsol_ode_get_matrix_type(ptr::null()), -1);
2303 assert_eq!(diffsol_ode_get_ode_solver(ptr::null()), -1);
2304 assert_eq!(diffsol_ode_get_linear_solver(ptr::null()), -1);
2305 assert_eq!(
2306 diffsol_ode_set_ode_solver(ptr::null_mut(), 0),
2307 DIFFSOL_BAD_ARG
2308 );
2309 assert_eq!(
2310 diffsol_ode_set_linear_solver(ptr::null_mut(), 0),
2311 DIFFSOL_BAD_ARG
2312 );
2313 assert_eq!(diffsol_ode_set_ode_solver(ode, 99), DIFFSOL_BAD_ARG);
2314 assert_eq!(diffsol_ode_set_linear_solver(ode, 99), DIFFSOL_BAD_ARG);
2315 assert_eq!(
2316 diffsol_ode_get_rtol(ptr::null(), &mut out_value),
2317 DIFFSOL_BAD_ARG
2318 );
2319 assert_eq!(diffsol_ode_get_rtol(ode, ptr::null_mut()), DIFFSOL_BAD_ARG);
2320 assert_eq!(diffsol_ode_set_rtol(ptr::null_mut(), 1e-3), DIFFSOL_BAD_ARG);
2321 assert_eq!(
2322 diffsol_ode_get_atol(ptr::null(), &mut out_value),
2323 DIFFSOL_BAD_ARG
2324 );
2325 assert_eq!(diffsol_ode_get_atol(ode, ptr::null_mut()), DIFFSOL_BAD_ARG);
2326 assert_eq!(diffsol_ode_set_atol(ptr::null_mut(), 1e-3), DIFFSOL_BAD_ARG);
2327 assert_eq!(
2328 diffsol_ode_solve(ode, params.as_ptr(), params.len(), 1.0, ptr::null_mut()),
2329 DIFFSOL_BAD_ARG
2330 );
2331 assert_eq!(
2332 diffsol_ode_solve_hybrid(
2333 ode,
2334 params.as_ptr(),
2335 params.len(),
2336 1.0,
2337 ptr::null_mut(),
2338 ),
2339 DIFFSOL_BAD_ARG
2340 );
2341 assert_eq!(
2342 diffsol_ode_solve_dense(
2343 ode,
2344 params.as_ptr(),
2345 params.len(),
2346 t_eval.as_ptr(),
2347 t_eval.len(),
2348 ptr::null_mut(),
2349 ),
2350 DIFFSOL_BAD_ARG
2351 );
2352 assert_eq!(
2353 diffsol_ode_solve_hybrid_dense(
2354 ode,
2355 params.as_ptr(),
2356 params.len(),
2357 t_eval.as_ptr(),
2358 t_eval.len(),
2359 ptr::null_mut(),
2360 ),
2361 DIFFSOL_BAD_ARG
2362 );
2363 #[cfg(feature = "diffsl-llvm")]
2364 if matches!(jit_backend, JitBackendType::Llvm) {
2365 assert_eq!(
2366 diffsol_ode_solve_fwd_sens(
2367 ode,
2368 params.as_ptr(),
2369 params.len(),
2370 t_eval.as_ptr(),
2371 t_eval.len(),
2372 ptr::null_mut(),
2373 ),
2374 DIFFSOL_BAD_ARG
2375 );
2376 assert_eq!(
2377 diffsol_ode_solve_hybrid_fwd_sens(
2378 ode,
2379 params.as_ptr(),
2380 params.len(),
2381 t_eval.as_ptr(),
2382 t_eval.len(),
2383 ptr::null_mut(),
2384 ),
2385 DIFFSOL_BAD_ARG
2386 );
2387 let mut objective = 0.0;
2388 let mut sens_ptr = ptr::null_mut();
2389 assert_eq!(
2390 diffsol_ode_solve_sum_squares_adj(
2391 ode,
2392 params.as_ptr(),
2393 params.len(),
2394 t_eval.as_ptr(),
2395 1,
2396 t_eval.len(),
2397 1,
2398 1,
2399 t_eval.as_ptr(),
2400 t_eval.len(),
2401 ptr::null_mut(),
2402 &mut sens_ptr,
2403 ),
2404 DIFFSOL_BAD_ARG
2405 );
2406 assert_eq!(
2407 diffsol_ode_solve_sum_squares_adj(
2408 ode,
2409 params.as_ptr(),
2410 params.len(),
2411 t_eval.as_ptr(),
2412 1,
2413 t_eval.len(),
2414 1,
2415 1,
2416 t_eval.as_ptr(),
2417 t_eval.len(),
2418 &mut objective,
2419 ptr::null_mut(),
2420 ),
2421 DIFFSOL_BAD_ARG
2422 );
2423 }
2424
2425 diffsol_ode_free(ode);
2426 }
2427 }
2428 }
2429
2430 #[test]
2431 fn c_api_hybrid_jit_solver_paths_match_expected_values() {
2432 for jit_backend in available_jit_backends() {
2433 unsafe {
2434 let code = CString::new(hybrid_logistic_diffsl_code()).unwrap();
2435 let ode = make_ode_ptr_with_code(
2436 jit_backend,
2437 code.as_ptr(),
2438 matrix_type_to_i32(MatrixType::NalgebraDense),
2439 linear_solver_to_i32(LinearSolverType::Default),
2440 ode_solver_to_i32(OdeSolverType::Bdf),
2441 );
2442 assert!(!ode.is_null());
2443
2444 let params = [2.0f64];
2445 let mut solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2446 assert_eq!(
2447 diffsol_ode_solve_hybrid(
2448 ode,
2449 params.as_ptr(),
2450 params.len(),
2451 2.0,
2452 &mut solution_ptr
2453 ),
2454 DIFFSOL_OK
2455 );
2456 let mut ys_ptr = ptr::null_mut();
2457 let mut ts_ptr = ptr::null_mut();
2458 assert_eq!(
2459 diffsol_solution_wrapper_get_ys(solution_ptr, &mut ys_ptr),
2460 DIFFSOL_OK
2461 );
2462 assert_eq!(
2463 diffsol_solution_wrapper_get_ts(solution_ptr, &mut ts_ptr),
2464 DIFFSOL_OK
2465 );
2466 let (_rows, cols, ys) = ffi_read_host_array_matrix(ys_ptr);
2467 let ts = ffi_read_host_array_vector(ts_ptr);
2468 assert!(cols >= 1);
2469 assert_close(*ts.last().unwrap(), 2.0, 5e-4, "jit hybrid solve time");
2470 assert_close(
2471 *ys.last().unwrap(),
2472 hybrid_logistic_state(2.0, 2.0),
2473 5e-4,
2474 "jit hybrid solve value",
2475 );
2476 ffi_free_solution(solution_ptr);
2477
2478 #[cfg(feature = "diffsl-llvm")]
2479 if matches!(jit_backend, JitBackendType::Llvm) {
2480 let t_eval = [0.25f64, 0.5f64, 1.0f64];
2481 let mut sens_solution_ptr: *mut SolutionWrapper = ptr::null_mut();
2482 assert_eq!(
2483 diffsol_ode_solve_hybrid_fwd_sens(
2484 ode,
2485 params.as_ptr(),
2486 params.len(),
2487 t_eval.as_ptr(),
2488 t_eval.len(),
2489 &mut sens_solution_ptr,
2490 ),
2491 DIFFSOL_OK
2492 );
2493 let mut sens_list = ptr::null_mut();
2494 let mut sens_len = 0usize;
2495 assert_eq!(
2496 diffsol_solution_wrapper_get_sens(
2497 sens_solution_ptr,
2498 &mut sens_list,
2499 &mut sens_len
2500 ),
2501 DIFFSOL_OK
2502 );
2503 let sens_values = ffi_read_host_array_list_matrices(sens_list, sens_len);
2504 for (i, (&value, &t)) in sens_values[0].2.iter().zip(t_eval.iter()).enumerate()
2505 {
2506 assert_close(
2507 value,
2508 hybrid_logistic_state_dr(2.0, t),
2509 5e-4,
2510 &format!("jit hybrid sensitivity[{i}]"),
2511 );
2512 }
2513 ffi_free_solution(sens_solution_ptr);
2514 }
2515
2516 diffsol_ode_free(ode);
2517 }
2518 }
2519 }
2520}