1use std::{
96 cell::{Cell, RefCell},
97 ffi::c_void,
98 fmt,
99};
100
101use diffsol::{
102 error::DiffsolError,
103 jacobian::{
104 find_adjoint_non_zeros, find_constant_sens_non_zeros, find_jacobian_non_zeros,
105 find_matrix_non_zeros, find_sens_adjoint_non_zeros, find_sens_non_zeros, JacobianColoring,
106 },
107 matrix::sparsity::MatrixSparsity,
108 op::OpStatistics,
109 ConstantOp, ConstantOpSens, ConstantOpSensAdjoint, LinearOp, LinearOpTranspose, Matrix,
110 NonLinearOp, NonLinearOpAdjoint, NonLinearOpJacobian, NonLinearOpSens, NonLinearOpSensAdjoint,
111 OdeEquations, OdeEquationsRef, Op, Scalar, Vector,
112};
113
114use num_traits::Zero;
115
116use crate::{
117 error::DiffsolRtError,
118 linear_solver_type::LinearSolverType,
119 matrix_type::MatrixType,
120 ode::OdeWrapper,
121 ode_solver_type::OdeSolverType,
122 scalar_type::ToScalarType,
123 solve::{GenericSolve, Solve},
124 solve_eqn::{EqnRequirement, SolveEqn},
125};
126
127pub type OdeCallbackFn<T> = Option<
132 unsafe extern "C" fn(x: *const T, p: *const T, t: T, y: *mut T, user_data: *mut c_void) -> i32,
133>;
134
135pub type OdeCallbackMulFn<T> = Option<
138 unsafe extern "C" fn(
139 x: *const T,
140 p: *const T,
141 t: T,
142 v: *const T,
143 y: *mut T,
144 user_data: *mut c_void,
145 ) -> i32,
146>;
147
148pub type OdeCallbackInitFn<T> =
150 Option<unsafe extern "C" fn(p: *const T, t: T, y: *mut T, user_data: *mut c_void) -> i32>;
151
152pub type OdeCallbackInitMulFn<T> = Option<
154 unsafe extern "C" fn(p: *const T, t: T, v: *const T, y: *mut T, user_data: *mut c_void) -> i32,
155>;
156
157pub type OdeCallbackMassFn<T> = Option<
159 unsafe extern "C" fn(
160 v: *const T,
161 p: *const T,
162 t: T,
163 beta: T,
164 y: *mut T,
165 user_data: *mut c_void,
166 ) -> i32,
167>;
168
169pub type OdeCallbackFreeFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
171
172#[repr(C)]
178#[derive(Clone, Copy, Debug)]
179pub struct OdeCallbacksDesc<T> {
180 pub nstates: usize,
182 pub nparams: usize,
184 pub nout: usize,
186 pub nroots: usize,
188 pub user_data: *mut c_void,
190 pub free_user_data: OdeCallbackFreeFn,
192
193 pub rhs: OdeCallbackFn<T>,
194 pub rhs_jac: OdeCallbackMulFn<T>,
195 pub rhs_sens: OdeCallbackMulFn<T>,
196 pub rhs_adjoint: OdeCallbackMulFn<T>,
197 pub rhs_sens_adjoint: OdeCallbackMulFn<T>,
198
199 pub init: OdeCallbackInitFn<T>,
200 pub init_sens: OdeCallbackInitMulFn<T>,
201 pub init_sens_adjoint: OdeCallbackInitMulFn<T>,
202
203 pub mass: OdeCallbackMassFn<T>,
204 pub mass_adjoint: OdeCallbackMassFn<T>,
205
206 pub root: OdeCallbackFn<T>,
207 pub root_jac: OdeCallbackMulFn<T>,
208 pub root_sens: OdeCallbackMulFn<T>,
209 pub root_adjoint: OdeCallbackMulFn<T>,
210 pub root_sens_adjoint: OdeCallbackMulFn<T>,
211
212 pub out: OdeCallbackFn<T>,
213 pub out_jac: OdeCallbackMulFn<T>,
214 pub out_sens: OdeCallbackMulFn<T>,
215 pub out_adjoint: OdeCallbackMulFn<T>,
216 pub out_sens_adjoint: OdeCallbackMulFn<T>,
217
218 pub reset: OdeCallbackFn<T>,
219 pub reset_jac: OdeCallbackMulFn<T>,
220 pub reset_sens: OdeCallbackMulFn<T>,
221 pub reset_adjoint: OdeCallbackMulFn<T>,
222 pub reset_sens_adjoint: OdeCallbackMulFn<T>,
223}
224
225unsafe impl<T: Send> Send for OdeCallbacksDesc<T> {}
231
232pub type DiffsolOdeCallbacksF64 = OdeCallbacksDesc<f64>;
234pub type DiffsolOdeCallbacksF32 = OdeCallbacksDesc<f32>;
236
237impl<T> OdeCallbacksDesc<T> {
238 pub fn new(
240 nstates: usize,
241 nparams: usize,
242 rhs: unsafe extern "C" fn(
243 x: *const T,
244 p: *const T,
245 t: T,
246 y: *mut T,
247 user_data: *mut c_void,
248 ) -> i32,
249 init: unsafe extern "C" fn(p: *const T, t: T, y: *mut T, user_data: *mut c_void) -> i32,
250 ) -> Self {
251 Self {
252 nstates,
253 nparams,
254 nout: 0,
255 nroots: 0,
256 user_data: std::ptr::null_mut(),
257 free_user_data: None,
258 rhs: Some(rhs),
259 rhs_jac: None,
260 rhs_sens: None,
261 rhs_adjoint: None,
262 rhs_sens_adjoint: None,
263 init: Some(init),
264 init_sens: None,
265 init_sens_adjoint: None,
266 mass: None,
267 mass_adjoint: None,
268 root: None,
269 root_jac: None,
270 root_sens: None,
271 root_adjoint: None,
272 root_sens_adjoint: None,
273 out: None,
274 out_jac: None,
275 out_sens: None,
276 out_adjoint: None,
277 out_sens_adjoint: None,
278 reset: None,
279 reset_jac: None,
280 reset_sens: None,
281 reset_adjoint: None,
282 reset_sens_adjoint: None,
283 }
284 }
285
286 fn validate(&self) -> Result<(), String> {
288 if self.rhs.is_none() {
289 return Err("the 'rhs' callback is required".to_string());
290 }
291 if self.init.is_none() {
292 return Err("the 'init' callback is required".to_string());
293 }
294 if self.nstates == 0 {
295 return Err("nstates must be greater than 0".to_string());
296 }
297 if self.root.is_some() != (self.nroots > 0) {
298 return Err(
299 "nroots must be greater than 0 exactly when the 'root' callback is provided"
300 .to_string(),
301 );
302 }
303 if self.out.is_some() != (self.nout > 0) {
304 return Err(
305 "nout must be greater than 0 exactly when the 'out' callback is provided"
306 .to_string(),
307 );
308 }
309 if self.reset.is_some() && self.root.is_none() {
310 return Err("the 'reset' callback requires the 'root' callback".to_string());
311 }
312 if self.mass_adjoint.is_some() && self.mass.is_none() {
313 return Err("the 'mass_adjoint' callback requires the 'mass' callback".to_string());
314 }
315 const SUFFIXES: [&str; 4] = ["jac", "sens", "adjoint", "sens_adjoint"];
317 let derived = [
318 (
319 "root",
320 self.root.is_some(),
321 [
322 self.root_jac.is_some(),
323 self.root_sens.is_some(),
324 self.root_adjoint.is_some(),
325 self.root_sens_adjoint.is_some(),
326 ],
327 ),
328 (
329 "out",
330 self.out.is_some(),
331 [
332 self.out_jac.is_some(),
333 self.out_sens.is_some(),
334 self.out_adjoint.is_some(),
335 self.out_sens_adjoint.is_some(),
336 ],
337 ),
338 (
339 "reset",
340 self.reset.is_some(),
341 [
342 self.reset_jac.is_some(),
343 self.reset_sens.is_some(),
344 self.reset_adjoint.is_some(),
345 self.reset_sens_adjoint.is_some(),
346 ],
347 ),
348 ];
349 for (base, has_base, has_derived) in derived {
350 for (suffix, has) in SUFFIXES.into_iter().zip(has_derived) {
351 if has && !has_base {
352 return Err(format!(
353 "the '{base}_{suffix}' callback requires the '{base}' callback"
354 ));
355 }
356 }
357 }
358 Ok(())
359 }
360
361 pub(crate) unsafe fn free_user_data(&self) {
366 if let Some(free) = self.free_user_data {
367 unsafe { free(self.user_data) };
368 }
369 }
370}
371
372pub trait CallbackScalar: crate::scalar_type::Scalar + sealed::Sealed {}
376
377mod sealed {
378 use crate::{
379 error::DiffsolRtError, linear_solver_type::LinearSolverType, matrix_type::MatrixType,
380 ode::OdeWrapper, ode_callbacks::OdeCallbacksDesc, ode_solver_type::OdeSolverType,
381 };
382
383 pub trait Sealed: Sized {
384 unsafe fn new_ode(
390 callbacks: OdeCallbacksDesc<Self>,
391 matrix_type: MatrixType,
392 linear_solver: LinearSolverType,
393 ode_solver: OdeSolverType,
394 ) -> Result<OdeWrapper, DiffsolRtError>;
395 }
396}
397
398macro_rules! solve_for_matrix_type {
402 ($t:ty, $callbacks:expr, $matrix_type:expr) => {{
403 let callbacks = $callbacks;
404 match $matrix_type {
406 MatrixType::NalgebraDense => {
407 let eqn = unsafe {
408 OdeCallbacks::<diffsol::NalgebraMat<$t>>::new(callbacks, Default::default())
409 };
410 Box::new(GenericSolve::from_eqn(eqn?)?) as Box<dyn Solve>
411 }
412 MatrixType::FaerDense => {
413 let eqn = unsafe {
414 OdeCallbacks::<diffsol::FaerMat<$t>>::new(callbacks, Default::default())
415 };
416 Box::new(GenericSolve::from_eqn(eqn?)?)
417 }
418 MatrixType::FaerSparse => {
419 let eqn = unsafe {
420 OdeCallbacks::<diffsol::FaerSparseMat<$t>>::new(callbacks, Default::default())
421 };
422 Box::new(GenericSolve::from_eqn(eqn?)?)
423 }
424 }
425 }};
426}
427
428macro_rules! impl_callback_scalar {
429 ($t:ty) => {
430 impl sealed::Sealed for $t {
431 unsafe fn new_ode(
432 callbacks: OdeCallbacksDesc<$t>,
433 matrix_type: MatrixType,
434 linear_solver: LinearSolverType,
435 ode_solver: OdeSolverType,
436 ) -> Result<OdeWrapper, DiffsolRtError> {
437 let solve = solve_for_matrix_type!($t, callbacks, matrix_type);
438 OdeWrapper::build(
439 String::new(),
440 <$t as ToScalarType>::scalar_type(),
441 solve,
442 None,
443 linear_solver,
444 ode_solver,
445 )
446 }
447 }
448
449 impl CallbackScalar for $t {}
450 };
451}
452
453impl_callback_scalar!(f64);
454impl_callback_scalar!(f32);
455
456impl OdeWrapper {
457 pub unsafe fn new_callbacks<T: CallbackScalar>(
469 callbacks: OdeCallbacksDesc<T>,
470 matrix_type: MatrixType,
471 linear_solver: LinearSolverType,
472 ode_solver: OdeSolverType,
473 ) -> Result<Self, DiffsolRtError> {
474 unsafe { T::new_ode(callbacks, matrix_type, linear_solver, ode_solver) }
476 }
477}
478
479#[derive(Clone, Copy, Debug, PartialEq, Eq)]
481pub enum OdeCallbackError {
482 Failed { callback: &'static str, code: i32 },
484 Missing { callback: &'static str },
486}
487
488impl fmt::Display for OdeCallbackError {
489 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
490 match self {
491 OdeCallbackError::Failed { callback, code } => {
492 write!(f, "callback '{callback}' returned error code {code}")
493 }
494 OdeCallbackError::Missing { callback } => {
495 f.write_str(&missing_message("evaluating the equations", callback))
496 }
497 }
498 }
499}
500
501fn missing_message(operation: &str, name: &str) -> String {
504 format!("OdeCallbacks: {operation} requires the '{name}' callback")
505}
506
507struct Pattern<M: Matrix> {
509 sparsity: M::Sparsity,
510 coloring: JacobianColoring<M>,
511}
512
513impl<M: Matrix> Pattern<M> {
514 fn new(nrows: usize, ncols: usize, non_zeros: Vec<(usize, usize)>, ctx: &M::C) -> Self {
515 let sparsity = MatrixSparsity::try_from_indices(nrows, ncols, non_zeros.clone())
516 .expect("invalid sparsity pattern");
517 let coloring = JacobianColoring::new(&sparsity, &non_zeros, ctx.clone());
518 Self { sparsity, coloring }
519 }
520}
521
522struct NonLinearPatterns<M: Matrix> {
524 jac: Option<Pattern<M>>,
525 adjoint: Option<Pattern<M>>,
526 sens: Option<Pattern<M>>,
527 sens_adjoint: Option<Pattern<M>>,
528}
529
530impl<M: Matrix> Default for NonLinearPatterns<M> {
531 fn default() -> Self {
532 Self {
533 jac: None,
534 adjoint: None,
535 sens: None,
536 sens_adjoint: None,
537 }
538 }
539}
540
541struct Patterns<M: Matrix> {
544 rhs: NonLinearPatterns<M>,
545 root: NonLinearPatterns<M>,
546 out: NonLinearPatterns<M>,
547 reset: NonLinearPatterns<M>,
548 init_sens: Option<Pattern<M>>,
549 mass: Option<Pattern<M>>,
550 mass_transpose: Option<Pattern<M>>,
551}
552
553impl<M: Matrix> Default for Patterns<M> {
554 fn default() -> Self {
555 Self {
556 rhs: NonLinearPatterns::default(),
557 root: NonLinearPatterns::default(),
558 out: NonLinearPatterns::default(),
559 reset: NonLinearPatterns::default(),
560 init_sens: None,
561 mass: None,
562 mass_transpose: None,
563 }
564 }
565}
566
567pub struct OdeCallbacks<M: Matrix> {
569 callbacks: OdeCallbacksDesc<M::T>,
570 p: M::V,
571 ctx: M::C,
572 patterns: Patterns<M>,
573 statistics: RefCell<OpStatistics>,
574 error: Cell<Option<OdeCallbackError>>,
575}
576
577impl<M: Matrix> Drop for OdeCallbacks<M> {
578 fn drop(&mut self) {
579 unsafe { self.callbacks.free_user_data() };
582 }
583}
584
585impl<M: Matrix> OdeCallbacks<M> {
586 pub unsafe fn new(callbacks: OdeCallbacksDesc<M::T>, ctx: M::C) -> Result<Self, DiffsolError> {
605 let mut eqn = Self {
606 p: M::V::zeros(callbacks.nparams, ctx.clone()),
607 callbacks,
608 ctx,
609 patterns: Patterns::default(),
610 statistics: RefCell::new(OpStatistics::default()),
611 error: Cell::new(None),
612 };
613 eqn.callbacks
615 .validate()
616 .map_err(|msg| DiffsolError::Other(format!("Invalid ODE callbacks: {msg}")))?;
617 if M::is_sparse() {
618 eqn.patterns = eqn.find_patterns();
619 if let Some(err) = eqn.take_callback_error() {
620 return Err(DiffsolError::Other(format!(
621 "Failed to find the sparsity pattern of the ODE callbacks: {err}"
622 )));
623 }
624 }
625 Ok(eqn)
626 }
627
628 pub fn take_callback_error(&self) -> Option<OdeCallbackError> {
630 self.error.take()
631 }
632
633 fn record(&self, err: OdeCallbackError, y: &mut [M::T]) {
634 if self.error.get().is_none() {
635 self.error.set(Some(err));
636 }
637 y.fill(M::T::NAN);
638 }
639
640 fn check_code(&self, callback: &'static str, code: i32, y: &mut [M::T]) {
641 if code != 0 {
642 self.record(OdeCallbackError::Failed { callback, code }, y);
643 }
644 }
645
646 fn run<F>(
649 &self,
650 name: &'static str,
651 f: Option<F>,
652 y: &mut [M::T],
653 call: impl FnOnce(F, &mut [M::T]) -> i32,
654 ) {
655 match f {
656 Some(f) => {
657 let code = call(f, y);
658 self.check_code(name, code, y);
659 }
660 None => self.record(OdeCallbackError::Missing { callback: name }, y),
661 }
662 }
663
664 fn eval(&self, name: &'static str, f: OdeCallbackFn<M::T>, x: &M::V, t: M::T, y: &mut M::V) {
669 let ud = self.callbacks.user_data;
670 y.for_each_batch_host([x, &self.p], |y, [x, p], _| {
671 self.run(name, f, y, |f, y| unsafe {
672 f(x.as_ptr(), p.as_ptr(), t, y.as_mut_ptr(), ud)
673 })
674 });
675 }
676
677 fn eval_mul(
679 &self,
680 name: &'static str,
681 f: OdeCallbackMulFn<M::T>,
682 x: &M::V,
683 t: M::T,
684 v: &M::V,
685 y: &mut M::V,
686 ) {
687 let ud = self.callbacks.user_data;
688 y.for_each_batch_host([x, &self.p, v], |y, [x, p, v], _| {
689 self.run(name, f, y, |f, y| unsafe {
690 f(x.as_ptr(), p.as_ptr(), t, v.as_ptr(), y.as_mut_ptr(), ud)
691 })
692 });
693 }
694
695 fn eval_init(&self, name: &'static str, f: OdeCallbackInitFn<M::T>, t: M::T, y: &mut M::V) {
697 let ud = self.callbacks.user_data;
698 y.for_each_batch_host([&self.p], |y, [p], _| {
699 self.run(name, f, y, |f, y| unsafe {
700 f(p.as_ptr(), t, y.as_mut_ptr(), ud)
701 })
702 });
703 }
704
705 fn eval_init_mul(
707 &self,
708 name: &'static str,
709 f: OdeCallbackInitMulFn<M::T>,
710 t: M::T,
711 v: &M::V,
712 y: &mut M::V,
713 ) {
714 let ud = self.callbacks.user_data;
715 y.for_each_batch_host([&self.p, v], |y, [p, v], _| {
716 self.run(name, f, y, |f, y| unsafe {
717 f(p.as_ptr(), t, v.as_ptr(), y.as_mut_ptr(), ud)
718 })
719 });
720 }
721
722 fn eval_mass(
724 &self,
725 name: &'static str,
726 f: OdeCallbackMassFn<M::T>,
727 v: &M::V,
728 t: M::T,
729 beta: M::T,
730 y: &mut M::V,
731 ) {
732 let ud = self.callbacks.user_data;
733 y.for_each_batch_host([v, &self.p], |y, [v, p], _| {
734 self.run(name, f, y, |f, y| unsafe {
735 f(v.as_ptr(), p.as_ptr(), t, beta, y.as_mut_ptr(), ud)
736 })
737 });
738 }
739
740 fn find_patterns(&self) -> Patterns<M> {
745 let t0 = M::T::zero();
746 let y0 = self.init().call(t0);
747 let cb = &self.callbacks;
748 let ctx = &self.ctx;
749 let rhs = nonlinear_patterns(
750 &self.rhs(),
751 &y0,
752 t0,
753 [cb.rhs_jac, cb.rhs_adjoint, cb.rhs_sens, cb.rhs_sens_adjoint],
754 ctx,
755 );
756 let root = self
757 .root()
758 .map(|root| nonlinear_patterns(&root, &y0, t0, [None, None, cb.root_sens, None], ctx));
759 let out = self.out().map(|out| {
760 let callbacks = [None, cb.out_adjoint, cb.out_sens, cb.out_sens_adjoint];
761 nonlinear_patterns(&out, &y0, t0, callbacks, ctx)
762 });
763 let reset = self.reset().map(|reset| {
764 nonlinear_patterns(&reset, &y0, t0, [None, None, cb.reset_sens, None], ctx)
765 });
766 let init_sens = (cb.init_sens.is_some() && cb.nparams > 0).then(|| {
767 let init = self.init();
768 let non_zeros = find_constant_sens_non_zeros(&init, t0);
769 Pattern::new(init.nout(), cb.nparams, non_zeros, ctx)
770 });
771 let n = cb.nstates;
772 let (mass, mass_transpose) = match self.mass() {
773 Some(mass) => {
774 let non_zeros = find_matrix_non_zeros(&mass, t0);
775 let transpose = cb
776 .mass_adjoint
777 .map(|_| Pattern::new(n, n, transposed(&non_zeros), ctx));
778 (Some(Pattern::new(n, n, non_zeros, ctx)), transpose)
779 }
780 None => (None, None),
781 };
782 Patterns {
783 rhs,
784 root: root.unwrap_or_default(),
785 out: out.unwrap_or_default(),
786 reset: reset.unwrap_or_default(),
787 init_sens,
788 mass,
789 mass_transpose,
790 }
791 }
792
793 fn check(&self, requirement: EqnRequirement) -> Result<(), DiffsolRtError> {
796 let cb = &self.callbacks;
797 let (has_out, has_root, has_reset) =
798 (cb.out.is_some(), cb.root.is_some(), cb.reset.is_some());
799 let needs_rhs_jac = |method| !matches!(method, OdeSolverType::Tsit45);
802 let (operation, rows) = match requirement {
803 EqnRequirement::Solve(method) => (
804 "this solver",
805 vec![(needs_rhs_jac(method), "rhs_jac", cb.rhs_jac.is_some())],
806 ),
807 EqnRequirement::FwdSens => (
808 "a forward sensitivity solve",
809 vec![
810 (true, "rhs_jac", cb.rhs_jac.is_some()),
811 (true, "rhs_sens", cb.rhs_sens.is_some()),
812 (true, "init_sens", cb.init_sens.is_some()),
813 (has_out, "out_jac", cb.out_jac.is_some()),
814 (has_out, "out_sens", cb.out_sens.is_some()),
815 (has_root, "root_jac", cb.root_jac.is_some()),
816 (has_root, "root_sens", cb.root_sens.is_some()),
817 (has_reset, "reset_jac", cb.reset_jac.is_some()),
818 (has_reset, "reset_sens", cb.reset_sens.is_some()),
819 ],
820 ),
821 EqnRequirement::Adjoint(method) => (
822 "an adjoint solve",
823 vec![
824 (needs_rhs_jac(method), "rhs_jac", cb.rhs_jac.is_some()),
825 (true, "rhs_adjoint", cb.rhs_adjoint.is_some()),
826 (true, "rhs_sens_adjoint", cb.rhs_sens_adjoint.is_some()),
827 (true, "init_sens_adjoint", cb.init_sens_adjoint.is_some()),
828 (cb.mass.is_some(), "mass_adjoint", cb.mass_adjoint.is_some()),
829 (has_out, "out_adjoint", cb.out_adjoint.is_some()),
830 (has_out, "out_sens_adjoint", cb.out_sens_adjoint.is_some()),
831 (has_root, "root_jac", cb.root_jac.is_some()),
832 (has_root, "root_adjoint", cb.root_adjoint.is_some()),
833 (
834 has_root,
835 "root_sens_adjoint",
836 cb.root_sens_adjoint.is_some(),
837 ),
838 (has_reset, "reset_jac", cb.reset_jac.is_some()),
839 (has_reset, "reset_adjoint", cb.reset_adjoint.is_some()),
840 (
841 has_reset,
842 "reset_sens_adjoint",
843 cb.reset_sens_adjoint.is_some(),
844 ),
845 ],
846 ),
847 };
848 match rows
849 .into_iter()
850 .find(|&(needed, _, provided)| needed && !provided)
851 {
852 Some((_, name, _)) => Err(DiffsolError::Other(missing_message(operation, name)).into()),
853 None => Ok(()),
854 }
855 }
856}
857
858fn transposed(non_zeros: &[(usize, usize)]) -> Vec<(usize, usize)> {
860 non_zeros.iter().map(|&(i, j)| (j, i)).collect()
861}
862
863fn nonlinear_patterns<M, F>(
867 op: &F,
868 y0: &M::V,
869 t0: M::T,
870 callbacks: [OdeCallbackMulFn<M::T>; 4],
871 ctx: &M::C,
872) -> NonLinearPatterns<M>
873where
874 M: Matrix,
875 F: NonLinearOpJacobian<M = M, V = M::V, T = M::T, C = M::C>
876 + NonLinearOpAdjoint<M = M, V = M::V, T = M::T, C = M::C>
877 + NonLinearOpSens<M = M, V = M::V, T = M::T, C = M::C>
878 + NonLinearOpSensAdjoint<M = M, V = M::V, T = M::T, C = M::C>,
879{
880 let [has_jac, has_adjoint, has_sens, has_sens_adjoint] = callbacks.map(|f| f.is_some());
881 let (nstates, nout, nparams) = (op.nstates(), op.nout(), op.nparams());
882 let has_params = nparams > 0;
883 let jac = has_jac.then(|| find_jacobian_non_zeros(op, y0, t0));
884 let sens = (has_sens && has_params).then(|| find_sens_non_zeros(op, y0, t0));
885 let adjoint = has_adjoint.then(|| match &jac {
886 Some(jac) => transposed(jac),
887 None => find_adjoint_non_zeros(op, y0, t0),
888 });
889 let sens_adjoint = (has_sens_adjoint && has_params).then(|| match &sens {
890 Some(sens) => transposed(sens),
891 None => find_sens_adjoint_non_zeros(op, y0, t0),
892 });
893 NonLinearPatterns {
894 jac: jac.map(|nz| Pattern::new(nout, nstates, nz, ctx)),
895 adjoint: adjoint.map(|nz| Pattern::new(nstates, nout, nz, ctx)),
896 sens: sens.map(|nz| Pattern::new(nout, nparams, nz, ctx)),
897 sens_adjoint: sens_adjoint.map(|nz| Pattern::new(nparams, nout, nz, ctx)),
898 }
899}
900
901impl<M: Matrix> Op for OdeCallbacks<M> {
902 type T = M::T;
903 type V = M::V;
904 type M = M;
905 type C = M::C;
906
907 fn context(&self) -> &Self::C {
908 &self.ctx
909 }
910 fn nstates(&self) -> usize {
911 self.callbacks.nstates
912 }
913 fn nout(&self) -> usize {
914 if self.callbacks.out.is_some() {
915 self.callbacks.nout
916 } else {
917 self.callbacks.nstates
918 }
919 }
920 fn nparams(&self) -> usize {
921 self.callbacks.nparams
922 }
923 fn statistics(&self) -> OpStatistics {
924 self.statistics.borrow().clone()
925 }
926}
927
928pub struct OdeCallbacksRhs<'a, M: Matrix>(&'a OdeCallbacks<M>);
930pub struct OdeCallbacksRoot<'a, M: Matrix>(&'a OdeCallbacks<M>);
932pub struct OdeCallbacksOut<'a, M: Matrix>(&'a OdeCallbacks<M>);
934pub struct OdeCallbacksReset<'a, M: Matrix>(&'a OdeCallbacks<M>);
936pub struct OdeCallbacksInit<'a, M: Matrix>(&'a OdeCallbacks<M>);
938pub struct OdeCallbacksMass<'a, M: Matrix>(&'a OdeCallbacks<M>);
940
941macro_rules! impl_op_for_view {
942 ($view:ident, |$eqn:ident| $nout:expr $(, $stats:ident)?) => {
943 impl<M: Matrix> Op for $view<'_, M> {
944 type T = M::T;
945 type V = M::V;
946 type M = M;
947 type C = M::C;
948
949 fn context(&self) -> &Self::C {
950 &self.0.ctx
951 }
952 fn nstates(&self) -> usize {
953 self.0.callbacks.nstates
954 }
955 #[allow(clippy::misnamed_getters)]
956 fn nout(&self) -> usize {
957 let $eqn = &self.0.callbacks;
958 $nout
959 }
960 fn nparams(&self) -> usize {
961 self.0.callbacks.nparams
962 }
963 $(
964 fn $stats(&self) -> OpStatistics {
965 self.0.statistics.borrow().clone()
966 }
967 )?
968 }
969 };
970}
971
972impl_op_for_view!(OdeCallbacksRhs, |cb| cb.nstates, statistics);
973impl_op_for_view!(OdeCallbacksRoot, |cb| cb.nroots);
974impl_op_for_view!(OdeCallbacksOut, |cb| cb.nout);
975impl_op_for_view!(OdeCallbacksReset, |cb| cb.nstates);
976impl_op_for_view!(OdeCallbacksInit, |cb| cb.nstates);
977impl_op_for_view!(OdeCallbacksMass, |cb| cb.nstates);
978
979macro_rules! impl_nonlinear_view {
982 ($view:ident, $f:ident, $stats:literal) => {
983 paste::paste! {
984 impl<M: Matrix> NonLinearOp for $view<'_, M> {
985 fn call_inplace(&self, x: &M::V, t: M::T, y: &mut M::V) {
986 if $stats {
987 self.0.statistics.borrow_mut().increment_call();
988 }
989 self.0.eval(stringify!($f), self.0.callbacks.$f, x, t, y);
990 }
991 }
992
993 impl<M: Matrix> NonLinearOpJacobian for $view<'_, M> {
994 fn jac_mul_inplace(&self, x: &M::V, t: M::T, v: &M::V, y: &mut M::V) {
995 if $stats {
996 self.0.statistics.borrow_mut().increment_jac_mul();
997 }
998 self.0
999 .eval_mul(stringify!([<$f _jac>]), self.0.callbacks.[<$f _jac>], x, t, v, y);
1000 }
1001 fn jacobian_inplace(&self, x: &M::V, t: M::T, y: &mut M) {
1002 if $stats {
1003 self.0.statistics.borrow_mut().increment_matrix();
1004 }
1005 match self.0.patterns.$f.jac.as_ref() {
1006 Some(pattern) => pattern.coloring.jacobian_inplace(self, x, t, y),
1007 None => self._default_jacobian_inplace(x, t, y),
1008 }
1009 }
1010 fn jacobian_sparsity(&self) -> Option<M::Sparsity> {
1011 self.0
1012 .patterns
1013 .$f
1014 .jac
1015 .as_ref()
1016 .map(|p| p.sparsity.clone())
1017 }
1018 }
1019
1020 impl<M: Matrix> NonLinearOpSens for $view<'_, M> {
1021 fn sens_mul_inplace(&self, x: &M::V, t: M::T, v: &M::V, y: &mut M::V) {
1022 self.0
1023 .eval_mul(stringify!([<$f _sens>]), self.0.callbacks.[<$f _sens>], x, t, v, y);
1024 }
1025 fn sens_inplace(&self, x: &M::V, t: M::T, y: &mut M) {
1026 match self.0.patterns.$f.sens.as_ref() {
1027 Some(pattern) => pattern.coloring.sens_inplace(self, x, t, y),
1028 None => self._default_sens_inplace(x, t, y),
1029 }
1030 }
1031 fn sens_sparsity(&self) -> Option<M::Sparsity> {
1032 self.0
1033 .patterns
1034 .$f
1035 .sens
1036 .as_ref()
1037 .map(|p| p.sparsity.clone())
1038 }
1039 }
1040
1041 impl<M: Matrix> NonLinearOpAdjoint for $view<'_, M> {
1042 fn jac_transpose_mul_inplace(&self, x: &M::V, t: M::T, v: &M::V, y: &mut M::V) {
1043 if $stats {
1044 self.0.statistics.borrow_mut().increment_jac_adj_mul();
1045 }
1046 self.0
1047 .eval_mul(stringify!([<$f _adjoint>]), self.0.callbacks.[<$f _adjoint>], x, t, v, y);
1048 }
1049 fn adjoint_inplace(&self, x: &M::V, t: M::T, y: &mut M) {
1050 match self.0.patterns.$f.adjoint.as_ref() {
1051 Some(pattern) => pattern.coloring.adjoint_inplace(self, x, t, y),
1052 None => self._default_adjoint_inplace(x, t, y),
1053 }
1054 }
1055 fn adjoint_sparsity(&self) -> Option<M::Sparsity> {
1056 self.0
1057 .patterns
1058 .$f
1059 .adjoint
1060 .as_ref()
1061 .map(|p| p.sparsity.clone())
1062 }
1063 }
1064
1065 impl<M: Matrix> NonLinearOpSensAdjoint for $view<'_, M> {
1066 fn sens_transpose_mul_inplace(&self, x: &M::V, t: M::T, v: &M::V, y: &mut M::V) {
1067 self.0.eval_mul(
1068 stringify!([<$f _sens_adjoint>]),
1069 self.0.callbacks.[<$f _sens_adjoint>],
1070 x,
1071 t,
1072 v,
1073 y,
1074 );
1075 }
1076 fn sens_adjoint_inplace(&self, x: &M::V, t: M::T, y: &mut M) {
1077 match self.0.patterns.$f.sens_adjoint.as_ref() {
1078 Some(pattern) => pattern.coloring.sens_adjoint_inplace(self, x, t, y),
1079 None => self._default_sens_adjoint_inplace(x, t, y),
1080 }
1081 }
1082 fn sens_adjoint_sparsity(&self) -> Option<M::Sparsity> {
1083 self.0
1084 .patterns
1085 .$f
1086 .sens_adjoint
1087 .as_ref()
1088 .map(|p| p.sparsity.clone())
1089 }
1090 }
1091 }
1092 };
1093}
1094
1095impl_nonlinear_view!(OdeCallbacksRhs, rhs, true);
1096impl_nonlinear_view!(OdeCallbacksRoot, root, false);
1097impl_nonlinear_view!(OdeCallbacksOut, out, false);
1098impl_nonlinear_view!(OdeCallbacksReset, reset, false);
1099
1100impl<M: Matrix> ConstantOp for OdeCallbacksInit<'_, M> {
1101 fn call_inplace(&self, t: M::T, y: &mut M::V) {
1102 self.0.eval_init("init", self.0.callbacks.init, t, y);
1103 }
1104}
1105
1106impl<M: Matrix> ConstantOpSens for OdeCallbacksInit<'_, M> {
1107 fn sens_mul_inplace(&self, t: M::T, v: &M::V, y: &mut M::V) {
1108 self.0
1109 .eval_init_mul("init_sens", self.0.callbacks.init_sens, t, v, y);
1110 }
1111 fn sens_inplace(&self, t: M::T, y: &mut M) {
1112 match self.0.patterns.init_sens.as_ref() {
1113 Some(pattern) => pattern.coloring.constant_sens_inplace(self, t, y),
1114 None => self._default_sens_inplace(t, y),
1115 }
1116 }
1117 fn sens_sparsity(&self) -> Option<M::Sparsity> {
1118 self.0
1119 .patterns
1120 .init_sens
1121 .as_ref()
1122 .map(|p| p.sparsity.clone())
1123 }
1124}
1125
1126impl<M: Matrix> ConstantOpSensAdjoint for OdeCallbacksInit<'_, M> {
1127 fn sens_transpose_mul_inplace(&self, t: M::T, v: &M::V, y: &mut M::V) {
1128 self.0.eval_init_mul(
1129 "init_sens_adjoint",
1130 self.0.callbacks.init_sens_adjoint,
1131 t,
1132 v,
1133 y,
1134 );
1135 }
1136}
1137
1138impl<M: Matrix> LinearOp for OdeCallbacksMass<'_, M> {
1139 fn gemv_inplace(&self, x: &M::V, t: M::T, beta: M::T, y: &mut M::V) {
1140 self.0
1141 .eval_mass("mass", self.0.callbacks.mass, x, t, beta, y);
1142 }
1143 fn matrix_inplace(&self, t: M::T, y: &mut M) {
1144 match self.0.patterns.mass.as_ref() {
1145 Some(pattern) => pattern.coloring.matrix_inplace(self, t, y),
1146 None => self._default_matrix_inplace(t, y),
1147 }
1148 }
1149 fn sparsity(&self) -> Option<M::Sparsity> {
1150 self.0.patterns.mass.as_ref().map(|p| p.sparsity.clone())
1151 }
1152}
1153
1154impl<M: Matrix> LinearOpTranspose for OdeCallbacksMass<'_, M> {
1155 fn gemv_transpose_inplace(&self, x: &M::V, t: M::T, beta: M::T, y: &mut M::V) {
1156 self.0
1157 .eval_mass("mass_adjoint", self.0.callbacks.mass_adjoint, x, t, beta, y);
1158 }
1159 fn transpose_inplace(&self, t: M::T, y: &mut M) {
1160 match self.0.patterns.mass_transpose.as_ref() {
1161 Some(pattern) => pattern.coloring.transpose_inplace(self, t, y),
1162 None => self._default_transpose_inplace(t, y),
1163 }
1164 }
1165 fn transpose_sparsity(&self) -> Option<M::Sparsity> {
1166 self.0
1167 .patterns
1168 .mass_transpose
1169 .as_ref()
1170 .map(|p| p.sparsity.clone())
1171 }
1172}
1173
1174impl<'a, M: Matrix> OdeEquationsRef<'a> for OdeCallbacks<M> {
1175 type Mass = OdeCallbacksMass<'a, M>;
1176 type Rhs = OdeCallbacksRhs<'a, M>;
1177 type Root = OdeCallbacksRoot<'a, M>;
1178 type Init = OdeCallbacksInit<'a, M>;
1179 type Out = OdeCallbacksOut<'a, M>;
1180 type Reset = OdeCallbacksReset<'a, M>;
1181}
1182
1183impl<M: Matrix> OdeEquations for OdeCallbacks<M> {
1184 fn rhs(&self) -> OdeCallbacksRhs<'_, M> {
1185 OdeCallbacksRhs(self)
1186 }
1187 fn mass(&self) -> Option<OdeCallbacksMass<'_, M>> {
1188 self.callbacks.mass.map(|_| OdeCallbacksMass(self))
1189 }
1190 fn root(&self) -> Option<OdeCallbacksRoot<'_, M>> {
1191 self.callbacks.root.map(|_| OdeCallbacksRoot(self))
1192 }
1193 fn out(&self) -> Option<OdeCallbacksOut<'_, M>> {
1194 self.callbacks.out.map(|_| OdeCallbacksOut(self))
1195 }
1196 fn reset(&self) -> Option<OdeCallbacksReset<'_, M>> {
1197 self.callbacks.reset.map(|_| OdeCallbacksReset(self))
1198 }
1199 fn init(&self) -> OdeCallbacksInit<'_, M> {
1200 OdeCallbacksInit(self)
1201 }
1202 fn set_params(&mut self, p: &M::V) {
1203 self.p.copy_from(p);
1204 }
1205 fn get_params(&self, p: &mut M::V) {
1206 p.copy_from(&self.p);
1207 }
1208}
1209
1210impl<M: Matrix> SolveEqn for OdeCallbacks<M> {
1211 fn check_requirement(&self, requirement: EqnRequirement) -> Result<(), DiffsolRtError> {
1212 self.check(requirement)
1213 }
1214
1215 fn take_error(&self) -> Option<String> {
1216 self.take_callback_error().map(|err| err.to_string())
1217 }
1218}
1219
1220#[cfg(test)]
1221mod tests {
1222 use std::ffi::c_void;
1223 use std::sync::atomic::{AtomicUsize, Ordering};
1224
1225 use diffsol::{
1226 ConstantOp, Context, LinearOp, LinearOpTranspose, Matrix, NalgebraContext, NalgebraMat,
1227 NonLinearOp, NonLinearOpJacobian, OdeEquations, Vector,
1228 };
1229
1230 use super::{OdeCallbackError, OdeCallbacks, OdeCallbacksDesc};
1231
1232 type M = NalgebraMat<f64>;
1233
1234 unsafe extern "C" fn rhs(
1235 x: *const f64,
1236 p: *const f64,
1237 _t: f64,
1238 y: *mut f64,
1239 _ud: *mut c_void,
1240 ) -> i32 {
1241 unsafe { *y = -*p * *x };
1242 0
1243 }
1244
1245 unsafe extern "C" fn rhs_jac(
1246 _x: *const f64,
1247 p: *const f64,
1248 _t: f64,
1249 v: *const f64,
1250 y: *mut f64,
1251 _ud: *mut c_void,
1252 ) -> i32 {
1253 unsafe { *y = -*p * *v };
1254 0
1255 }
1256
1257 unsafe extern "C" fn failing_rhs(
1258 _x: *const f64,
1259 _p: *const f64,
1260 _t: f64,
1261 _y: *mut f64,
1262 _ud: *mut c_void,
1263 ) -> i32 {
1264 7
1265 }
1266
1267 unsafe extern "C" fn init(_p: *const f64, _t: f64, y: *mut f64, _ud: *mut c_void) -> i32 {
1268 unsafe { *y = 1.0 };
1269 0
1270 }
1271
1272 unsafe extern "C" fn count_free(ud: *mut c_void) {
1273 unsafe { &*(ud as *const AtomicUsize) }.fetch_add(1, Ordering::SeqCst);
1274 }
1275
1276 unsafe extern "C" fn rhs2(
1278 x: *const f64,
1279 _p: *const f64,
1280 _t: f64,
1281 y: *mut f64,
1282 _ud: *mut c_void,
1283 ) -> i32 {
1284 let (x, y) = unsafe {
1285 (
1286 std::slice::from_raw_parts(x, 2),
1287 std::slice::from_raw_parts_mut(y, 2),
1288 )
1289 };
1290 y.copy_from_slice(&[-x[0], -x[1]]);
1291 0
1292 }
1293
1294 unsafe extern "C" fn init2(_p: *const f64, _t: f64, y: *mut f64, _ud: *mut c_void) -> i32 {
1295 unsafe { std::slice::from_raw_parts_mut(y, 2) }.copy_from_slice(&[1.0, 1.0]);
1296 0
1297 }
1298
1299 unsafe extern "C" fn mass2(
1300 v: *const f64,
1301 _p: *const f64,
1302 _t: f64,
1303 beta: f64,
1304 y: *mut f64,
1305 _ud: *mut c_void,
1306 ) -> i32 {
1307 let (v, y) = unsafe {
1308 (
1309 std::slice::from_raw_parts(v, 2),
1310 std::slice::from_raw_parts_mut(y, 2),
1311 )
1312 };
1313 y[0] = v[0] + 2.0 * v[1] + beta * y[0];
1314 y[1] = 3.0 * v[1] + beta * y[1];
1315 0
1316 }
1317
1318 unsafe extern "C" fn mass2_adjoint(
1319 v: *const f64,
1320 _p: *const f64,
1321 _t: f64,
1322 beta: f64,
1323 y: *mut f64,
1324 _ud: *mut c_void,
1325 ) -> i32 {
1326 let (v, y) = unsafe {
1327 (
1328 std::slice::from_raw_parts(v, 2),
1329 std::slice::from_raw_parts_mut(y, 2),
1330 )
1331 };
1332 y[0] = v[0] + beta * y[0];
1333 y[1] = 2.0 * v[0] + 3.0 * v[1] + beta * y[1];
1334 0
1335 }
1336
1337 fn desc() -> OdeCallbacksDesc<f64> {
1338 OdeCallbacksDesc::new(1, 1, rhs, init)
1339 }
1340
1341 fn build(desc: OdeCallbacksDesc<f64>) -> Result<OdeCallbacks<M>, diffsol::DiffsolError> {
1342 unsafe { OdeCallbacks::<M>::new(desc, NalgebraContext::default()) }
1344 }
1345
1346 fn build_err(desc: OdeCallbacksDesc<f64>) -> String {
1347 match build(desc) {
1348 Ok(_) => panic!("expected the descriptor to be rejected"),
1349 Err(err) => err.to_string(),
1350 }
1351 }
1352
1353 #[test]
1354 fn evaluates_callbacks() {
1355 let mut d = desc();
1356 d.rhs_jac = Some(rhs_jac);
1357 let mut eqn = build(d).unwrap();
1358 let ctx = NalgebraContext::default();
1359 eqn.set_params(&ctx.vector_from_vec(vec![2.0]));
1360 let x = ctx.vector_from_vec(vec![3.0]);
1361 eqn.rhs()
1362 .call(&x, 0.0)
1363 .assert_eq_st(&ctx.vector_from_vec(vec![-6.0]), 1e-12);
1364 eqn.rhs()
1365 .jac_mul(&x, 0.0, &x)
1366 .assert_eq_st(&ctx.vector_from_vec(vec![-6.0]), 1e-12);
1367 eqn.init()
1368 .call(0.0)
1369 .assert_eq_st(&ctx.vector_from_vec(vec![1.0]), 1e-12);
1370 assert!(eqn.mass().is_none());
1371 assert!(eqn.root().is_none());
1372 assert!(eqn.out().is_none());
1373 assert!(eqn.reset().is_none());
1374 assert!(eqn.take_callback_error().is_none());
1375 }
1376
1377 #[test]
1378 fn records_failures_and_missing_callbacks() {
1379 let mut d = desc();
1380 d.rhs = Some(failing_rhs);
1381 let eqn = build(d).unwrap();
1382 let x = NalgebraContext::default().vector_from_vec(vec![3.0]);
1383 let y = eqn.rhs().call(&x, 0.0);
1384 assert!(y.get_index(0).is_nan());
1385 assert_eq!(
1386 eqn.take_callback_error(),
1387 Some(OdeCallbackError::Failed {
1388 callback: "rhs",
1389 code: 7
1390 })
1391 );
1392 assert!(eqn.take_callback_error().is_none());
1393
1394 let y = eqn.rhs().jac_mul(&x, 0.0, &x);
1395 assert!(y.get_index(0).is_nan());
1396 assert_eq!(
1397 eqn.take_callback_error(),
1398 Some(OdeCallbackError::Missing {
1399 callback: "rhs_jac"
1400 })
1401 );
1402 }
1403
1404 #[test]
1405 fn rejects_invalid_descriptors() {
1406 let mut d = desc();
1407 d.rhs = None;
1408 assert!(build_err(d).contains("'rhs' callback is required"));
1409
1410 let mut d = desc();
1411 d.init = None;
1412 assert!(build_err(d).contains("'init' callback is required"));
1413
1414 let mut d = desc();
1415 d.nstates = 0;
1416 assert!(build_err(d).contains("nstates"));
1417
1418 let mut d = desc();
1419 d.nout = 2;
1420 assert!(build_err(d).contains("nout"));
1421
1422 let mut d = desc();
1423 d.out = Some(rhs);
1424 assert!(build_err(d).contains("nout"));
1425
1426 let mut d = desc();
1427 d.nroots = 1;
1428 assert!(build_err(d).contains("nroots"));
1429
1430 let mut d = desc();
1431 d.out_jac = Some(rhs_jac);
1432 assert!(build_err(d).contains("'out_jac' callback requires the 'out' callback"));
1433
1434 let mut d = desc();
1435 d.root = Some(rhs);
1436 d.nroots = 1;
1437 d.reset_jac = Some(rhs_jac);
1438 assert!(build_err(d).contains("'reset_jac' callback requires the 'reset' callback"));
1439 }
1440
1441 #[test]
1442 fn frees_user_data_exactly_once() {
1443 let count = AtomicUsize::new(0);
1444 let mut d = desc();
1445 d.user_data = &count as *const AtomicUsize as *mut c_void;
1446 d.free_user_data = Some(count_free);
1447
1448 drop(build(d).unwrap());
1449 assert_eq!(count.load(Ordering::SeqCst), 1);
1450
1451 d.rhs = None;
1452 assert!(build(d).is_err());
1453 assert_eq!(count.load(Ordering::SeqCst), 2);
1454 }
1455
1456 #[test]
1459 fn sparse_mass_and_transpose_matrices() {
1460 type S = diffsol::FaerSparseMat<f64>;
1461 let mut desc = OdeCallbacksDesc::new(2, 0, rhs2, init2);
1462 desc.mass = Some(mass2);
1463 desc.mass_adjoint = Some(mass2_adjoint);
1464 let eqn = unsafe { OdeCallbacks::<S>::new(desc, Default::default()) }.unwrap();
1466 let mass = eqn.mass().unwrap();
1467 let dense = |m: &S| {
1468 let mut d = [[0.0; 2]; 2];
1469 let (indices, values) = m.triplet_iter();
1470 for ((i, j), v) in indices.zip(values) {
1471 d[i][j] += v;
1472 }
1473 d
1474 };
1475 let m = mass.matrix(0.0);
1476 assert_eq!(dense(&m), [[1.0, 2.0], [0.0, 3.0]]);
1477 let mut mt = S::new_from_sparsity(2, 2, mass.transpose_sparsity(), Default::default());
1478 mass.transpose_inplace(0.0, &mut mt);
1479 assert_eq!(dense(&mt), [[1.0, 0.0], [2.0, 3.0]]);
1480 }
1481}