1#[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
26pub(crate) type DiffSlSolve<M, CG> = GenericSolve<M, DiffSl<M, CG>>;
28
29impl OdeWrapper {
30 #[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 #[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 #[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#[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 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 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
414fn check_diffsl_requirement(requirement: EqnRequirement) -> Result<(), DiffsolRtError> {
416 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
431macro_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 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}