Skip to content

Commit debe992

Browse files
committed
positive and negative links for stock-flow diagrams
1 parent 73a6807 commit debe992

File tree

5 files changed

+92
-12
lines changed

5 files changed

+92
-12
lines changed

packages/catlog-wasm/src/theories.rs

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use catlog::one::Path;
1212
use catlog::stdlib::{analyses, models, theories, theory_morphisms};
1313
use catlog::zero::name;
1414

15-
use super::model_morphism::{MotifOccurrence, MotifsOptions, motifs};
15+
use super::model_morphism::{motifs, MotifOccurrence, MotifsOptions};
1616
use super::result::JsResult;
1717
use super::{analyses::*, model::DblModel, theory::DblTheory};
1818

@@ -304,6 +304,39 @@ impl ThCategoryLinks {
304304
}
305305
}
306306

307+
/// The theory of categories with signed links.
308+
#[wasm_bindgen]
309+
pub struct ThCategorySignedLinks(Rc<theory::DiscreteTabTheory>);
310+
311+
#[wasm_bindgen]
312+
impl ThCategorySignedLinks {
313+
#[wasm_bindgen(constructor)]
314+
pub fn new() -> Self {
315+
Self(Rc::new(theories::th_category_signed_links()))
316+
}
317+
318+
#[wasm_bindgen]
319+
pub fn theory(&self) -> DblTheory {
320+
DblTheory(self.0.clone().into())
321+
}
322+
323+
/// Simulates the mass-action ODE system derived from a model.
324+
#[wasm_bindgen(js_name = "massAction")]
325+
pub fn mass_action(
326+
&self,
327+
model: &DblModel,
328+
data: analyses::ode::MassActionProblemData,
329+
) -> Result<ODEResult, String> {
330+
Ok(ODEResult(
331+
analyses::ode::StockFlowMassActionAnalysis::default()
332+
.build_numerical_system(model.discrete_tab()?, data)
333+
.solve_with_defaults()
334+
.map_err(|err| format!("{err:?}"))
335+
.into(),
336+
))
337+
}
338+
}
339+
307340
/// The theory of strict symmetric monoidal categories.
308341
#[wasm_bindgen]
309342
pub struct ThSymMonoidalCategory(Rc<theory::ModalDblTheory>);

packages/catlog/src/stdlib/analyses/ode/mass_action.rs

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::dbl::{
2222
};
2323
use crate::one::FgCategory;
2424
use crate::simulate::ode::{NumericalPolynomialSystem, ODEProblem, PolynomialSystem};
25-
use crate::zero::{QualifiedName, alg::Polynomial, name, rig::Monomial};
25+
use crate::zero::{alg::Polynomial, name, rig::Monomial, QualifiedName};
2626

2727
/// Data defining a mass-action ODE problem for a model.
2828
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
@@ -218,7 +218,9 @@ pub struct StockFlowMassActionAnalysis {
218218
/// Morphism types for flows between stocks.
219219
pub flow_mor_type: TabMorType,
220220
/// Morphism types for links for stocks to flows.
221-
pub link_mor_type: TabMorType,
221+
pub pos_link_mor_type: TabMorType,
222+
/// Morphism types for links for stocks to flows.
223+
pub neg_link_mor_type: TabMorType,
222224
}
223225

224226
impl Default for StockFlowMassActionAnalysis {
@@ -228,7 +230,8 @@ impl Default for StockFlowMassActionAnalysis {
228230
Self {
229231
stock_ob_type,
230232
flow_mor_type,
231-
link_mor_type: TabMorType::Basic(name("Link")),
233+
pos_link_mor_type: TabMorType::Basic(name("PositiveLink")),
234+
neg_link_mor_type: TabMorType::Basic(name("NegativeLink")),
232235
}
233236
}
234237
}
@@ -247,7 +250,20 @@ impl StockFlowMassActionAnalysis {
247250
})
248251
.collect();
249252

