Skip to main content

diffsol_c/
ode_callbacks.rs

1//! ODE equations defined by C callbacks.
2//!
3//! [`OdeCallbacks`] implements [`diffsol::OdeEquations`]. It also implements each
4//! flavour that the solvers need: implicit, forward sensitivities and adjoint. The equations
5//! come from a table of C function pointers, [`OdeCallbacksDesc`]. Thus a C host (or a different
6//! FFI host) can define its own equations.
7//!
8//! There is one callback for each closure of [`diffsol::OdeBuilder`]. Each callback has the
9//! arguments of its closure, plus a `user_data` pointer as the last argument. Only `rhs` and
10//! `init` are necessary.
11//!
12//! # Callback conventions
13//!
14//! Each pointer points to a contiguous array of the scalar type `T` (`double` or `float`). The
15//! table gives the array lengths. The counts are fields of [`OdeCallbacksDesc`].
16//!
17//! | callback | `x` | `p` | `v` | `y` (output) |
18//! |---|---|---|---|---|
19//! | `rhs`, `reset` | nstates | nparams | - | nstates |
20//! | `rhs_jac`, `reset_jac` (`y = J v`) | nstates | nparams | nstates | nstates |
21//! | `rhs_sens`, `reset_sens` (`y = J_p v`) | nstates | nparams | nparams | nstates |
22//! | `rhs_adjoint`, `reset_adjoint` (`y = -J^T v`) | nstates | nparams | nstates | nstates |
23//! | `rhs_sens_adjoint`, `reset_sens_adjoint` (`y = -J_p^T v`) | nstates | nparams | nstates | nparams |
24//! | `root` | nstates | nparams | - | nroots |
25//! | `root_jac` / `root_sens` | nstates | nparams | nstates / nparams | nroots |
26//! | `root_adjoint` / `root_sens_adjoint` | nstates | nparams | nroots | nstates / nparams |
27//! | `out` | nstates | nparams | - | nout |
28//! | `out_jac` / `out_sens` | nstates | nparams | nstates / nparams | nout |
29//! | `out_adjoint` / `out_sens_adjoint` | nstates | nparams | nout | nstates / nparams |
30//! | `init` | - | nparams | - | nstates |
31//! | `init_sens` / `init_sens_adjoint` | - | nparams | nparams / nstates | nstates / nparams |
32//! | `mass` / `mass_adjoint` (`y = M v + beta y`, `M^T` for the adjoint) | - | nparams | nstates | nstates |
33//!
34//! `J` is the Jacobian of the function with respect to the state `x`. `J_p` is the Jacobian
35//! with respect to the parameters `p`.
36//!
37//! Each `*_adjoint` and `*_sens_adjoint` callback calculates the *negative* transposed product.
38//! For example, `out_adjoint` calculates `y = -J^T v`, and `init_sens_adjoint` calculates
39//! `y = -(dy0/dp)^T v`. `mass_adjoint` is the only exception: it calculates `y = M^T v + beta y`.
40//!
41//! Each callback has the same meaning as the related [`diffsol::OdeBuilder`] closure. For
42//! example, `rhs_adjoint` is the `rhs_adjoint` closure of
43//! [`diffsol::OdeBuilder::rhs_adjoint_implicit`].
44//!
45//! # Return codes
46//!
47//! Each callback returns an `int`. The value `0` means success. A different value means
48//! failure. When a callback fails, its output is filled with NaN.
49//!
50//! The solver can recover from a failure. For example, it can reject a step and try a smaller
51//! step. Thus `rhs` can return an error for a state that is not in its domain.
52//!
53//! If the solve succeeds, the callback failures are ignored.
54//!
55//! If the solve fails, its error is the error of the solver. If a callback failed during the
56//! solve, the message also gives the first failure, for example
57//! `(a callback failed during the solve: callback 'rhs' returned error code 7)`. This failure is
58//! often the cause. But it can also be an earlier failure that the solver recovered from.
59//!
60//! # Sparsity patterns
61//!
62//! For a sparse matrix type (`FaerSparse`), the construction finds the sparsity pattern of each
63//! matrix one time. It calls each derivative callback with NaN in the inputs `x` and `v`. Each
64//! output that is NaN is a non-zero. This is the same method that [`diffsol::OdeBuilder`] uses.
65//!
66//! The probe uses the initial state at `t = 0`, with all parameters zero. The patterns do not
67//! change after this, also when the parameters change. Thus, for a sparse matrix type:
68//! - each callback must accept NaN inputs, and the parameters `p = 0`;
69//! - the pattern of each derivative must not depend on the values of `x`, `p` or `t`. For
70//!   example, do not use an `if` on an input value to decide whether to write an output.
71//!
72//! If a callback does not obey these rules, the matrix can be incorrect. Then the solver can
73//! converge slowly or fail. The dense matrix types do not use a pattern.
74//!
75//! # Ownership of `user_data`
76//!
77//! Each callback gets `user_data` as its last argument, with no change.
78//!
79//! If `free_user_data` is set, [`OdeCallbacks`] owns `user_data` from the start of its
80//! construction. This is also true if the construction fails. [`OdeCallbacks`] calls
81//! `free_user_data(user_data)` one time, when it is dropped.
82//!
83//! If `free_user_data` is not set, `user_data` must stay valid until the equations are dropped.
84//!
85//! # Threads
86//!
87//! The callbacks are called one at a time. They are never called at the same time from two
88//! threads.
89//!
90//! But a solver can move between threads. An `OdeWrapper` is `Send` and `Sync`. Thus the
91//! callbacks run on the thread that uses the solver at that time. `free_user_data` runs on the
92//! thread that drops the solver. If `user_data` must stay on one thread (for example a Python
93//! object that needs the GIL, or a JNI local reference), use the solver and drop it only on that
94//! thread.
95use 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
127// Each callback type is a nullable function pointer, the same as a function pointer type in C:
128// `None` is `NULL`, and means that the callback is not given.
129
130/// `y = F(x, p, t)`: used for `rhs`, `root`, `out` and `reset`.
131pub 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
135/// `y = A(x, p, t) v`: used for every `*_jac`, `*_sens`, `*_adjoint` and `*_sens_adjoint`
136/// callback of `rhs`, `root`, `out` and `reset`.
137pub 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
148/// `y = y0(p, t)`: the initial state.
149pub type OdeCallbackInitFn<T> =
150    Option<unsafe extern "C" fn(p: *const T, t: T, y: *mut T, user_data: *mut c_void) -> i32>;
151
152/// `y = A(p, t) v`: used for `init_sens` and `init_sens_adjoint`.
153pub 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
157/// `y = M(p, t) v + beta y`: used for `mass` (and `mass_adjoint`, with `M^T`).
158pub 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
169/// Frees `user_data`, see the [module documentation](self).
170pub type OdeCallbackFreeFn = Option<unsafe extern "C" fn(user_data: *mut c_void)>;
171
172/// The C callbacks (and the problem dimensions) that define an [`OdeCallbacks`].
173///
174/// A `NULL` function pointer means the callback is not provided. Only `rhs` and `init` are
175/// required; see the [module documentation](self) for the meaning and argument lengths of each
176/// callback.
177#[repr(C)]
178#[derive(Clone, Copy, Debug)]
179pub struct OdeCallbacksDesc<T> {
180    /// Number of states.
181    pub nstates: usize,
182    /// Number of parameters.
183    pub nparams: usize,
184    /// Number of outputs of `out`, which must be 0 exactly when `out` is not provided.
185    pub nout: usize,
186    /// Number of root functions of `root`, which must be 0 exactly when `root` is not provided.
187    pub nroots: usize,
188    /// Passed as the last argument of every callback.
189    pub user_data: *mut c_void,
190    /// Called once with `user_data` when the equations are dropped. May be `NULL`.
191    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
225// SAFETY: `user_data` is the only field that is not `Send`. The caller of the C API agrees to
226// the thread contract in the module documentation: the callbacks and `free_user_data` can run
227// on any thread, but never at the same time. Thus it is safe to move the descriptor to a
228// different thread. The descriptor is not `Sync`, because two threads must not call the
229// callbacks at the same time.
230unsafe impl<T: Send> Send for OdeCallbacksDesc<T> {}
231
232/// [`OdeCallbacksDesc`] for `double` precision.
233pub type DiffsolOdeCallbacksF64 = OdeCallbacksDesc<f64>;
234/// [`OdeCallbacksDesc`] for `float` precision.
235pub type DiffsolOdeCallbacksF32 = OdeCallbacksDesc<f32>;
236
237impl<T> OdeCallbacksDesc<T> {
238    /// A descriptor with only the required callbacks set, and no `user_data`.
239    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    /// Check the descriptor is self-consistent, returning a description of the first problem.
287    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        // the derivative callbacks of root, out and reset, in the order of `SUFFIXES`
316        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    /// Free `user_data` with `free_user_data`, if set.
362    ///
363    /// # Safety
364    /// Must be called at most once per `user_data`, and only if nothing else owns it.
365    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
372/// A scalar type that C callbacks can use: `f64` or `f32`.
373///
374/// This trait is sealed. Users cannot implement it for other types.
375pub 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        /// Construct a solver from `callbacks`, with the scalar type `Self`.
385        ///
386        /// # Safety
387        /// `callbacks` must satisfy the conditions of
388        /// [`OdeCallbacks::new`](crate::ode_callbacks::OdeCallbacks::new).
389        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
398/// Build a [Solve] for the matrix type `matrix_type` and the scalar type `$t` from `callbacks`.
399///
400/// The caller must satisfy the safety conditions of [OdeCallbacks::new].
401macro_rules! solve_for_matrix_type {
402    ($t:ty, $callbacks:expr, $matrix_type:expr) => {{
403        let callbacks = $callbacks;
404        // SAFETY: the caller guarantees the conditions of `OdeCallbacks::new`
405        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    /// Construct an ODE solver from equations that C callbacks give.
458    ///
459    /// The scalar type `T` (`f64` or `f32`) of the callbacks is also the scalar type of the
460    /// solver. [crate::ode_callbacks] gives the callback conventions.
461    ///
462    /// The solver takes ownership of `callbacks.user_data`. If `callbacks.free_user_data` is
463    /// set, the solver calls it one time. It calls it when the solver is dropped. If this
464    /// function fails, it calls it before it returns.
465    ///
466    /// # Safety
467    /// `callbacks` must satisfy the conditions of [OdeCallbacks::new].
468    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        // SAFETY: the caller guarantees the conditions of `OdeCallbacks::new`
475        unsafe { T::new_ode(callbacks, matrix_type, linear_solver, ode_solver) }
476    }
477}
478
479/// An error recorded while evaluating the callbacks of an [`OdeCallbacks`].
480#[derive(Clone, Copy, Debug, PartialEq, Eq)]
481pub enum OdeCallbackError {
482    /// The callback returned a non-zero status code.
483    Failed { callback: &'static str, code: i32 },
484    /// The solver needed a callback that was not provided.
485    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
501/// The message for a missing callback `name`, the same whether the missing callback is found
502/// by the check before a solve or when the solver calls it.
503fn missing_message(operation: &str, name: &str) -> String {
504    format!("OdeCallbacks: {operation} requires the '{name}' callback")
505}
506
507/// A sparsity pattern together with the coloring used to evaluate a matrix with that pattern.
508struct 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
522/// The patterns of the matrices of one non-linear operator (`rhs`, `root`, `out` or `reset`).
523struct 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
541/// Every sparsity pattern of an [`OdeCallbacks`]. Only computed for sparse matrix types, where
542/// the matrices cannot be built without them.
543struct 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
567/// ODE equations defined by C callbacks, see the [module documentation](self).
568pub 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        // SAFETY: `OdeCallbacks` took ownership of `user_data` on construction, and is only
580        // dropped once
581        unsafe { self.callbacks.free_user_data() };
582    }
583}
584
585impl<M: Matrix> OdeCallbacks<M> {
586    /// Create the equations from `callbacks`. All parameters are zero at the start.
587    ///
588    /// This function takes ownership of `callbacks.user_data`, also if it returns an error. See
589    /// the [module documentation](self).
590    ///
591    /// For sparse matrix types, this function finds the sparsity pattern of each derivative
592    /// callback. To do this, it calls the callback with NaN inputs at the initial state and
593    /// `t = 0`.
594    ///
595    /// # Safety
596    /// - Each function pointer in `callbacks` must be null or a valid function with the
597    ///   signature that the [module documentation](self) gives.
598    /// - Each function must stay valid until the equations are dropped.
599    /// - Each function must read and write only the array lengths in the table.
600    /// - If `free_user_data` is set, the equations own `user_data`. Do not free `user_data`
601    ///   yourself, and do not give it to other equations.
602    /// - If `free_user_data` is not set, `user_data` must stay valid until the equations are
603    ///   dropped.
604    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` owns `user_data` from here on, so it is freed even if validation fails
614        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    /// Take (and clear) the first error recorded while evaluating the callbacks.
629    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    /// Call the callback `f` on one batch lane `y` with `call`. If `f` is missing or fails, record
647    /// the error and fill `y` with NaN.
648    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    // SAFETY (for each `unsafe` call below): the slices have the lengths documented for the
665    // callback, and `user_data` is the pointer the caller gave with the callbacks.
666
667    /// Evaluate `y = f(x, p, t)` for each batch lane.
668    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    /// Evaluate `y = f(x, p, t, v)` for each batch lane.
678    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    /// Evaluate `y = f(p, t)` for each batch lane.
696    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    /// Evaluate `y = f(p, t, v)` for each batch lane.
706    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    /// Evaluate `y = f(v, p, t, beta, y)` for each batch lane.
723    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    /// Find the sparsity patterns of the matrices that diffsol builds, for each provided
741    /// derivative callback. These are the same matrices that `OdeBuilder` finds patterns for:
742    /// all four of the rhs, the sensitivities of root and reset, and the sensitivities and
743    /// adjoints of out.
744    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    /// Check the callbacks needed by `requirement` were provided. Each row of the tables below is
794    /// `(needed, name, provided)`: the callback `name` is needed if `needed` is true.
795    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        // implicit methods need the rhs Jacobian. Tsit45, the only explicit method, does not
800        // support a mass matrix, so a mass matrix needs no separate row.
801        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
858/// The pattern of the transpose of a matrix with the non-zeros `non_zeros`.
859fn transposed(non_zeros: &[(usize, usize)]) -> Vec<(usize, usize)> {
860    non_zeros.iter().map(|&(i, j)| (j, i)).collect()
861}
862
863/// Find the patterns of the matrices of a non-linear operator, for each of the
864/// `[jac, adjoint, sens, sens_adjoint]` callbacks that is provided. The pattern of an adjoint
865/// matrix is the transpose of the forward pattern when that was found, otherwise it is probed.
866fn 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
928/// The right-hand side `F(x, p, t)` of an [`OdeCallbacks`].
929pub struct OdeCallbacksRhs<'a, M: Matrix>(&'a OdeCallbacks<M>);
930/// The root function of an [`OdeCallbacks`].
931pub struct OdeCallbacksRoot<'a, M: Matrix>(&'a OdeCallbacks<M>);
932/// The output function of an [`OdeCallbacks`].
933pub struct OdeCallbacksOut<'a, M: Matrix>(&'a OdeCallbacks<M>);
934/// The reset function of an [`OdeCallbacks`].
935pub struct OdeCallbacksReset<'a, M: Matrix>(&'a OdeCallbacks<M>);
936/// The initial condition of an [`OdeCallbacks`].
937pub struct OdeCallbacksInit<'a, M: Matrix>(&'a OdeCallbacks<M>);
938/// The mass matrix of an [`OdeCallbacks`].
939pub 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
979/// Implement the non-linear operator traits for a view of `rhs`, `root`, `out` or `reset`.
980/// `$stats` says whether to count evaluations in the equations' statistics (only the rhs does).
981macro_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    // a 2 state system with the non-symmetric mass matrix [[1, 2], [0, 3]]
1277    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        // SAFETY: the test callbacks are valid and use the documented lengths
1343        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    /// The mass matrix and its transpose are built correctly from their colorings for a
1457    /// sparse matrix type, also when the mass matrix is not symmetric.
1458    #[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        // SAFETY: the test callbacks are valid and use the documented lengths
1465        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}