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(_) | EqnRequirement::Vjp(_)
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 linear_solver_type::LinearSolverType,
484 matrix_type::MatrixType,
485 ode_solver_type::OdeSolverType,
486 scalar_type::ScalarType,
487 test_support::{
488 assert_close, hybrid_logistic_diffsl_code, hybrid_logistic_state, lane_f64,
489 lane_matrix, logistic_diffsl_code, logistic_state, two_param_logistic_diffsl_code,
490 vector_tensor, LOGISTIC_X0,
491 },
492 };
493
494 use super::{solve_factory_with_jit_backend, DiffSlSolve};
495
496 use crate::solve::{GenericSolve, Solve};
497 use crate::solve_eqn::SolveEqn;
498
499 fn make_generic_solve<T, CG>() -> DiffSlSolve<diffsol::NalgebraMat<T>, CG>
500 where
501 T: crate::scalar_type::Scalar + diffsol::NalgebraScalar,
502 CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
503 diffsol::DiffSl<diffsol::NalgebraMat<T>, CG>: SolveEqn,
504 diffsol::NalgebraMat<T>: diffsol::matrix::Matrix,
505 <diffsol::NalgebraMat<T> as MatrixCommon>::T: crate::scalar_type::Scalar,
506 <diffsol::NalgebraMat<T> as MatrixCommon>::V: diffsol::DefaultDenseMatrix,
507 {
508 let problem = OdeBuilder::<diffsol::NalgebraMat<T>>::new()
509 .build_from_diffsl::<CG>(logistic_diffsl_code())
510 .unwrap();
511 GenericSolve { problem }
512 }
513
514 fn assert_factory_supports_all_matrix_and_scalar_types<CG>()
515 where
516 CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
517 diffsol::DiffSl<diffsol::NalgebraMat<f32>, CG>: SolveEqn,
518 diffsol::DiffSl<diffsol::NalgebraMat<f64>, CG>: SolveEqn,
519 diffsol::DiffSl<diffsol::FaerMat<f32>, CG>: SolveEqn,
520 diffsol::DiffSl<diffsol::FaerMat<f64>, CG>: SolveEqn,
521 diffsol::DiffSl<diffsol::FaerSparseMat<f32>, CG>: SolveEqn,
522 diffsol::DiffSl<diffsol::FaerSparseMat<f64>, CG>: SolveEqn,
523 {
524 for matrix_type in [
525 MatrixType::NalgebraDense,
526 MatrixType::FaerDense,
527 MatrixType::FaerSparse,
528 ] {
529 for scalar_type in [ScalarType::F32, ScalarType::F64] {
530 assert!(solve_factory_with_jit_backend::<CG>(
531 logistic_diffsl_code(),
532 matrix_type,
533 scalar_type,
534 )
535 .is_ok());
536 }
537 }
538 }
539
540 fn assert_solve_metadata_and_helpers<T, CG>(tol: f64)
541 where
542 T: crate::scalar_type::Scalar + diffsol::NalgebraScalar,
543 CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
544 diffsol::DiffSl<diffsol::NalgebraMat<T>, CG>: SolveEqn,
545 diffsol::NalgebraMat<T>: diffsol::matrix::Matrix,
546 <diffsol::NalgebraMat<T> as MatrixCommon>::T: crate::scalar_type::Scalar,
547 <diffsol::NalgebraMat<T> as MatrixCommon>::V: diffsol::DefaultDenseMatrix,
548 DiffSlSolve<diffsol::NalgebraMat<T>, CG>: Solve,
549 {
550 let mut solve = make_generic_solve::<T, CG>();
551 assert_eq!(solve.matrix_type(), MatrixType::NalgebraDense);
552 assert_eq!(solve.nstates(), 1);
553 assert_eq!(solve.nparams(), 1);
554 assert_eq!(solve.nout(), 1);
555 assert!(!solve.has_stop());
556 assert!(solve.check(LinearSolverType::Default).is_ok());
557 assert!(solve.check(LinearSolverType::Lu).is_ok());
558 assert!(solve.check(LinearSolverType::Klu).is_err());
559
560 solve.set_atol(1e-5);
561 solve.set_rtol(1e-4);
562 assert_close(solve.atol(), 1e-5, tol, "solve atol");
563 assert_close(solve.rtol(), 1e-4, tol, "solve rtol");
564 solve.set_t0(0.125);
565 solve.set_h0(0.25);
566 solve.set_integrate_out(true);
567 assert_close(solve.t0(), 0.125, tol, "solve t0");
568 assert_close(solve.h0(), 0.25, tol, "solve h0");
569 assert!(solve.integrate_out());
570
571 solve.set_sens_rtol(Some(1e-3));
572 solve.set_sens_atol(Some(1e-4));
573 solve.set_out_rtol(Some(2e-3));
574 solve.set_out_atol(Some(2e-4));
575 solve.set_param_rtol(Some(3e-3));
576 solve.set_param_atol(Some(3e-4));
577 assert_close(solve.sens_rtol().unwrap(), 1e-3, tol, "solve sens rtol");
578 assert_close(solve.sens_atol().unwrap(), 1e-4, tol, "solve sens atol");
579 assert_close(solve.out_rtol().unwrap(), 2e-3, tol, "solve out rtol");
580 assert_close(solve.out_atol().unwrap(), 2e-4, tol, "solve out atol");
581 assert_close(solve.param_rtol().unwrap(), 3e-3, tol, "solve param rtol");
582 assert_close(solve.param_atol().unwrap(), 3e-4, tol, "solve param atol");
583
584 solve.set_sens_rtol(None);
585 solve.set_sens_atol(None);
586 solve.set_out_rtol(None);
587 solve.set_out_atol(None);
588 solve.set_param_rtol(None);
589 solve.set_param_atol(None);
590 assert_eq!(solve.sens_rtol(), None);
591 assert_eq!(solve.sens_atol(), None);
592 assert_eq!(solve.out_rtol(), None);
593 assert_eq!(solve.out_atol(), None);
594 assert_eq!(solve.param_rtol(), None);
595 assert_eq!(solve.param_atol(), None);
596
597 let y0 = lane_f64(&solve.y0(vector_tensor(&[2.0]).view()).unwrap(), 0);
598 assert_eq!(y0.len(), 1);
599 assert_close(y0[0], LOGISTIC_X0, tol, "y0[0]");
600
601 let rhs = lane_f64(
602 &solve
603 .rhs(
604 vector_tensor(&[2.0]).view(),
605 0.0,
606 vector_tensor(&[0.25]).view(),
607 )
608 .unwrap(),
609 0,
610 );
611 assert_eq!(rhs.len(), 1);
612 assert_close(rhs[0], 0.375, tol, "solve rhs");
613
614 let jac_mul = lane_f64(
615 &solve
616 .rhs_jac_mul(
617 vector_tensor(&[2.0]).view(),
618 0.0,
619 vector_tensor(&[0.25]).view(),
620 vector_tensor(&[3.0]).view(),
621 )
622 .unwrap(),
623 0,
624 );
625 assert_eq!(jac_mul.len(), 1);
626 assert_close(jac_mul[0], 3.0, tol, "solve rhs jac mul");
627 }
628
629 fn assert_solve_runtime_paths<T, CG>(tol: f64)
630 where
631 T: crate::scalar_type::Scalar + diffsol::NalgebraScalar,
632 CG: diffsol::CodegenModule + CodegenModuleJit + CodegenModuleCompile,
633 diffsol::DiffSl<diffsol::NalgebraMat<T>, CG>: SolveEqn,
634 diffsol::NalgebraMat<T>: diffsol::matrix::Matrix,
635 <diffsol::NalgebraMat<T> as MatrixCommon>::T: crate::scalar_type::Scalar,
636 <diffsol::NalgebraMat<T> as MatrixCommon>::V: diffsol::DefaultDenseMatrix,
637 DiffSlSolve<diffsol::NalgebraMat<T>, CG>: Solve,
638 {
639 let mut solve = make_generic_solve::<T, CG>();
640 let soln = solve
641 .solve(
642 OdeSolverType::Bdf,
643 LinearSolverType::Lu,
644 vector_tensor(&[2.0]).view(),
645 1.0,
646 )
647 .unwrap();
648 let ts = lane_f64(&soln.clone().get_ts(), 0);
649 let ys = lane_matrix(&soln.clone().get_ys(), 0);
650 assert_close(*ts.last().unwrap(), 1.0, tol, "solve final time");
651 assert_close(
652 ys[(0, ts.len() - 1)],
653 logistic_state(LOGISTIC_X0, 2.0, 1.0),
654 tol,
655 "solve final value",
656 );
657
658 let mut solve = make_generic_solve::<T, CG>();
659 let dense = solve
660 .solve_dense(
661 OdeSolverType::Tsit45,
662 LinearSolverType::Lu,
663 vector_tensor(&[2.0]).view(),
664 vector_tensor(&[0.25, 0.5, 1.0]).view(),
665 )
666 .unwrap();
667 let ts = lane_f64(&dense.clone().get_ts(), 0);
668 let ys = lane_matrix(&dense.clone().get_ys(), 0);
669 assert_eq!(ts, vec![0.25, 0.5, 1.0]);
670 for (i, &t) in ts.iter().enumerate() {
671 assert_close(
672 ys[(0, i)],
673 logistic_state(LOGISTIC_X0, 2.0, t),
674 tol,
675 &format!("solve_dense[{i}]"),
676 );
677 }
678
679 let mut solve = make_generic_solve::<T, CG>();
680 let err = match solve.solve(
681 OdeSolverType::Bdf,
682 LinearSolverType::Default,
683 vector_tensor(&[]).view(),
684 1.0,
685 ) {
686 Ok(_) => panic!("expected parameter count mismatch"),
687 Err(err) => err,
688 };
689 assert!(err
690 .to_string()
691 .contains("params: Expected 1 values in each lane, got 0"));
692
693 let hybrid_problem = OdeBuilder::<diffsol::NalgebraMat<T>>::new()
694 .build_from_diffsl::<CG>(hybrid_logistic_diffsl_code())
695 .unwrap();
696 let mut hybrid_solve = GenericSolve {
697 problem: hybrid_problem,
698 };
699 let hybrid = hybrid_solve
700 .solve(
701 OdeSolverType::Bdf,
702 LinearSolverType::Lu,
703 vector_tensor(&[2.0]).view(),
704 2.0,
705 )
706 .unwrap();
707 let hybrid_ts = lane_f64(&hybrid.clone().get_ts(), 0);
708 let hybrid_ys = lane_matrix(&hybrid.clone().get_ys(), 0);
709 assert_close(*hybrid_ts.last().unwrap(), 2.0, tol, "solve final time");
710 assert_close(
711 hybrid_ys[(0, hybrid_ts.len() - 1)],
712 hybrid_logistic_state(2.0, 2.0),
713 tol,
714 "solve final value",
715 );
716
717 let hybrid_problem = OdeBuilder::<diffsol::NalgebraMat<T>>::new()
718 .build_from_diffsl::<CG>(hybrid_logistic_diffsl_code())
719 .unwrap();
720 let mut hybrid_solve = GenericSolve {
721 problem: hybrid_problem,
722 };
723 let hybrid_dense = hybrid_solve
724 .solve_dense(
725 OdeSolverType::Tsit45,
726 LinearSolverType::Lu,
727 vector_tensor(&[2.0]).view(),
728 vector_tensor(&[0.5, 1.0, 1.5, 2.0]).view(),
729 )
730 .unwrap();
731 let hybrid_dense_ts = lane_f64(&hybrid_dense.clone().get_ts(), 0);
732 let hybrid_dense_ys = lane_matrix(&hybrid_dense.clone().get_ys(), 0);
733 assert_eq!(hybrid_dense_ts, vec![0.5, 1.0, 1.5, 2.0]);
734 for (i, &t) in hybrid_dense_ts.iter().enumerate() {
735 assert_close(
736 hybrid_dense_ys[(0, i)],
737 hybrid_logistic_state(2.0, t),
738 tol,
739 &format!("solve_dense[{i}]"),
740 );
741 }
742 }
743
744 #[cfg(feature = "diffsl-llvm")]
745 fn assert_solve_sensitivity_paths() {
746 let t_eval = [0.25, 0.5, 1.0];
747
748 let mut solve = make_generic_solve::<f64, diffsol::LlvmModule>();
749 let sens = solve
750 .solve_fwd_sens(
751 OdeSolverType::Bdf,
752 LinearSolverType::Lu,
753 vector_tensor(&[2.0]).view(),
754 vector_tensor(&t_eval).view(),
755 )
756 .unwrap();
757 let sens_values = sens.clone().get_sens();
758 assert_eq!(sens_values.len(), 1);
759 let sens_matrix = lane_matrix(&sens_values.into_iter().next().unwrap(), 0);
760 for (i, &t) in t_eval.iter().enumerate() {
761 assert_close(
762 sens_matrix[(0, i)],
763 logistic_state_dr(LOGISTIC_X0, 2.0, t),
764 5e-4,
765 &format!("solve_fwd_sens[{i}]"),
766 );
767 }
768
769 let hybrid_problem = OdeBuilder::<diffsol::NalgebraMat<f64>>::new()
770 .build_from_diffsl::<diffsol::LlvmModule>(hybrid_logistic_diffsl_code())
771 .unwrap();
772 let mut solve = GenericSolve {
773 problem: hybrid_problem,
774 };
775 let hybrid_sens = solve
776 .solve_fwd_sens(
777 OdeSolverType::Bdf,
778 LinearSolverType::Lu,
779 vector_tensor(&[2.0]).view(),
780 vector_tensor(&t_eval).view(),
781 )
782 .unwrap();
783 let sens_values = hybrid_sens.clone().get_sens();
784 let sens_matrix = lane_matrix(&sens_values.into_iter().next().unwrap(), 0);
785 for (i, &t) in t_eval.iter().enumerate() {
786 assert_close(
787 sens_matrix[(0, i)],
788 hybrid_logistic_state_dr(2.0, t),
789 5e-4,
790 &format!("solve_fwd_sens[{i}]"),
791 );
792 }
793 }
794
795 #[cfg(feature = "diffsl-cranelift")]
796 fn assert_sens_atol_round_trips_with_multiple_params() {
797 let problem = OdeBuilder::<diffsol::NalgebraMat<f64>>::new()
799 .build_from_diffsl::<diffsol::CraneliftJitModule>(two_param_logistic_diffsl_code())
800 .unwrap();
801 let mut solve = GenericSolve { problem };
802 assert_eq!(solve.nparams(), 2);
803
804 solve.set_sens_atol(Some(1e-4));
805 let sens_atol = solve.problem.sens_atol.as_ref().unwrap();
806 assert_eq!(sens_atol.context().nbatch(), 2);
807 assert_eq!(sens_atol.len(), solve.nstates());
808 assert_close(solve.sens_atol().unwrap(), 1e-4, 1e-12, "sens atol");
809 }
810
811 #[cfg(feature = "diffsl-cranelift")]
812 #[test]
813 fn solve_factory_supports_all_jit_matrix_and_scalar_types_for_cranelift() {
814 assert_factory_supports_all_matrix_and_scalar_types::<diffsol::CraneliftJitModule>();
815 }
816
817 #[cfg(feature = "diffsl-cranelift")]
818 #[test]
819 fn solve_trait_helpers_and_runtime_paths_for_cranelift() {
820 assert_solve_metadata_and_helpers::<f64, diffsol::CraneliftJitModule>(1e-12);
821 assert_solve_runtime_paths::<f64, diffsol::CraneliftJitModule>(5e-4);
822 assert_solve_metadata_and_helpers::<f32, diffsol::CraneliftJitModule>(5e-3);
823 assert_solve_runtime_paths::<f32, diffsol::CraneliftJitModule>(5e-3);
824 }
825
826 #[cfg(feature = "diffsl-cranelift")]
827 #[test]
828 fn sens_atol_round_trips_with_multiple_params_for_cranelift() {
829 assert_sens_atol_round_trips_with_multiple_params();
830 }
831
832 #[cfg(feature = "diffsl-llvm")]
833 #[test]
834 fn solve_trait_helpers_and_runtime_paths_for_llvm() {
835 assert_solve_metadata_and_helpers::<f64, diffsol::LlvmModule>(1e-12);
836 assert_solve_runtime_paths::<f64, diffsol::LlvmModule>(5e-4);
837 assert_solve_metadata_and_helpers::<f32, diffsol::LlvmModule>(5e-3);
838 assert_solve_runtime_paths::<f32, diffsol::LlvmModule>(5e-3);
839 }
840
841 #[cfg(feature = "diffsl-llvm")]
842 #[test]
843 fn solve_trait_sensitivity_paths_for_llvm() {
844 assert_solve_sensitivity_paths();
845 }
846
847 #[cfg(feature = "diffsl-cranelift")]
848 #[test]
849 fn setup_problem_validates_parameter_count_for_cranelift() {
850 let mut solve = make_generic_solve::<f64, diffsol::CraneliftJitModule>();
851 let err = solve.setup_problem(vector_tensor(&[]).view()).unwrap_err();
852 assert!(err
853 .to_string()
854 .contains("params: Expected 1 values in each lane, got 0"));
855
856 solve.setup_problem(vector_tensor(&[2.0]).view()).unwrap();
857 let mut params = solve.problem.context().vector_zeros(1);
858 solve.problem.eqn.get_params(&mut params);
859 assert_eq!(params.get_index(0), 2.0);
860 }
861
862 #[cfg(feature = "diffsl-llvm")]
863 #[test]
864 fn setup_problem_validates_parameter_count_for_llvm() {
865 let mut solve = make_generic_solve::<f64, diffsol::LlvmModule>();
866 let err = solve.setup_problem(vector_tensor(&[]).view()).unwrap_err();
867 assert!(err
868 .to_string()
869 .contains("params: Expected 1 values in each lane, got 0"));
870
871 solve.setup_problem(vector_tensor(&[2.0]).view()).unwrap();
872 let mut params = solve.problem.context().vector_zeros(1);
873 solve.problem.eqn.get_params(&mut params);
874 assert_eq!(params.get_index(0), 2.0);
875 }
876}