catlog/stdlib/
models.rs

1//! Standard library of models of double theories.
2
3use std::rc::Rc;
4
5use crate::dbl::{model::*, theory::*};
6use crate::one::{Path, QualifiedPath};
7use crate::zero::{QualifiedName, name};
8
9/// The positive self-loop.
10///
11/// A signed graph or free [signed category](super::theories::th_signed_category),
12/// possibly with delays or indeterminates.
13pub fn positive_loop(th: Rc<DiscreteDblTheory>) -> DiscreteDblModel {
14    loop_of_type(th, name("Object"), Path::Id(name("Object")))
15}
16
17/// The negative self-loop.
18///
19/// A signed graph or free [signed category](super::theories::th_signed_category),
20/// possibly with delays or indeterminates.
21pub fn negative_loop(th: Rc<DiscreteDblTheory>) -> DiscreteDblModel {
22    loop_of_type(th, name("Object"), name("Negative").into())
23}
24
25/// The delayed positive self-loop.
26///
27/// A free [delayable signed category](super::theories::th_delayable_signed_category).
28pub fn delayed_positive_loop(th: Rc<DiscreteDblTheory>) -> DiscreteDblModel {
29    loop_of_type(th, name("Object"), name("Slow").into())
30}
31
32/// The delayed negative self-loop.
33///
34/// A free [delayable signed category](super::theories::th_delayable_signed_category).
35pub fn delayed_negative_loop(th: Rc<DiscreteDblTheory>) -> DiscreteDblModel {
36    loop_of_type(th, name("Object"), Path::pair(name("Negative"), name("Slow")))
37}
38
39/// Creates a self-loop with given object and morphism types.
40fn loop_of_type(
41    th: Rc<DiscreteDblTheory>,
42    ob_type: QualifiedName,
43    mor_type: QualifiedPath,
44) -> DiscreteDblModel {
45    let mut model = DiscreteDblModel::new(th);
46    model.add_ob(name("x"), ob_type);
47    model.add_mor(name("loop"), name("x"), name("x"), mor_type);
48    model
49}
50
51/// The positive feedback loop between two objects.
52///
53/// A signed graph or free [signed category](super::theories::th_signed_category).
54pub fn positive_feedback(th: Rc<DiscreteDblTheory>) -> DiscreteDblModel {
55    let mut model = DiscreteDblModel::new(th);
56    model.add_ob(name("x"), name("Object"));
57    model.add_ob(name("y"), name("Object"));
58    model.add_mor(name("positive1"), name("x"), name("y"), Path::Id(name("Object")));
59    model.add_mor(name("positive2"), name("y"), name("x"), Path::Id(name("Object")));
60    model
61}
62
63/// The negative feedback loop between two objects.
64///
65/// A signed graph or free [signed category](super::theories::th_signed_category).
66pub fn negative_feedback(th: Rc<DiscreteDblTheory>) -> DiscreteDblModel {
67    let mut model = DiscreteDblModel::new(th);
68    model.add_ob(name("x"), name("Object"));
69    model.add_ob(name("y"), name("Object"));
70    model.add_mor(name("positive"), name("x"), name("y"), Path::Id(name("Object")));
71    model.add_mor(name("negative"), name("y"), name("x"), name("Negative").into());
72    model
73}
74
75/// The "walking attribute" schema.
76///
77/// A schema with one entity type, one attribute type, and one attribute.
78pub fn walking_attr(th: Rc<DiscreteDblTheory>) -> DiscreteDblModel {
79    let mut model = DiscreteDblModel::new(th);
80    model.add_ob(name("entity"), name("Entity"));
81    model.add_ob(name("type"), name("AttrType"));
82    model.add_mor(name("attr"), name("entity"), name("type"), name("Attr").into());
83    model
84}
85
86/// The "walking" backward link.
87///
88/// This is the free category with links that has a link from the codomain of a
89/// morphism back to the morphism itself.
90///
91/// In system dynamics jargon, a backward link defines a "reinforcing loop,"
92/// assuming the link has a positive effect on the flow. An example is an
93/// infection flow an infectious disease model, where increasing the number of
94/// infectives increases the rate of infection of the remaining susceptibles
95/// (other things equal).
96pub fn backward_link(th: Rc<DiscreteTabTheory>) -> DiscreteTabModel {
97    backward_link_of_type(th, TabMorType::Basic(name("Link")))
98}
99
100/// The "walking" backward positive link.
101///
102/// This is the free category with signed links that has a positive link from
103/// the codomain of a morphism back to the morphism itself.
104pub fn positive_backward_link(th: Rc<DiscreteTabTheory>) -> DiscreteTabModel {
105    // The type for positive links is just `Link`.
106    backward_link_of_type(th, TabMorType::Basic(name("Link")))
107}
108
109/// The "walking" backward negative link.
110///
111/// This is the free category with signed links that has a negative link from
112/// the codomain of a morphism back to the morphism itself.
113pub fn negative_backward_link(th: Rc<DiscreteTabTheory>) -> DiscreteTabModel {
114    backward_link_of_type(th, TabMorType::Basic(name("NegativeLink")))
115}
116
117fn backward_link_of_type(th: Rc<DiscreteTabTheory>, link_type: TabMorType) -> DiscreteTabModel {
118    let ob_type = TabObType::Basic(name("Object"));
119    let mut model = DiscreteTabModel::new(th.clone());
120    model.add_ob(name("x"), ob_type.clone());
121    model.add_ob(name("y"), ob_type.clone());
122    model.add_mor(name("f"), name("x").into(), name("y").into(), th.hom_type(ob_type));
123    model.add_mor(name("link"), name("y").into(), model.tabulated_gen(name("f")), link_type);
124    model
125}
126
127/// A reaction involving three species, one playing the role of a catalyst.
128///
129/// A free symmetric monoidal category, viewed as a reaction network.
130pub fn catalyzed_reaction(th: Rc<ModalDblTheory<Unital>>) -> ModalDblModel<Unital> {
131    let (ob_type, op) = (ModalObType::new(name("Object")), name("tensor"));
132    let mut model = ModalDblModel::new(th);
133    model.add_ob(name("x"), ob_type.clone());
134    model.add_ob(name("y"), ob_type.clone());
135    model.add_ob(name("c"), ob_type.clone());
136    let [x, y, c] = [name("x"), name("y"), name("c")].map(ModalOb::from);
137    model.add_mor(
138        name("f"),
139        ModalOb::App(ModalOb::List(List::Symmetric, vec![x, c.clone()]).into(), op.clone()),
140        ModalOb::App(ModalOb::List(List::Symmetric, vec![y, c]).into(), op),
141        ModalMorType::Zero(ob_type),
142    );
143    model
144}
145
146/// The SIR model viewed as a reaction network.
147pub fn sir_petri(th: Rc<ModalDblTheory<Unital>>) -> ModalDblModel<Unital> {
148    let (ob_type, op) = (ModalObType::new(name("Object")), name("tensor"));
149    let mut model = ModalDblModel::new(th);
150    let (s, i, r) = (name("S"), name("I"), name("R"));
151    model.add_ob(s.clone(), ob_type.clone());
152    model.add_ob(i.clone(), ob_type.clone());
153    model.add_ob(r.clone(), ob_type.clone());
154    model.add_mor(
155        name("infect"),
156        ModalOb::App(
157            ModalOb::List(List::Symmetric, vec![s.into(), i.clone().into()]).into(),
158            op.clone(),
159        ),
160        ModalOb::App(
161            ModalOb::List(List::Symmetric, vec![i.clone().into(), i.clone().into()]).into(),
162            op.clone(),
163        ),
164        ModalMorType::Zero(ob_type.clone()),
165    );
166    model.add_mor(name("recover"), i.into(), r.into(), ModalMorType::Zero(ob_type));
167    model
168}
169
170/// An example of Lotka–Volterra dynamics viewed as a non-unital theory for a symmetric multicategory.
171pub fn lotka_volterra_dynamics(th: Rc<ModalDblTheory<NonUnital>>) -> ModalDblModel<NonUnital> {
172    let ob_type = ModalObType::new(name("State"));
173    let mor_type: ModalMorType = ModeApp::new(name("Contribution")).into();
174
175    let mut model = ModalDblModel::new(th);
176    // We're going to build a two-level predator-prey model, but where (in absence of signed
177    // arrows) all interactions have positive coefficients.
178    let (a, b, c) = (name("A"), name("B"), name("C"));
179
180    model.add_ob(a.clone(), ob_type.clone());
181    model.add_ob(b.clone(), ob_type.clone());
182    model.add_ob(c.clone(), ob_type.clone());
183    // The growth terms, corresponding to
184    // dA/dt += g_A A
185    // dB/dt += g_B B
186    // dC/dt += g_C C
187    model.add_mor(
188        name("A_growth"),
189        ModalOb::List(List::Symmetric, vec![a.clone().into()]),
190        a.clone().into(),
191        mor_type.clone(),
192    );
193    model.add_mor(
194        name("B_growth"),
195        ModalOb::List(List::Symmetric, vec![b.clone().into()]),
196        b.clone().into(),
197        mor_type.clone(),
198    );
199    model.add_mor(
200        name("C_growth"),
201        ModalOb::List(List::Symmetric, vec![c.clone().into()]),
202        c.clone().into(),
203        mor_type.clone(),
204    );
205    // The interaction terms, corresponding to
206    // dB/dt += k_AB AB
207    // dA/dt += k_BA AB
208    // dC/dt += k_BC BC
209    // dB/dt += k_CB BC
210    model.add_mor(
211        name("AB_interaction"),
212        ModalOb::List(List::Symmetric, vec![a.clone().into(), b.clone().into()]),
213        b.clone().into(),
214        mor_type.clone(),
215    );
216    model.add_mor(
217        name("BA_interaction"),
218        ModalOb::List(List::Symmetric, vec![a.clone().into(), b.clone().into()]),
219        a.clone().into(),
220        mor_type.clone(),
221    );
222    model.add_mor(
223        name("BC_interaction"),
224        ModalOb::List(List::Symmetric, vec![b.clone().into(), c.clone().into()]),
225        c.clone().into(),
226        mor_type.clone(),
227    );
228    model.add_mor(
229        name("CB_interaction"),
230        ModalOb::List(List::Symmetric, vec![b.clone().into(), c.clone().into()]),
231        b.clone().into(),
232        mor_type,
233    );
234
235    model
236}
237
238#[cfg(test)]
239mod tests {
240    use super::super::theories::*;
241    use super::*;
242    use crate::validate::Validate;
243
244    #[test]
245    fn signed_categories() {
246        let th = Rc::new(th_signed_category());
247        assert!(positive_loop(th.clone()).validate().is_ok());
248        assert!(negative_loop(th.clone()).validate().is_ok());
249        assert!(positive_feedback(th.clone()).validate().is_ok());
250        assert!(negative_feedback(th.clone()).validate().is_ok());
251    }
252
253    #[test]
254    fn delayable_signed_categories() {
255        let th = Rc::new(th_delayable_signed_category());
256        assert!(positive_loop(th.clone()).validate().is_ok());
257        assert!(negative_loop(th.clone()).validate().is_ok());
258        assert!(delayed_positive_loop(th.clone()).validate().is_ok());
259        assert!(delayed_negative_loop(th.clone()).validate().is_ok());
260    }
261
262    #[test]
263    fn schemas() {
264        let th = Rc::new(th_schema());
265        assert!(walking_attr(th).validate().is_ok());
266    }
267
268    #[test]
269    fn categories_with_links() {
270        let th = Rc::new(th_category_links());
271        assert!(backward_link(th).validate().is_ok());
272    }
273
274    #[test]
275    fn categories_with_signed_links() {
276        let th = Rc::new(th_category_signed_links());
277        assert!(positive_backward_link(th.clone()).validate().is_ok());
278        assert!(negative_backward_link(th.clone()).validate().is_ok());
279    }
280
281    #[test]
282    fn sym_monoidal_categories() {
283        let th = Rc::new(th_sym_monoidal_category());
284        assert!(catalyzed_reaction(th.clone()).validate().is_ok());
285        assert!(sir_petri(th).validate().is_ok());
286    }
287
288    #[test]
289    fn polynomial_ode_systems() {
290        let th = Rc::new(th_polynomial_ode_system());
291        assert!(lotka_volterra_dynamics(th.clone()).validate().is_ok());
292    }
293}