Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions packages/catlog-wasm/src/theories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,23 @@ impl ThCategoryLinks {
}
}

/// The theory of categories with links and dynamic variables.
#[wasm_bindgen]
pub struct ThCategoryDynamicStockFlow(Rc<theory::UstrDiscreteTabTheory>);

#[wasm_bindgen]
impl ThCategoryDynamicStockFlow {
#[wasm_bindgen(constructor)]
pub fn new() -> Self {
Self(Rc::new(theories::th_category_dynamic_stockflow()))
}

#[wasm_bindgen]
pub fn theory(&self) -> DblTheory {
DblTheory(self.0.clone().into())
}
}

/// The theory of strict symmetric monoidal categories.
#[wasm_bindgen]
pub struct ThSymMonoidalCategory(Rc<theory::UstrModalDblTheory>);
Expand Down
47 changes: 47 additions & 0 deletions packages/catlog/src/stdlib/models.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,47 @@ pub fn backward_link(th: Rc<UstrDiscreteTabTheory>) -> UstrDiscreteTabModel {
model
}

/** Water flowing in from a source

*/
pub fn water_volume(th: Rc<UstrDiscreteTabTheory>) -> UstrDiscreteTabModel {
let mut model = UstrDiscreteTabModel::new(th.clone());
let (source, water, container, sediment) =
(ustr("Source"), ustr("Water"), ustr("Container"), ustr("Sediment"));
let ob_type = TabObType::Basic(ustr("Object"));
model.add_ob(source, ob_type.clone());
model.add_ob(water, ob_type.clone());
model.add_ob(container, ob_type.clone());
model.add_ob(sediment, ob_type.clone());
let inflow = ustr("inflow");
let flow = ustr("deposits");
model.add_mor(inflow, TabOb::Basic(source), TabOb::Basic(water), th.hom_type(ob_type.clone()));
model.add_mor(flow, TabOb::Basic(water), TabOb::Basic(sediment), th.hom_type(ob_type));
let spillover = ustr("SpilloverChecker");
// flow link
model.add_ob(spillover, TabObType::Basic(ustr("AuxiliaryVariable")));
model.add_mor(
ustr("dynVolume"),
TabOb::Basic(spillover),
model.tabulated_gen(flow),
TabMorType::Basic(ustr("FlowLink")),
);
// variable links
model.add_mor(
ustr("left"),
TabOb::Basic(spillover),
TabOb::Basic(water),
TabMorType::Basic(ustr("VariableLink")),
);
model.add_mor(
ustr("right"),
TabOb::Basic(spillover),
TabOb::Basic(container),
TabMorType::Basic(ustr("VariableLink")),
);
model
}

#[cfg(test)]
mod tests {
use super::super::theories::*;
Expand Down Expand Up @@ -156,4 +197,10 @@ mod tests {
let th = Rc::new(th_category_links());
assert!(backward_link(th).validate().is_ok());
}

#[test]
fn categories_dynamic_stockflow() {
let th = Rc::new(th_category_dynamic_stockflow());
assert!(water_volume(th).validate().is_ok());
}
}
23 changes: 23 additions & 0 deletions packages/catlog/src/stdlib/theories.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,29 @@ pub fn th_category_links() -> UstrDiscreteTabTheory {
th
}

/// The theory of stock and flow diagrams with dynamic variables.
pub fn th_category_dynamic_stockflow() -> UstrDiscreteTabTheory {
let mut th: UstrDiscreteTabTheory = Default::default();
let x = ustr("Object");
th.add_ob_type(x);
th.add_mor_type(
ustr("Link"),
TabObType::Basic(x),
th.tabulator(th.hom_type(TabObType::Basic(x))),
);
let v = ustr("AuxiliaryVariable");
th.add_ob_type(v);
// there is a proarrow from the flow tabulator to the dynamic variable
th.add_mor_type(
ustr("FlowLink"),
TabObType::Basic(v),
th.tabulator(th.hom_type(TabObType::Basic(x))),
);
// there is a proarrow from the variable to stocks
th.add_mor_type(ustr("VariableLink"), TabObType::Basic(v), TabObType::Basic(x));
th
}

