catlog/stdlib/analyses/ode/
linear_ode.rs1use std::collections::HashMap;
7use std::hash::Hash;
8use std::ops::Add;
9
10use indexmap::IndexMap;
11use itertools::Itertools;
12use nalgebra::{DMatrix, DVector};
13use num_traits::Zero;
14
15#[cfg(feature = "serde")]
16use serde::{Deserialize, Serialize};
17#[cfg(feature = "serde-wasm")]
18use tsify::Tsify;
19
20use super::{ODEAnalysis, Parameter, SignedCoefficientBuilder};
21use crate::simulate::ode::{NumericalPolynomialSystem, ODEProblem, PolynomialSystem};
22use crate::{
23 dbl::model::DiscreteDblModel,
24 one::QualifiedPath,
25 zero::{QualifiedName, rig::Monomial},
26};
27
28#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
30#[cfg_attr(feature = "serde-wasm", derive(Tsify))]
31#[cfg_attr(
32 feature = "serde-wasm",
33 tsify(into_wasm_abi, from_wasm_abi, hashmap_as_object)
34)]
35pub struct LinearODEProblemData {
36 #[cfg_attr(feature = "serde", serde(rename = "coefficients"))]
38 coefficients: HashMap<QualifiedName, f32>,
39
40 #[cfg_attr(feature = "serde", serde(rename = "initialValues"))]
42 initial_values: HashMap<QualifiedName, f32>,
43
44 duration: f32,
46}
47
48pub fn linear_polynomial_system<Var, Coef>(
51 vars: &[Var],
52 coefficients: DMatrix<Coef>,
53) -> PolynomialSystem<Var, Coef, u8>
54where
55 Var: Clone + Hash + Ord,
56 Coef: Clone + Add<Output = Coef> + Zero,
57{
58 let system = PolynomialSystem {
59 components: coefficients
60 .row_iter()
61 .zip(vars)
62 .map(|(row, i)| {
63 (
64 i.clone(),
65 row.iter()
66 .zip(vars)
67 .map(|(a, j)| (a.clone(), Monomial::generator(j.clone())))
68 .collect(),
69 )
70 })
71 .collect(),
72 };
73 system.normalize()
74}
75
76impl SignedCoefficientBuilder<QualifiedName, QualifiedPath> {
77 pub fn linear_ode_analysis(
83 &self,
84 model: &DiscreteDblModel,
85 data: LinearODEProblemData,
86 ) -> ODEAnalysis<NumericalPolynomialSystem<u8>> {
87 let (system, ob_index) = self.linear_ode_system(model);
88 let n = ob_index.len();
89
90 let initial_values = ob_index
91 .keys()
92 .map(|ob| data.initial_values.get(ob).copied().unwrap_or_default());
93 let x0 = DVector::from_iterator(n, initial_values);
94
95 let system = system
96 .extend_scalars(|poly| {
97 poly.eval(|id| data.coefficients.get(id).copied().unwrap_or_default())
98 })
99 .to_numerical();
100 let problem = ODEProblem::new(system, x0).end_time(data.duration);
101 ODEAnalysis::new(problem, ob_index)
102 }
103
104 pub fn linear_ode_system(
106 &self,
107 model: &DiscreteDblModel,
108 ) -> (
109 PolynomialSystem<QualifiedName, Parameter<QualifiedName>, u8>,
110 IndexMap<QualifiedName, usize>,
111 ) {
112 let (matrix, ob_index) = self.build_matrix(model);
113 let system = linear_polynomial_system(&ob_index.keys().cloned().collect_vec(), matrix);
114 (system, ob_index)
115 }
116}
117
118#[cfg(test)]
119mod test {
120 use expect_test::expect;
121 use std::rc::Rc;
122
123 use super::*;
124 use crate::stdlib;
125 use crate::{one::Path, zero::name};
126
127 fn builder() -> SignedCoefficientBuilder<QualifiedName, QualifiedPath> {
128 SignedCoefficientBuilder::new(name("Object"))
129 .add_positive(Path::Id(name("Object")))
130 .add_negative(Path::single(name("Negative")))
131 }
132
133 #[test]
134 fn negative_feedback_symbolic() {
135 let th = Rc::new(stdlib::theories::th_signed_category());
136 let neg_feedback = stdlib::models::negative_feedback(th);
137 let (sys, _) = builder().linear_ode_system(&neg_feedback);
138 let expected = expect![[r#"
139 dx = -negative y
140 dy = positive x
141 "#]];
142 expected.assert_eq(&sys.to_string());
143 }
144
145 #[test]
146 fn negative_feedback_numerical() {
147 let th = Rc::new(stdlib::theories::th_signed_category());
148 let neg_feedback = stdlib::models::negative_feedback(th);
149
150 let data = LinearODEProblemData {
151 coefficients: [(name("positive"), 2.0), (name("negative"), 1.0)].into_iter().collect(),
152 initial_values: [(name("x"), 1.0), (name("y"), 1.0)].into_iter().collect(),
153 duration: 10.0,
154 };
155
156 let sys = builder().linear_ode_analysis(&neg_feedback, data).problem.system;
157 let expected = expect![[r#"
158 dx0 = -x1
159 dx1 = 2 x0
160 "#]];
161 expected.assert_eq(&sys.to_string());
162 }
163}