Skip to main content

diffsol_c/
ode_diffsl.rs

1//! DiffSL equations: the [OdeWrapper] constructors, the solver factories, and the
2//! [SolveEqn] impls for each code generation backend.
3
4#[cfg(feature = "diffsl-external-dynamic")]
5use std::path::PathBuf;
6
7use diffsol::{
8    error::DiffsolError, matrix::Matrix, DefaultDenseMatrix, DiffSl, ObjectModule, Vector,
9};
10#[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
11use diffsol::{CodegenModule, CodegenModuleCompile, CodegenModuleJit, OdeBuilder};
12
13use crate::error::DiffsolRtError;
14#[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
15use crate::jit::JitBackendType;
16use crate::linear_solver_type::LinearSolverType;
17use crate::matrix_type::MatrixType;
18use crate::ode::OdeWrapper;
19use crate::ode_solver_type::OdeSolverType;
20#[cfg(feature = "external")]
21use crate::scalar_type::ExternalScalar;
22use crate::scalar_type::{Scalar, ScalarType};
23use crate::solve::{GenericSolve, Solve};
24use crate::solve_eqn::{unsupported, EqnRequirement, SolveEqn};
25
26/// A [`GenericSolve`] over DiffSL equations compiled with the code generation backend `CG`.
27pub(crate) type DiffSlSolve<M, CG> = GenericSolve<M, DiffSl<M, CG>>;
28
29impl OdeWrapper {
30    /// Construct an ODE solver backed by externally-provided DiffSL symbols.
31    #[cfg(feature = "external")]
32    pub fn new_external(
33        rhs_state_deps: Vec<(usize, usize)>,
34        rhs_input_deps: Vec<(usize, usize)>,
35        mass_state_deps: Vec<(usize, usize)>,
36        scalar_type: ScalarType,
37        matrix_type: MatrixType,
38        linear_solver: LinearSolverType,
39        ode_solver: OdeSolverType,
40    ) -> Result<Self, DiffsolRtError> {
41        let solve = solve_factory_external(
42            rhs_state_deps,
43            rhs_input_deps,
44            mass_state_deps,
45            matrix_type,
46            scalar_type,
47        )?;
48        Self::build(
49            String::new(),
50            scalar_type,
51            solve,
52            None,
53            linear_solver,
54            ode_solver,
55        )
56    }
57
58    /// Construct an ODE solver backed by DiffSL symbols loaded from a dynamic library.
59    #[cfg(feature = "diffsl-external-dynamic")]
60    #[allow(clippy::too_many_arguments)]
61    pub fn new_external_dynamic(
62        path: impl Into<std::path::PathBuf>,
63        rhs_state_deps: Vec<(usize, usize)>,
64        rhs_input_deps: Vec<(usize, usize)>,
65        mass_state_deps: Vec<(usize, usize)>,
66        scalar_type: ScalarType,
67        matrix_type: MatrixType,
68        linear_solver: LinearSolverType,
69        ode_solver: OdeSolverType,
70    ) -> Result<Self, DiffsolRtError> {
71        let solve = solve_factory_external_dynamic(
72            path.into(),
73            rhs_state_deps,
74            rhs_input_deps,
75            mass_state_deps,
76            matrix_type,
77            scalar_type,
78        )?;
79        Self::build(
80            String::new(),
81            scalar_type,
82            solve,
83            None,
84            linear_solver,
85            ode_solver,
86        )
87    }
88
89    /// Construct an ODE solver by JIT-compiling DiffSL code immediately.
90    #[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
91    pub fn new_jit(
92        code: &str,
93        jit_backend: JitBackendType,
94        scalar_type: ScalarType,
95        matrix_type: MatrixType,
96        linear_solver: LinearSolverType,
97        ode_solver: OdeSolverType,
98    ) -> Result<Self, DiffsolRtError> {
99        let solve = solve_factory_jit(code, jit_backend, matrix_type, scalar_type)?;
100        Self::build(
101            code.to_owned(),
102            scalar_type,
103            solve,
104            Some(jit_backend),
105            linear_solver,
106            ode_solver,
107        )
108    }
109}
110
111// Public factory method for generating an instance based on matrix type
112#[cfg(feature = "external")]
113pub(crate) fn solve_factory_external(
114    rhs_state_deps: Vec<(usize, usize)>,
115    rhs_input_deps: Vec<(usize, usize)>,
116    mass_state_deps: Vec<(usize, usize)>,
117    matrix_type: MatrixType,
118    scalar_type: ScalarType,
119) -> Result<Box<dyn Solve>, DiffsolRtError> {
120    // the C ABI only carries the rhs and mass patterns; the out, reset and stop operators' are
121    // left empty, which is the same "no non-zeros" contract the other lists already use
122    let deps = diffsol::DiffSlDeps {
123        rhs_state: rhs_state_deps,
124        rhs_input: rhs_input_deps,
125        mass_state: mass_state_deps,
126        ..Default::default()
127    };
128    let solve: Box<dyn Solve> = match matrix_type {
129        MatrixType::NalgebraDense => match scalar_type {
130            #[cfg(feature = "diffsl-external-f32")]
131            ScalarType::F32 => Box::new(DiffSlSolve::<
132                diffsol::NalgebraMat<f32>,
133                diffsl::ExternalModule<f32>,
134            >::from_external(deps, false)?),
135            #[cfg(feature = "diffsl-external-f64")]
136            ScalarType::F64 => Box::new(DiffSlSolve::<
137                diffsol::NalgebraMat<f64>,
138                diffsl::ExternalModule<f64>,
139            >::from_external(deps, false)?),
140            _ => {
141                return Err(DiffsolRtError::from(DiffsolError::Other(
142                    "Unsupported scalar type for NalgebraDense".to_string(),
143                )));
144            }
145        },
146        MatrixType::FaerDense => match scalar_type {
147            #[cfg(feature = "diffsl-external-f32")]
148            ScalarType::F32 => Box::new(DiffSlSolve::<
149                diffsol::FaerMat<f32>,
150                diffsl::ExternalModule<f32>,
151            >::from_external(deps, false)?),
152            #[cfg(feature = "diffsl-external-f64")]
153            ScalarType::F64 => Box::new(DiffSlSolve::<
154                diffsol::FaerMat<f64>,
155                diffsl::ExternalModule<f64>,
156            >::from_external(deps, false)?),
157            _ => {
158                return Err(DiffsolRtError::from(DiffsolError::Other(
159                    "Unsupported scalar type for FaerDense".to_string(),
160                )));
161            }
162        },
163        MatrixType::FaerSparse => match scalar_type {
164            #[cfg(feature = "diffsl-external-f32")]
165            ScalarType::F32 => Box::new(DiffSlSolve::<
166                diffsol::FaerSparseMat<f32>,
167                diffsl::ExternalModule<f32>,
168            >::from_external(deps, false)?),
169            #[cfg(feature = "diffsl-external-f64")]
170            ScalarType::F64 => Box::new(DiffSlSolve::<
171                diffsol::FaerSparseMat<f64>,
172                diffsl::ExternalModule<f64>,
173            >::from_external(deps, false)?),
174            _ => {
175                return Err(DiffsolRtError::from(DiffsolError::Other(
176                    "Unsupported scalar type for FaerSparse".to_string(),
177                )));
178            }
179        },
180    };
181    Ok(solve)
182}
183
184#[cfg(feature = "diffsl-external-dynamic")]
185pub(crate) fn solve_factory_external_dynamic(
186    path: PathBuf,
187    rhs_state_deps: Vec<(usize, usize)>,
188    rhs_input_deps: Vec<(usize, usize)>,
189    mass_state_deps: Vec<(usize, usize)>,
190    matrix_type: MatrixType,
191    scalar_type: ScalarType,
192) -> Result<Box<dyn Solve>, DiffsolRtError> {
193    // see `solve_factory_external`: the C ABI carries only the rhs and mass patterns
194    let deps = diffsol::DiffSlDeps {
195        rhs_state: rhs_state_deps,
196        rhs_input: rhs_input_deps,
197        mass_state: mass_state_deps,
198        ..Default::default()
199    };
200    let solve: Box<dyn Solve> = match matrix_type {
201        MatrixType::NalgebraDense => match scalar_type {
202            ScalarType::F32 => Box::new(DiffSlSolve::<
203                diffsol::NalgebraMat<f32>,
204                diffsl::ExternalDynModule<f32>,
205            >::from_external_dynamic(path, deps, false)?),
206            ScalarType::F64 => Box::new(DiffSlSolve::<
207                diffsol::NalgebraMat<f64>,
208                diffsl::ExternalDynModule<f64>,
209            >::from_external_dynamic(path, deps, false)?),
210        },
211        MatrixType::FaerDense => match scalar_type {
212            ScalarType::F32 => Box::new(DiffSlSolve::<
213                diffsol::FaerMat<f32>,
214                diffsl::ExternalDynModule<f32>,
215            >::from_external_dynamic(path, deps, false)?),
216            ScalarType::F64 => Box::new(DiffSlSolve::<
217                diffsol::FaerMat<f64>,
218                diffsl::ExternalDynModule<f64>,
219            >::from_external_dynamic(path, deps, false)?),
220        },
221        MatrixType::FaerSparse => match scalar_type {
222            ScalarType::F32 => Box::new(DiffSlSolve::<
223                diffsol::FaerSparseMat<f32>,
224                diffsl::ExternalDynModule<f32>,
225            >::from_external_dynamic(path, deps, false)?),
226            ScalarType::F64 => Box::new(DiffSlSolve::<
227                diffsol::FaerSparseMat<f64>,
228                diffsl::ExternalDynModule<f64>,
229            >::from_external_dynamic(path, deps, false)?),
230        },
231    };
232    Ok(solve)
233}
234
235#[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
236pub(crate) fn solve_factory_jit(
237    code: &str,
238    jit_backend: JitBackendType,
239    matrix_type: MatrixType,
240    scalar_type: ScalarType,
241) -> Result<Box<dyn Solve>, DiffsolRtError> {
242    match jit_backend {
243        #[cfg(feature = "diffsl-cranelift")]
244        JitBackendType::Cranelift => solve_factory_with_jit_backend::<diffsol::CraneliftJitModule>(
245            code,
246            matrix_type,
247            scalar_type,
248        ),
249        #[cfg(feature = "diffsl-llvm")]
250        JitBackendType::Llvm => {
251            solve_factory_with_jit_backend::<diffsol::LlvmModule>(code, matrix_type, scalar_type)
252        }
253    }
254}
255
256#[cfg(any(feature = "diffsl-cranelift", feature = "diffsl-llvm"))]
257fn solve_factory_with_jit_backend<CG>(
258    code: &str,
259    matrix_type: MatrixType,
260    scalar_type: ScalarType,
261) -> Result<Box<dyn Solve>, DiffsolRtError>
262where
263    CG: CodegenModule + CodegenModuleJit + CodegenModuleCompile,
264    DiffSl<diffsol::NalgebraMat<f32>, CG>: SolveEqn,
265    DiffSl<diffsol::NalgebraMat<f64>, CG>: SolveEqn,
266    DiffSl<diffsol::FaerMat<f32>, CG>: SolveEqn,
267    DiffSl<diffsol::FaerMat<f64>, CG>: SolveEqn,
268    DiffSl<diffsol::FaerSparseMat<f32>, CG>: SolveEqn,
269    DiffSl<diffsol::FaerSparseMat<f64>, CG>: SolveEqn,
270{
271    let solve: Box<dyn Solve> = match matrix_type {
272        MatrixType::NalgebraDense => match scalar_type {
273            ScalarType::F32 => {
274                let problem =
275                    OdeBuilder::<diffsol::NalgebraMat<f32>>::new().build_from_diffsl::<CG>(code)?;
276                Box::new(GenericSolve { problem })
277            }
278            ScalarType::F64 => {
279                let problem =
280                    OdeBuilder::<diffsol::NalgebraMat<f64>>::new().build_from_diffsl::<CG>(code)?;
281                Box::new(GenericSolve { problem })
282            }
283        },
284        MatrixType::FaerDense => match scalar_type {
285            ScalarType::F32 => {
286                let problem =
287                    OdeBuilder::<diffsol::FaerMat<f32>>::new().build_from_diffsl::<CG>(code)?;
288                Box::new(GenericSolve { problem })
289            }
290            ScalarType::F64 => {
291                let problem =
292                    OdeBuilder::<diffsol::FaerMat<f64>>::new().build_from_diffsl::<CG>(code)?;
293                Box::new(GenericSolve { problem })
294            }
295        },
296        MatrixType::FaerSparse => match scalar_type {
297            ScalarType::F32 => {
298                let problem = OdeBuilder::<diffsol::FaerSparseMat<f32>>::new()
299                    .build_from_diffsl::<CG>(code)?;
300                Box::new(GenericSolve { problem })
301            }
302            ScalarType::F64 => {
303                let problem = OdeBuilder::<diffsol::FaerSparseMat<f64>>::new()
304                    .build_from_diffsl::<CG>(code)?;
305                Box::new(GenericSolve { problem })
306            }
307        },
308    };
309    Ok(solve)
310}
311
312pub(crate) fn solve_factory_from_serialized_diffsl(
313    serialized_diffsl: &[u8],
314    matrix_type: MatrixType,
315    scalar_type: ScalarType,
316) -> Result<Box<dyn Solve>, DiffsolRtError> {
317    let solve: Box<dyn Solve> = match matrix_type {
318        MatrixType::NalgebraDense => match scalar_type {
319            ScalarType::F32 => Box::new(
320                DiffSlSolve::<diffsol::NalgebraMat<f32>, ObjectModule>::from_serialized_diffsl(
321                    serialized_diffsl,
322                )?,
323            ),
324            ScalarType::F64 => Box::new(
325                DiffSlSolve::<diffsol::NalgebraMat<f64>, ObjectModule>::from_serialized_diffsl(
326                    serialized_diffsl,
327                )?,
328            ),
329        },
330        MatrixType::FaerDense => match scalar_type {
331            ScalarType::F32 => Box::new(
332                DiffSlSolve::<diffsol::FaerMat<f32>, ObjectModule>::from_serialized_diffsl(
333                    serialized_diffsl,
334                )?,
335            ),
336            ScalarType::F64 => Box::new(
337                DiffSlSolve::<diffsol::FaerMat<f64>, ObjectModule>::from_serialized_diffsl(
338                    serialized_diffsl,
339                )?,
340            ),
341        },
342        MatrixType::FaerSparse => match scalar_type {
343            ScalarType::F32 => Box::new(
344                DiffSlSolve::<diffsol::FaerSparseMat<f32>, ObjectModule>::from_serialized_diffsl(
345                    serialized_diffsl,
346                )?,
347            ),
348            ScalarType::F64 => Box::new(
349                DiffSlSolve::<diffsol::FaerSparseMat<f64>, ObjectModule>::from_serialized_diffsl(
350                    serialized_diffsl,
351                )?,
352            ),
353        },
354    };
355    Ok(solve)
356}
357
358#[cfg(feature = "external")]
359impl<M> DiffSlSolve<M, diffsl::ExternalModule<M::T>>
360where
361    M: Matrix<T: ExternalScalar>,
362    M::V: Vector + DefaultDenseMatrix,
363{
364    pub fn from_external(
365        deps: diffsol::DiffSlDeps,
366        include_sensitivities: bool,
367    ) -> Result<Self, DiffsolRtError> {
368        let eqn = DiffSl::<M, diffsl::ExternalModule<M::T>>::from_external(
369            M::C::default(),
370            deps,
371            include_sensitivities,
372        )?;
373        Self::from_eqn(eqn)
374    }
375}
376
377#[cfg(feature = "diffsl-external-dynamic")]
378impl<M> DiffSlSolve<M, diffsl::ExternalDynModule<M::T>>
379where
380    M: Matrix<T: Scalar>,
381    M::V: Vector + DefaultDenseMatrix,
382{
383    pub fn from_external_dynamic(
384        path: impl Into<PathBuf>,
385        deps: diffsol::DiffSlDeps,
386        include_sensitivities: bool,
387    ) -> Result<Self, DiffsolRtError> {
388        let eqn = DiffSl::<M, diffsl::ExternalDynModule<M::T>>::from_external_dynamic(
389            path,
390            M::C::default(),
391            deps,
392            include_sensitivities,
393        )?;
394        Self::from_eqn(eqn)
395    }
396}
397
398impl<M> DiffSlSolve<M, ObjectModule>
399where
400    M: Matrix<T: Scalar>,
401    M::V: Vector + DefaultDenseMatrix,
402{
403    pub fn from_serialized_diffsl(serialized_diffsl: &[u8]) -> Result<Self, DiffsolRtError> {
404        let eqn = serde_json::from_slice::<DiffSl<M, ObjectModule>>(serialized_diffsl)
405            .map_err(|e| DiffsolRtError::from(DiffsolError::Other(e.to_string())))?;
406        Self::from_eqn(eqn)
407    }
408}
409
410fn serialize_json<T: serde::Serialize>(eqn: &T) -> Result<Vec<u8>, DiffsolRtError> {
411    serde_json::to_vec(eqn).map_err(|e| DiffsolRtError::from(DiffsolError::Other(e.to_string())))
412}
413
414/// The requirement check of DiffSL equations, which is the same for each backend.
415fn check_diffsl_requirement(requirement: EqnRequirement) -> Result<(), DiffsolRtError> {
416    // sensitivity analysis of DiffSL equations is not supported on Windows
417    let needs_sens = matches!(
418        requirement,
419        EqnRequirement::FwdSens | EqnRequirement::Adjoint(_)
420    );
421    if needs_sens && cfg!(target_os = "windows") {
422        return Err(DiffsolError::Other(
423            "Sensitivity analysis is not supported on Windows, please use a linux or macOS system."
424                .to_string(),
425        )
426        .into());
427    }
428    Ok(())
429}
430
431/// Implement [`SolveEqn`] for `DiffSl<M, $cg>`, where the scalar type of `M` satisfies
432/// `$scalar`. The serialization depends on the backend `$cg`, so each backend gives its own
433/// `serialized` body, with `$eqn` the equations.
434macro_rules! impl_solve_eqn_for_diffsl {
435    ($cg:ty, $scalar:path, |$eqn:ident| $serialized:expr) => {
436        impl<M> SolveEqn for DiffSl<M, $cg>
437        where
438            M: Matrix<T: $scalar>,
439            M::V: Vector + DefaultDenseMatrix,
440        {
441            fn serialized(&self) -> Result<Vec<u8>, DiffsolRtError> {
442                let $eqn = self;
443                $serialized
444            }
445
446            fn check_requirement(&self, requirement: EqnRequirement) -> Result<(), DiffsolRtError> {
447                check_diffsl_requirement(requirement)
448            }
449        }
450    };
451}
452
453#[cfg(feature = "diffsl-llvm")]
454impl_solve_eqn_for_diffsl!(diffsol::LlvmModule, Scalar, |eqn| serialize_json(eqn));
455
456impl_solve_eqn_for_diffsl!(diffsol::ObjectModule, Scalar, |eqn| serialize_json(eqn));
457
458#[cfg(feature = "diffsl-cranelift")]
459impl_solve_eqn_for_diffsl!(diffsol::CraneliftJitModule, Scalar, |_eqn| unsupported(
460    "ODE serialization is not supported for Cranelift-backed solvers"
461));
462
463#[cfg(feature = "external")]
464impl_solve_eqn_for_diffsl!(diffsl::ExternalModule<M::T>, ExternalScalar, |_eqn| {
465    unsupported("ODE serialization is only supported for JIT-backed solvers")
466});
467
468#[cfg(feature = "diffsl-external-dynamic")]
469impl_solve_eqn_for_diffsl!(diffsl::ExternalDynModule<M::T>, Scalar, |_eqn| unsupported(
470    "ODE serialization is only supported for JIT-backed solvers"
471));
472
473#[cfg(all(test, any(feature = "diffsl-cranelift", feature = "diffsl-llvm")))]
474mod tests {
475    use diffsol::MatrixCommon;
476    use diffsol::{
477        CodegenModuleCompile, CodegenModuleJit, Context, OdeBuilder, OdeEquations, Vector,
478    };
479
480    #[cfg(feature = "diffsl-llvm")]
481    use crate::test_support::{hybrid_logistic_state_dr, logistic_state_dr};
482    use crate::{
483        host_array::FromHostArray,
484        linear_solver_type::LinearSolverType,
485        matrix_type::MatrixType,
486        ode_solver_type::OdeSolverType,
487        scalar_type::ScalarType,
488        test_support::{
489            assert_close, hybrid_logistic_diffsl_code, hybrid_logistic_state, logistic_diffsl_code,
490            logistic_state, two_param_logistic_diffsl_code, LOGISTIC_X0,
491        },
492    };
493
494    use super::{solve_factory_with_jit_backend, DiffSlSolve};
495    use crate::solve::{GenericSolve, Solve};
496    use crate::solve_eqn::SolveEqn;
497
498    fn make_generic_solve<T, CG>() -> DiffSlSolve<diffsol::NalgebraMat<T>, CG>
499    where
500        T: crate::scalar_type::Scalar + diffsol::NalgebraScalar,
501        CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
502        diffsol::DiffSl<diffsol::NalgebraMat<T>, CG>: SolveEqn,
503        diffsol::NalgebraMat<T>: diffsol::matrix::Matrix,
504        <diffsol::NalgebraMat<T> as MatrixCommon>::T: crate::scalar_type::Scalar,
505        <diffsol::NalgebraMat<T> as MatrixCommon>::V: diffsol::DefaultDenseMatrix,
506    {
507        let problem = OdeBuilder::<diffsol::NalgebraMat<T>>::new()
508            .build_from_diffsl::<CG>(logistic_diffsl_code())
509            .unwrap();
510        GenericSolve { problem }
511    }
512
513    fn assert_factory_supports_all_matrix_and_scalar_types<CG>()
514    where
515        CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
516        diffsol::DiffSl<diffsol::NalgebraMat<f32>, CG>: SolveEqn,
517        diffsol::DiffSl<diffsol::NalgebraMat<f64>, CG>: SolveEqn,
518        diffsol::DiffSl<diffsol::FaerMat<f32>, CG>: SolveEqn,
519        diffsol::DiffSl<diffsol::FaerMat<f64>, CG>: SolveEqn,
520        diffsol::DiffSl<diffsol::FaerSparseMat<f32>, CG>: SolveEqn,
521        diffsol::DiffSl<diffsol::FaerSparseMat<f64>, CG>: SolveEqn,
522    {
523        for matrix_type in [
524            MatrixType::NalgebraDense,
525            MatrixType::FaerDense,
526            MatrixType::FaerSparse,
527        ] {
528            for scalar_type in [ScalarType::F32, ScalarType::F64] {
529                assert!(solve_factory_with_jit_backend::<CG>(
530                    logistic_diffsl_code(),
531                    matrix_type,
532                    scalar_type,
533                )
534                .is_ok());
535            }
536        }
537    }
538
539    fn assert_solve_metadata_and_helpers<T, CG>(tol: f64)
540    where
541        T: crate::scalar_type::Scalar + diffsol::NalgebraScalar,
542        CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
543        diffsol::DiffSl<diffsol::NalgebraMat<T>, CG>: SolveEqn,
544        diffsol::NalgebraMat<T>: diffsol::matrix::Matrix,
545        <diffsol::NalgebraMat<T> as MatrixCommon>::T: crate::scalar_type::Scalar,
546        <diffsol::NalgebraMat<T> as MatrixCommon>::V: diffsol::DefaultDenseMatrix,
547        DiffSlSolve<diffsol::NalgebraMat<T>, CG>: Solve,
548    {
549        let mut solve = make_generic_solve::<T, CG>();
550        assert_eq!(solve.matrix_type(), MatrixType::NalgebraDense);
551        assert_eq!(solve.nstates(), 1);
552        assert_eq!(solve.nparams(), 1);
553        assert_eq!(solve.nout(), 1);
554        assert!(!solve.has_stop());
555        assert!(solve.check(LinearSolverType::Default).is_ok());
556        assert!(solve.check(LinearSolverType::Lu).is_ok());
557        assert!(solve.check(LinearSolverType::Klu).is_err());
558
559        solve.set_atol(1e-5);
560        solve.set_rtol(1e-4);
561        assert_close(solve.atol(), 1e-5, tol, "solve atol");
562        assert_close(solve.rtol(), 1e-4, tol, "solve rtol");
563        solve.set_t0(0.125);
564        solve.set_h0(0.25);
565        solve.set_integrate_out(true);
566        assert_close(solve.t0(), 0.125, tol, "solve t0");
567        assert_close(solve.h0(), 0.25, tol, "solve h0");
568        assert!(solve.integrate_out());
569
570        solve.set_sens_rtol(Some(1e-3));
571        solve.set_sens_atol(Some(1e-4));
572        solve.set_out_rtol(Some(2e-3));
573        solve.set_out_atol(Some(2e-4));
574        solve.set_param_rtol(Some(3e-3));
575        solve.set_param_atol(Some(3e-4));
576        assert_close(solve.sens_rtol().unwrap(), 1e-3, tol, "solve sens rtol");
577        assert_close(solve.sens_atol().unwrap(), 1e-4, tol, "solve sens atol");
578        assert_close(solve.out_rtol().unwrap(), 2e-3, tol, "solve out rtol");
579        assert_close(solve.out_atol().unwrap(), 2e-4, tol, "solve out atol");
580        assert_close(solve.param_rtol().unwrap(), 3e-3, tol, "solve param rtol");
581        assert_close(solve.param_atol().unwrap(), 3e-4, tol, "solve param atol");
582
583        solve.set_sens_rtol(None);
584        solve.set_sens_atol(None);
585        solve.set_out_rtol(None);
586        solve.set_out_atol(None);
587        solve.set_param_rtol(None);
588        solve.set_param_atol(None);
589        assert_eq!(solve.sens_rtol(), None);
590        assert_eq!(solve.sens_atol(), None);
591        assert_eq!(solve.out_rtol(), None);
592        assert_eq!(solve.out_atol(), None);
593        assert_eq!(solve.param_rtol(), None);
594        assert_eq!(solve.param_atol(), None);
595
596        let y0 = Vec::<f64>::from_host_array(solve.y0(&[2.0]).unwrap()).unwrap();
597        assert_eq!(y0.len(), 1);
598        assert_close(y0[0], LOGISTIC_X0, tol, "y0[0]");
599
600        let rhs = Vec::<f64>::from_host_array(solve.rhs(&[2.0], 0.0, &[0.25]).unwrap()).unwrap();
601        assert_eq!(rhs.len(), 1);
602        assert_close(rhs[0], 0.375, tol, "solve rhs");
603
604        let jac_mul =
605            Vec::<f64>::from_host_array(solve.rhs_jac_mul(&[2.0], 0.0, &[0.25], &[3.0]).unwrap())
606                .unwrap();
607        assert_eq!(jac_mul.len(), 1);
608        assert_close(jac_mul[0], 3.0, tol, "solve rhs jac mul");
609    }
610
611    fn assert_solve_runtime_paths<T, CG>(tol: f64)
612    where
613        T: crate::scalar_type::Scalar + diffsol::NalgebraScalar,
614        CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
615        diffsol::DiffSl<diffsol::NalgebraMat<T>, CG>: SolveEqn,
616        diffsol::NalgebraMat<T>: diffsol::matrix::Matrix,
617        <diffsol::NalgebraMat<T> as MatrixCommon>::T: crate::scalar_type::Scalar,
618        <diffsol::NalgebraMat<T> as MatrixCommon>::V: diffsol::DefaultDenseMatrix,
619        DiffSlSolve<diffsol::NalgebraMat<T>, CG>: Solve,
620    {
621        let mut solve = make_generic_solve::<T, CG>();
622        let soln = solve
623            .solve(OdeSolverType::Bdf, LinearSolverType::Lu, &[2.0], 1.0)
624            .unwrap();
625        let ts = Vec::<f64>::from_host_array(soln.get_ts()).unwrap();
626        let ys = Vec::<Vec<f64>>::from_host_array(soln.get_ys()).unwrap();
627        assert_close(*ts.last().unwrap(), 1.0, tol, "solve final time");
628        assert_close(
629            ys[0][ts.len() - 1],
630            logistic_state(LOGISTIC_X0, 2.0, 1.0),
631            tol,
632            "solve final value",
633        );
634
635        let mut solve = make_generic_solve::<T, CG>();
636        let dense = solve
637            .solve_dense(
638                OdeSolverType::Tsit45,
639                LinearSolverType::Lu,
640                &[2.0],
641                &[0.25, 0.5, 1.0],
642            )
643            .unwrap();
644        let ts = Vec::<f64>::from_host_array(dense.get_ts()).unwrap();
645        let ys = Vec::<Vec<f64>>::from_host_array(dense.get_ys()).unwrap();
646        assert_eq!(ts, vec![0.25, 0.5, 1.0]);
647        for (i, &t) in ts.iter().enumerate() {
648            assert_close(
649                ys[0][i],
650                logistic_state(LOGISTIC_X0, 2.0, t),
651                tol,
652                &format!("solve_dense[{i}]"),
653            );
654        }
655
656        let mut solve = make_generic_solve::<T, CG>();
657        let err = match solve.solve(OdeSolverType::Bdf, LinearSolverType::Default, &[], 1.0) {
658            Ok(_) => panic!("expected parameter count mismatch"),
659            Err(err) => err,
660        };
661        assert!(err.to_string().contains("Expecting 1 params but got 0"));
662
663        let hybrid_problem = OdeBuilder::<diffsol::NalgebraMat<T>>::new()
664            .build_from_diffsl::<CG>(hybrid_logistic_diffsl_code())
665            .unwrap();
666        let mut hybrid_solve = GenericSolve {
667            problem: hybrid_problem,
668        };
669        let hybrid = hybrid_solve
670            .solve(OdeSolverType::Bdf, LinearSolverType::Lu, &[2.0], 2.0)
671            .unwrap();
672        let hybrid_ts = Vec::<f64>::from_host_array(hybrid.get_ts()).unwrap();
673        let hybrid_ys = Vec::<Vec<f64>>::from_host_array(hybrid.get_ys()).unwrap();
674        assert_close(*hybrid_ts.last().unwrap(), 2.0, tol, "solve final time");
675        assert_close(
676            hybrid_ys[0][hybrid_ts.len() - 1],
677            hybrid_logistic_state(2.0, 2.0),
678            tol,
679            "solve final value",
680        );
681
682        let hybrid_problem = OdeBuilder::<diffsol::NalgebraMat<T>>::new()
683            .build_from_diffsl::<CG>(hybrid_logistic_diffsl_code())
684            .unwrap();
685        let mut hybrid_solve = GenericSolve {
686            problem: hybrid_problem,
687        };
688        let hybrid_dense = hybrid_solve
689            .solve_dense(
690                OdeSolverType::Tsit45,
691                LinearSolverType::Lu,
692                &[2.0],
693                &[0.5, 1.0, 1.5, 2.0],
694            )
695            .unwrap();
696        let hybrid_dense_ts = Vec::<f64>::from_host_array(hybrid_dense.get_ts()).unwrap();
697        let hybrid_dense_ys = Vec::<Vec<f64>>::from_host_array(hybrid_dense.get_ys()).unwrap();
698        assert_eq!(hybrid_dense_ts, vec![0.5, 1.0, 1.5, 2.0]);
699        for (i, &t) in hybrid_dense_ts.iter().enumerate() {
700            assert_close(
701                hybrid_dense_ys[0][i],
702                hybrid_logistic_state(2.0, t),
703                tol,
704                &format!("solve_dense[{i}]"),
705            );
706        }
707    }
708
709    #[cfg(feature = "diffsl-llvm")]
710    fn assert_solve_sensitivity_paths() {
711        let t_eval = [0.25, 0.5, 1.0];
712
713        let mut solve = make_generic_solve::<f64, diffsol::LlvmModule>();
714        let sens = solve
715            .solve_fwd_sens(OdeSolverType::Bdf, LinearSolverType::Lu, &[2.0], &t_eval)
716            .unwrap();
717        let sens_values = sens.get_sens();
718        assert_eq!(sens_values.len(), 1);
719        let sens_matrix =
720            Vec::<Vec<f64>>::from_host_array(sens_values.into_iter().next().unwrap()).unwrap();
721        for (i, &t) in t_eval.iter().enumerate() {
722            assert_close(
723                sens_matrix[0][i],
724                logistic_state_dr(LOGISTIC_X0, 2.0, t),
725                5e-4,
726                &format!("solve_fwd_sens[{i}]"),
727            );
728        }
729
730        let hybrid_problem = OdeBuilder::<diffsol::NalgebraMat<f64>>::new()
731            .build_from_diffsl::<diffsol::LlvmModule>(hybrid_logistic_diffsl_code())
732            .unwrap();
733        let mut solve = GenericSolve {
734            problem: hybrid_problem,
735        };
736        let hybrid_sens = solve
737            .solve_fwd_sens(OdeSolverType::Bdf, LinearSolverType::Lu, &[2.0], &t_eval)
738            .unwrap();
739        let sens_values = hybrid_sens.get_sens();
740        let sens_matrix =
741            Vec::<Vec<f64>>::from_host_array(sens_values.into_iter().next().unwrap()).unwrap();
742        for (i, &t) in t_eval.iter().enumerate() {
743            assert_close(
744                sens_matrix[0][i],
745                hybrid_logistic_state_dr(2.0, t),
746                5e-4,
747                &format!("solve_fwd_sens[{i}]"),
748            );
749        }
750    }
751
752    #[cfg(feature = "diffsl-cranelift")]
753    fn assert_sens_atol_round_trips_with_multiple_params() {
754        // two params make sens_atol batched (nbatch = 2), which get_index rejects
755        let problem = OdeBuilder::<diffsol::NalgebraMat<f64>>::new()
756            .build_from_diffsl::<diffsol::CraneliftJitModule>(two_param_logistic_diffsl_code())
757            .unwrap();
758        let mut solve = GenericSolve { problem };
759        assert_eq!(solve.nparams(), 2);
760
761        solve.set_sens_atol(Some(1e-4));
762        let sens_atol = solve.problem.sens_atol.as_ref().unwrap();
763        assert_eq!(sens_atol.context().nbatch(), 2);
764        assert_eq!(sens_atol.len(), solve.nstates());
765        assert_close(solve.sens_atol().unwrap(), 1e-4, 1e-12, "sens atol");
766    }
767
768    #[cfg(feature = "diffsl-cranelift")]
769    #[test]
770    fn solve_factory_supports_all_jit_matrix_and_scalar_types_for_cranelift() {
771        assert_factory_supports_all_matrix_and_scalar_types::<diffsol::CraneliftJitModule>();
772    }
773
774    #[cfg(feature = "diffsl-cranelift")]
775    #[test]
776    fn solve_trait_helpers_and_runtime_paths_for_cranelift() {
777        assert_solve_metadata_and_helpers::<f64, diffsol::CraneliftJitModule>(1e-12);
778        assert_solve_runtime_paths::<f64, diffsol::CraneliftJitModule>(5e-4);
779        assert_solve_metadata_and_helpers::<f32, diffsol::CraneliftJitModule>(5e-3);
780        assert_solve_runtime_paths::<f32, diffsol::CraneliftJitModule>(5e-3);
781    }
782
783    #[cfg(feature = "diffsl-cranelift")]
784    #[test]
785    fn sens_atol_round_trips_with_multiple_params_for_cranelift() {
786        assert_sens_atol_round_trips_with_multiple_params();
787    }
788
789    #[cfg(feature = "diffsl-llvm")]
790    #[test]
791    fn solve_trait_helpers_and_runtime_paths_for_llvm() {
792        assert_solve_metadata_and_helpers::<f64, diffsol::LlvmModule>(1e-12);
793        assert_solve_runtime_paths::<f64, diffsol::LlvmModule>(5e-4);
794        assert_solve_metadata_and_helpers::<f32, diffsol::LlvmModule>(5e-3);
795        assert_solve_runtime_paths::<f32, diffsol::LlvmModule>(5e-3);
796    }
797
798    #[cfg(feature = "diffsl-llvm")]
799    #[test]
800    fn solve_trait_sensitivity_paths_for_llvm() {
801        assert_solve_sensitivity_paths();
802    }
803
804    #[cfg(feature = "diffsl-cranelift")]
805    #[test]
806    fn setup_problem_validates_parameter_count_for_cranelift() {
807        let mut solve = make_generic_solve::<f64, diffsol::CraneliftJitModule>();
808        let err = solve.setup_problem(&[]).unwrap_err();
809        assert!(err.to_string().contains("Expecting 1 params but got 0"));
810
811        solve.setup_problem(&[2.0]).unwrap();
812        let mut params = solve.problem.context().vector_zeros(1);
813        solve.problem.eqn.get_params(&mut params);
814        assert_eq!(params.get_index(0), 2.0);
815    }
816
817    #[cfg(feature = "diffsl-llvm")]
818    #[test]
819    fn setup_problem_validates_parameter_count_for_llvm() {
820        let mut solve = make_generic_solve::<f64, diffsol::LlvmModule>();
821        let err = solve.setup_problem(&[]).unwrap_err();
822        assert!(err.to_string().contains("Expecting 1 params but got 0"));
823
824        solve.setup_problem(&[2.0]).unwrap();
825        let mut params = solve.problem.context().vector_zeros(1);
826        solve.problem.eqn.get_params(&mut params);
827        assert_eq!(params.get_index(0), 2.0);
828    }
829}