/// The theory of strict monoidal categories.
pub fn th_monoidal_category() -> UstrModalDblTheory {
th_list_algebra(List::Plain)
Expand Down
8 changes: 7 additions & 1 deletion packages/frontend/src/stdlib/analyses/model_graph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { DownloadSVGButton, GraphvizSVG, type SVGRefProp } from "../../visualiza
import * as GV from "./graph_visualization";

import "./graph_visualization.css";
// import { DiagramObjectDecl } from "../../diagram";

/** Configure a graph visualization for use with models of a double theory. */
export function configureModelGraph(options: {
Expand Down Expand Up @@ -98,6 +99,8 @@ export function modelToGraphviz(
model: ModelJudgment[],
theory: Theory,
attributes?: GV.GraphvizAttributes,
nodeAttributes?: (jgmt: ModelJudgment) => Record<string, string> | undefined,
edgeAttributes?: (jgmt: ModelJudgment) => Record<string, string> | undefined,
): Viz.Graph {
const nodes = new Map<string, Required<Viz.Graph>["nodes"][0]>();
for (const judgment of model) {
Expand All @@ -108,9 +111,11 @@ export function modelToGraphviz(
name: id,
attributes: {
id,
label: name,
class: GV.svgCssClasses(meta).join(" "),
fontname: GV.graphvizFontname(meta),
...(nodeAttributes?.(judgment) ?? {
label: name,
}),
},
});
}
Expand Down Expand Up @@ -150,6 +155,7 @@ export function modelToGraphviz(
fontname: GV.graphvizFontname(meta),
// Not recognized by Graphviz but will be passed through!
arrowstyle: meta?.arrowStyle ?? "default",
...(edgeAttributes?.(judgment) ?? {}),
},
});
}
Expand Down
21 changes: 20 additions & 1 deletion packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,11 +96,30 @@ export function StockFlowGraphviz(props: {

const vizLayout = () => {
const viz = vizResource();
const patternAuxiliaryVariable: P.Pattern<ModelJudgment> = {
tag: "object",
obType: {
content: "AuxiliaryVariable",
tag: "Basic",
},
};
return (
viz &&
vizLayoutGraph(
viz,
modelToGraphviz(props.model, props.theory, props.attributes),
modelToGraphviz(
props.model,
props.theory,
props.attributes,
(jgmt: ModelJudgment) =>
match(jgmt)
.with(patternAuxiliaryVariable, () => ({
xlabel: jgmt.name,
label: "",
}))
.with(P._, () => undefined)
.run(),
),
props.options,
)
);
Expand Down
14 changes: 14 additions & 0 deletions packages/frontend/src/stdlib/styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,17 @@
-webkit-mask: conic-gradient(at var(--corner) var(--corner), #0000 75%, #000 0) 0 0 /
calc(100% - var(--corner)) calc(100% - var(--corner)), linear-gradient(#000 0 0) content-box;
}

.circle {
border: 1px solid black;
padding: 2px;
}

.point {
border: 0px solid black;
padding: 0px;
}

.link {
border: 1px solid blue;
}
3 changes: 3 additions & 0 deletions packages/frontend/src/stdlib/styles.module.css.d.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
declare const styles: {
readonly box: string;
readonly cornerBox: string;
readonly circle: string;
readonly point: string;
readonly link: string;
};
export = styles;
14 changes: 14 additions & 0 deletions packages/frontend/src/stdlib/svg_styles.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,17 @@
fill: white;
stroke: black;
}

.circle circle {
fill: white;
stroke: black;
}

.point point {
fill: black;
stroke: black;
}

.link path {
stroke: blue;
}
3 changes: 3 additions & 0 deletions packages/frontend/src/stdlib/svg_styles.module.css.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
declare const styles: {
readonly box: string;
readonly circle: string;
readonly point: string;
readonly link: string;
};
export = styles;
74 changes: 74 additions & 0 deletions packages/frontend/src/stdlib/theories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -686,3 +686,77 @@ stdTheories.add(
});
},
);

stdTheories.add(
{
id: "dynamic-stackflow",
name: "Dynamic Stock Flow",
description: "Model accumulation (stocks) and change (flows)",
group: "System Dynamics",
help: "dynamic-stockflow",
},
(meta) => {
const thCategoryDynamicStockFlow = new catlog.ThCategoryDynamicStockFlow();
return new Theory({
...meta,
theory: thCategoryDynamicStockFlow.theory(),
onlyFreeModels: true,
modelTypes: [
{
tag: "ObType",
obType: { tag: "Basic", content: "Object" },
name: "Stock",
description: "Thing with an amount",
shortcut: ["S"],
cssClasses: [styles.box],
svgClasses: [svgStyles.box],
},
{
tag: "MorType",
morType: {
tag: "Hom",
content: { tag: "Basic", content: "Object" },
},
name: "Flow",
description: "Flow from one stock to another",
shortcut: ["F"],
arrowStyle: "double",
},
{
tag: "ObType",
obType: { tag: "Basic", content: "AuxiliaryVariable" },
name: "Variable",
description: "A function of different stocks",
shortcut: ["A"],
cssClasses: [styles.point],
svgClasses: [svgStyles.point],
},
{
tag: "MorType",
morType: { tag: "Basic", content: "FlowLink" },
name: "Flow Link",
description: "Influence of an auxiliary variable on a flow",
preferUnnamed: true,
shortcut: ["L"],
},
{
tag: "MorType",
morType: { tag: "Basic", content: "VariableLink" },
name: "Variable Link",
description: "Variable referencing a stock quantity",
shortcut: ["V"],
arrowStyle: "flat",
cssClasses: [styles.link],
svgClasses: [svgStyles.link],
},
],
modelAnalyses: [
analyses.configureStockFlowDiagram({
id: "diagram",
name: "Visualization",
description: "Visualize the stock and flow diagram",
}),
],
});
},
);
3 changes: 3 additions & 0 deletions packages/frontend/src/visualization/graph_layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ export interface Node<Id> extends GraphElement {

/** Node label, if any. */
label?: string;

/** Position of center of label. */
labelPos?: Point;
}

export interface Edge<Id> extends GraphElement {
Expand Down
13 changes: 13 additions & 0 deletions packages/frontend/src/visualization/graph_svg.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,25 @@
fill: transparent;
}

.node circle {
fill: transparent;
}

.node circle.point {
fill: black;
}

.edge line,
.edge path {
fill: none;
stroke: black;
stroke-width: 1.5;
}
.edge path.variable-link {
fill: none;
stroke: blue;
stroke-width: 1.5;
}

.edge path.double-outer {
stroke-width: 6;
Expand Down
Loading