catlog_wasm/
theories.rs

1/*! Wasm bindings for double theories from the `catlog` standard library.
2
3Each struct in this module provides a [`DblTheory`] plus possibly
4theory-specific analysis methods.
5 */
6
7use std::sync::Arc;
8
9use ustr::ustr;
10use wasm_bindgen::prelude::*;
11
12use catlog::dbl::{model, theory};
13use catlog::one::fin_category::FinMor;
14use catlog::stdlib::{analyses, models, theories};
15
16use super::model_morphism::{MotifsOptions, motifs};
17use super::{analyses::*, model::DblModel, theory::DblTheory};
18
19/// The empty or initial theory.
20#[wasm_bindgen]
21pub struct ThEmpty(Arc<theory::UstrDiscreteDblTheory>);
22
23#[wasm_bindgen]
24impl ThEmpty {
25    #[wasm_bindgen(constructor)]
26    pub fn new() -> Self {
27        Self(Arc::new(theories::th_empty()))
28    }
29
30    #[wasm_bindgen]
31    pub fn theory(&self) -> DblTheory {
32        DblTheory(self.0.clone().into())
33    }
34}
35
36/// The theory of categories.
37#[wasm_bindgen]
38pub struct ThCategory(Arc<theory::UstrDiscreteDblTheory>);
39
40#[wasm_bindgen]
41impl ThCategory {
42    #[wasm_bindgen(constructor)]
43    pub fn new() -> Self {
44        Self(Arc::new(theories::th_category()))
45    }
46
47    #[wasm_bindgen]
48    pub fn theory(&self) -> DblTheory {
49        DblTheory(self.0.clone().into())
50    }
51}
52
53/// The theory of database schemas with attributes.
54#[wasm_bindgen]
55pub struct ThSchema(Arc<theory::UstrDiscreteDblTheory>);
56
57#[wasm_bindgen]
58impl ThSchema {
59    #[wasm_bindgen(constructor)]
60    pub fn new() -> Self {
61        Self(Arc::new(theories::th_schema()))
62    }
63
64    #[wasm_bindgen]
65    pub fn theory(&self) -> DblTheory {
66        DblTheory(self.0.clone().into())
67    }
68}
69
70/// The theory of signed categories.
71#[wasm_bindgen]
72pub struct ThSignedCategory(Arc<theory::UstrDiscreteDblTheory>);
73
74#[wasm_bindgen]
75impl ThSignedCategory {
76    #[wasm_bindgen(constructor)]
77    pub fn new() -> Self {
78        Self(Arc::new(theories::th_signed_category()))
79    }
80
81    #[wasm_bindgen]
82    pub fn theory(&self) -> DblTheory {
83        DblTheory(self.0.clone().into())
84    }
85
86    /// Find positive feedback loops in a model.
87    #[wasm_bindgen(js_name = "positiveLoops")]
88    pub fn positive_loops(
89        &self,
90        model: &DblModel,
91        options: MotifsOptions,
92    ) -> Result<Vec<DblModel>, String> {
93        let positive_loop = models::positive_loop(self.0.clone());
94        motifs(&positive_loop, model, options)
95    }
96
97    /// Find negative feedback loops in a model.
98    #[wasm_bindgen(js_name = "negativeLoops")]
99    pub fn negative_loops(
100        &self,
101        model: &DblModel,
102        options: MotifsOptions,
103    ) -> Result<Vec<DblModel>, String> {
104        let negative_loop = models::negative_loop(self.0.clone());
105        motifs(&negative_loop, model, options)
106    }
107
108    /// Simulate Lotka-Volterra system derived from a model.
109    #[wasm_bindgen(js_name = "lotkaVolterra")]
110    pub fn lotka_volterra(
111        &self,
112        model: &DblModel,
113        data: LotkaVolterraModelData,
114    ) -> Result<ODEResult, String> {
115        let model: &model::DiscreteDblModel<_, _> = (&model.0)
116            .try_into()
117            .map_err(|_| "Lotka-Volterra simulation expects a discrete double model")?;
118        Ok(ODEResult(
119            analyses::ode::LotkaVolterraAnalysis::new(ustr("Object"))
120                .add_positive(FinMor::Id(ustr("Object")))
121                .add_negative(FinMor::Generator(ustr("Negative")))
122                .create_system(model, data.0)
123                .solve_with_defaults()
124                .map_err(|err| format!("{:?}", err))
125                .into(),
126        ))
127    }
128}
129
130/// The theory of delayable signed categories.
131#[wasm_bindgen]
132pub struct ThDelayableSignedCategory(Arc<theory::UstrDiscreteDblTheory>);
133
134#[wasm_bindgen]
135impl ThDelayableSignedCategory {
136    #[wasm_bindgen(constructor)]
137    pub fn new() -> Self {
138        Self(Arc::new(theories::th_delayable_signed_category()))
139    }
140
141    #[wasm_bindgen]
142    pub fn theory(&self) -> DblTheory {
143        DblTheory(self.0.clone().into())
144    }
145
146    /// Find (fast) positive feedback loops in a model.
147    #[wasm_bindgen(js_name = "positiveLoops")]
148    pub fn positive_loops(
149        &self,
150        model: &DblModel,
151        options: MotifsOptions,
152    ) -> Result<Vec<DblModel>, String> {
153        let positive_loop = models::positive_loop(self.0.clone());
154        motifs(&positive_loop, model, options)
155    }
156
157    /// Find (fast) negative feedback loops in a model.
158    #[wasm_bindgen(js_name = "negativeLoops")]
159    pub fn negative_loops(
160        &self,
161        model: &DblModel,
162        options: MotifsOptions,
163    ) -> Result<Vec<DblModel>, String> {
164        let negative_loop = models::negative_loop(self.0.clone());
165        motifs(&negative_loop, model, options)
166    }
167
168    /// Find delayed positive feedback loops in a model.
169    #[wasm_bindgen(js_name = "delayedPositiveLoops")]
170    pub fn delayed_positive_loops(
171        &self,
172        model: &DblModel,
173        options: MotifsOptions,
174    ) -> Result<Vec<DblModel>, String> {
175        let delayed_positive_loop = models::delayed_positive_loop(self.0.clone());
176        motifs(&delayed_positive_loop, model, options)
177    }
178
179    /// Find delayed negative feedback loops in a model.
180    #[wasm_bindgen(js_name = "delayedNegativeLoops")]
181    pub fn delayed_negative_loops(
182        &self,
183        model: &DblModel,
184        options: MotifsOptions,
185    ) -> Result<Vec<DblModel>, String> {
186        let delayed_negative_loop = models::delayed_negative_loop(self.0.clone());
187        motifs(&delayed_negative_loop, model, options)
188    }
189}
190
191/// The theory of nullable signed categories.
192#[wasm_bindgen]
193pub struct ThNullableSignedCategory(Arc<theory::UstrDiscreteDblTheory>);
194
195#[wasm_bindgen]
196impl ThNullableSignedCategory {
197    #[wasm_bindgen(constructor)]
198    pub fn new() -> Self {
199        Self(Arc::new(theories::th_nullable_signed_category()))
200    }
201
202    #[wasm_bindgen]
203    pub fn theory(&self) -> DblTheory {
204        DblTheory(self.0.clone().into())
205    }
206}
207
208/// The theory of categories with scalars.
209#[wasm_bindgen]
210pub struct ThCategoryWithScalars(Arc<theory::UstrDiscreteDblTheory>);
211
212#[wasm_bindgen]
213impl ThCategoryWithScalars {
214    #[wasm_bindgen(constructor)]
215    pub fn new() -> Self {
216        Self(Arc::new(theories::th_category_with_scalars()))
217    }
218
219    #[wasm_bindgen]
220    pub fn theory(&self) -> DblTheory {
221        DblTheory(self.0.clone().into())
222    }
223}
224
225/// The theory of categories with links.
226#[wasm_bindgen]
227pub struct ThCategoryLinks(Arc<theory::UstrDiscreteTabTheory>);
228
229#[wasm_bindgen]
230impl ThCategoryLinks {
231    #[wasm_bindgen(constructor)]
232    pub fn new() -> Self {
233        Self(Arc::new(theories::th_category_links()))
234    }
235
236    #[wasm_bindgen]
237    pub fn theory(&self) -> DblTheory {
238        DblTheory(self.0.clone().into())
239    }
240
241    /// Simulates the mass-action system derived from a model.
242    #[wasm_bindgen(js_name = "massAction")]
243    pub fn mass_action(
244        &self,
245        model: &DblModel,
246        data: MassActionModelData,
247    ) -> Result<ODEResult, String> {
248        let model: &model::DiscreteTabModel<_, _, _> = (&model.0)
249            .try_into()
250            .map_err(|_| "Mass-action simulation expects a discrete tabulator model")?;
251        Ok(ODEResult(
252            analyses::ode::StockFlowMassActionAnalysis::default()
253                .create_numerical_system(model, data.0)
254                .solve_with_defaults()
255                .map_err(|err| format!("{:?}", err))
256                .into(),
257        ))
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264    use crate::theory::*;
265    use ustr::ustr;
266
267    #[test]
268    fn discrete_dbl_theory() {
269        let th = ThSchema::new().theory();
270        let entity = ObType::Basic(ustr("Entity"));
271        let attr_type = ObType::Basic(ustr("AttrType"));
272        let attr = MorType::Basic(ustr("Attr"));
273        assert_eq!(th.src(attr.clone()), Ok(entity));
274        assert_eq!(th.tgt(attr), Ok(attr_type));
275    }
276
277    #[test]
278    fn discrete_tab_theory() {
279        let th = ThCategoryLinks::new().theory();
280        let x = ObType::Basic(ustr("Object"));
281        let link = MorType::Basic(ustr("Link"));
282        assert_eq!(th.src(link.clone()), Ok(x));
283        assert!(matches!(th.tgt(link), Ok(ObType::Tabulator(_))));
284    }
285}