250-
for link in model.mor_generators_with_type(&self.link_mor_type) {
253+
for link in model.mor_generators_with_type(&self.pos_link_mor_type) {
254+
let dom = model.mor_generator_dom(&link).unwrap_basic();
255+
let path = model.mor_generator_cod(&link).unwrap_tabulated();
256+
let Some(TabEdge::Basic(cod)) = path.only() else {
257+
panic!("Codomain of link should be basic morphism");
258+
};
259+
if let Some(term) = terms.get_mut(&cod) {
260+
*term = std::mem::take(term) * Monomial::generator(dom);
261+
} else {
262+
panic!("Codomain of link does not belong to model");
263+
};
264+
}
265+
266+
for link in model.mor_generators_with_type(&self.neg_link_mor_type) {
251267
let dom = model.mor_generator_dom(&link).unwrap_basic();
252268
let path = model.mor_generator_cod(&link).unwrap_tabulated();
253269
let Some(TabEdge::Basic(cod)) = path.only() else {

packages/catlog/src/stdlib/theories.rs

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! Standard library of double theories.
22
33
use crate::dbl::theory::*;
4-
use crate::one::{Path, fp_category::FpCategory};
4+
use crate::one::{fp_category::FpCategory, Path};
55
use crate::zero::name;
66

77
/// The empty theory, which has a single model, the empty model.
@@ -119,6 +119,27 @@ pub fn th_category_links() -> DiscreteTabTheory {
119119
th
120120
}
121121

122+
/// The theory of categories with signed links.
123+
///
124+
/// It can be useful to consider a version of stock and flow diagrams where the
125+
/// links are labelled with a sign: positive or negative
126+
pub fn th_category_signed_links() -> DiscreteTabTheory {
127+
let mut th = DiscreteTabTheory::new();
128+
th.add_ob_type(name("Object"));
129+
let ob_type = TabObType::Basic(name("Object"));
130+
th.add_mor_type(
131+
name("PositiveLink"),
132+
ob_type.clone(),
133+
th.tabulator(th.hom_type(ob_type.clone())),
134+
);
135+
th.add_mor_type(
136+
name("NegativeLink"),
137+
ob_type.clone(),
138+
th.tabulator(th.hom_type(ob_type.clone())),
139+
);
140+
th
141+
}
142+
122143
/// The theory of strict monoidal categories.
123144
pub fn th_monoidal_category() -> ModalDblTheory {
124145
th_list_algebra(List::Plain)

packages/frontend/src/stdlib/theories.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ stdTheories.add(
7878
{
7979
id: "primitive-stock-flow",
8080
name: "Stock and flow",
81-
description: "Model accumulation (stocks) and change (flows)",
81+
description: "Accumulation (stocks) and change (flows)",
8282
group: "System Dynamics",
8383
},
8484
async () => (await import("./theories/primitive-stock-flow")).default,

packages/frontend/src/stdlib/theories/primitive-stock-flow.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
1-
import { ThCategoryLinks } from "catlog-wasm";
1+
import { ThCategorySignedLinks } from "catlog-wasm";
22
import { Theory, type TheoryMeta } from "../../theory";
33
import * as analyses from "../analyses";
44
import styles from "../styles.module.css";
55
import svgStyles from "../svg_styles.module.css";
66

77
export default function createPrimitiveStockFlowTheory(theoryMeta: TheoryMeta): Theory {
8-
const thCategoryLinks = new ThCategoryLinks();
8+
const thCategoryLinks = new ThCategorySignedLinks();
99

1010
return new Theory({
1111
...theoryMeta,
@@ -34,12 +34,22 @@ export default function createPrimitiveStockFlowTheory(theoryMeta: TheoryMeta):
3434
},
3535
{
3636
tag: "MorType",
37-
morType: { tag: "Basic", content: "Link" },
38-
name: "Link",
39-
description: "Influence of a stock on a flow",
37+
morType: { tag: "Basic", content: "PositiveLink" },
38+
name: "Positive link",
39+
description: "Positive influence of a stock on a flow",
40+
arrowStyle: "plus",
4041
preferUnnamed: true,
4142
shortcut: ["L"],
4243
},
44+
{
45+
tag: "MorType",
46+
morType: { tag: "Basic", content: "NegativeLink" },
47+
name: "Negative link",
48+
description: "Negative influence of a stock on a flow",
49+
arrowStyle: "minus",
50+
preferUnnamed: true,
51+
shortcut: ["K"],
52+
},
4353
],
4454
modelAnalyses: [
4555
analyses.stockFlowDiagram({

0 commit comments

Comments
 (0)