catlog/stdlib/analyses/ode/
mod.rs

1//! ODE analyses of models.
2
3use std::collections::HashMap;
4
5use derivative::Derivative;
6use derive_more::Constructor;
7use indexmap::IndexMap;
8use ode_solvers::dop_shared::IntegrationError;
9
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12#[cfg(feature = "serde-wasm")]
13use tsify::Tsify;
14
15use crate::simulate::ode::{ODEProblem, ODESystem};
16use crate::zero::{QualifiedName, alg::Polynomial};
17
18/// Symbolic parameter in a polynomial system.
19pub type Parameter<Id> = Polynomial<Id, f32, i8>;
20
21/// Solution to an ODE problem.
22#[derive(Clone, Derivative)]
23#[derivative(Default(bound = ""))]
24#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
25#[cfg_attr(feature = "serde-wasm", derive(Tsify))]
26#[cfg_attr(feature = "serde-wasm", tsify(into_wasm_abi, from_wasm_abi))]
27pub struct ODESolution {
28    /// Values of time variable for the duration of the simulation.
29    pub(in crate::stdlib::analyses) time: Vec<f32>,
30
31    /// Values of state variables for the duration of the simulation.
32    pub(in crate::stdlib::analyses) states: HashMap<QualifiedName, Vec<f32>>,
33}
34
35/// Data needed to simulate and interpret an ODE analysis of a model.
36#[derive(Constructor)]
37pub struct ODEAnalysis<Sys> {
38    /// ODE problem for the analysis.
39    pub problem: ODEProblem<Sys>,
40
41    /// Map from IDs in model (usually object IDs) to variable indices.
42    pub variable_index: IndexMap<QualifiedName, usize>,
43}
44
45impl<Sys> ODEAnalysis<Sys> {
46    /// Solves the ODE with reasonable default settings and collects results.
47    pub fn solve_with_defaults(self) -> Result<ODESolution, IntegrationError>
48    where
49        Sys: ODESystem,
50    {
51        // ODE solver will fail in the degenerate case of an empty system.
52        if self.variable_index.is_empty() {
53            return Ok(Default::default());
54        }
55
56        let duration = self.problem.end_time - self.problem.start_time;
57        let output_step_size = (duration / 100.0).min(0.01f32);
58        let result = self.problem.solve_dopri5(output_step_size)?;
59
60        let (t_out, x_out) = result.get();
61        Ok(ODESolution {
62            time: t_out.clone(),
63            states: self
64                .variable_index
65                .into_iter()
66                .map(|(ob, i)| (ob, x_out.iter().map(|x| x[i]).collect()))
67                .collect(),
68        })
69    }
70}
71
72pub mod kuramoto;
73pub mod linear_ode;
74pub mod lotka_volterra;
75pub mod mass_action;
76pub mod polynomial_ode;
77pub mod signed_coefficients;
78
79pub use kuramoto::*;
80pub use linear_ode::*;
81pub use lotka_volterra::*;
82pub use mass_action::*;
83pub use polynomial_ode::*;
84pub use signed_coefficients::*;