From ab3186b877fe6dcabf5497748f14f10ba9dae6de Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Mon, 23 Jun 2025 19:31:11 -0700 Subject: [PATCH 01/12] First commit building StockFlow with Dynamic Variables --- packages/catlog-wasm/src/analyses.rs | 4 +++ packages/catlog-wasm/src/theories.rs | 41 ++++++++++++++++++++++ packages/catlog/src/dbl/theory.rs | 2 +- packages/catlog/src/stdlib/models.rs | 48 ++++++++++++++++++++++++++ packages/catlog/src/stdlib/theories.rs | 36 +++++++++++++++++++ 5 files changed, 130 insertions(+), 1 deletion(-) diff --git a/packages/catlog-wasm/src/analyses.rs b/packages/catlog-wasm/src/analyses.rs index 731f036c8..93d836f2c 100644 --- a/packages/catlog-wasm/src/analyses.rs +++ b/packages/catlog-wasm/src/analyses.rs @@ -16,3 +16,7 @@ pub struct LotkaVolterraModelData(pub analyses::ode::LotkaVolterraProblemData); + +#[derive(Serialize, Deserialize, Tsify)] +#[tsify(into_wasm_abi, from_wasm_abi)] +pub struct EnergeseMassActionModelData(pub analyses::ode::EnergeseMassActionProblemData); diff --git a/packages/catlog-wasm/src/theories.rs b/packages/catlog-wasm/src/theories.rs index de6d668ca..8665dacf3 100644 --- a/packages/catlog-wasm/src/theories.rs +++ b/packages/catlog-wasm/src/theories.rs @@ -258,6 +258,47 @@ impl ThCategoryLinks { } } +/// The theory of categories of energese. +#[wasm_bindgen] +pub struct ThCategoryDynamicStockFlow(Rc); + +#[wasm_bindgen] +impl ThCategoryDynamicStockFlow { + #[wasm_bindgen(constructor)] + pub fn new() -> Self { + Self(Rc::new(theories::th_category_energese())) + } + + #[wasm_bindgen] + pub fn theory(&self) -> DblTheory { + DblTheory(self.0.clone().into()) + } + + /// Simulates the mass-action system derived from a model. + #[wasm_bindgen(js_name = "energese")] + pub fn mass_action( + &self, + model: &DblModel, + data: DynamicStockFlowMassActionModelData, + ) -> Result { + let model: &model::DiscreteTabModel<_, _, _> = (&model.0) + .try_into() + .map_err(|_| "Mass-action simulation expects a discrete tabulator model")?; + // + // let analysis = analyses::ode::EnergeseMassActionAnalysis::default() + // .teeup_numerical_system(model, data.0.clone()); + // console::log_1(&format!("{:#?}", analysis).into()); + // + Ok(ODEResult( + analyses::ode::DynamicStockFlowMassActionAnalysis::default() + .create_numerical_system(model, data.0) + .solve_with_defaults() + .map_err(|err| format!("{:?}", err)) + .into(), + )) + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/packages/catlog/src/dbl/theory.rs b/packages/catlog/src/dbl/theory.rs index 45d92f04f..c19d71460 100644 --- a/packages/catlog/src/dbl/theory.rs +++ b/packages/catlog/src/dbl/theory.rs @@ -540,7 +540,7 @@ where self.make_mor_type(e) } - /// Adds a basic morphim type without initializing its source or target. + /// Adds a basic morphism type without initializing its source or target. pub fn make_mor_type(&mut self, e: E) -> bool { self.mor_types.insert(e) } diff --git a/packages/catlog/src/stdlib/models.rs b/packages/catlog/src/stdlib/models.rs index 7bfc2bf43..279fcee31 100644 --- a/packages/catlog/src/stdlib/models.rs +++ b/packages/catlog/src/stdlib/models.rs @@ -121,6 +121,48 @@ pub fn backward_link(th: Rc) -> UstrDiscreteTabModel { model } +/** Water flowing in from a source + +*/ +pub fn water_volume(th: Rc) -> 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"); + let dynamible_type = TabObType::Basic(ustr("DynamicVariable")); + // flow link + model.add_ob(spillover, dynamible_type.clone()); + model.add_mor( + ustr("dynVolume"), + TabOb::Basic(spillover), + model.tabulated_gen(flow), + TabMorType::Basic(ustr("FlowLink")), // flow + ); + // 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::*; @@ -156,4 +198,10 @@ mod tests { let th = Rc::new(th_category_links()); assert!(backward_link(th).validate().is_ok()); } + + #[test] + fn categories_dynvar() { + let th = Rc::new(th_category_dynvar()); + assert!(water_volume(th).validate().is_ok()); + } } diff --git a/packages/catlog/src/stdlib/theories.rs b/packages/catlog/src/stdlib/theories.rs index c6ccfb86f..b56fcf1a7 100644 --- a/packages/catlog/src/stdlib/theories.rs +++ b/packages/catlog/src/stdlib/theories.rs @@ -136,6 +136,42 @@ pub fn th_category_links() -> UstrDiscreteTabTheory { th } +/** The theory of stock and flow diagrams with dynamic variables. + +*/ +pub fn th_category_dynvar() -> UstrDiscreteTabTheory { + let mut th: UstrDiscreteTabTheory = Default::default(); + let x = ustr("Object"); + th.add_ob_type(x); + // there are two proarrows from stocks to flows. they correspond to inflows and outflows. + th.add_mor_type( + ustr("Inflow"), + TabObType::Basic(x), + th.tabulator(th.hom_type(TabObType::Basic(x))), + ); + th.add_mor_type( + ustr("Outflow"), + TabObType::Basic(x), + th.tabulator(th.hom_type(TabObType::Basic(x))), + ); + th.add_mor_type( + ustr("Link"), + TabObType::Basic(x), + th.tabulator(th.hom_type(TabObType::Basic(x))), + ); + let v = ustr("DynamicVariable"); + 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 +} + #[cfg(test)] mod tests { use super::*; From f309b9dc23fe2d0531767181dac13439315937a0 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Mon, 23 Jun 2025 23:32:31 -0700 Subject: [PATCH 02/12] DynamicStockFlow theory defined --- packages/catlog-wasm/src/analyses.rs | 4 -- packages/catlog-wasm/src/theories.rs | 17 ++--- packages/catlog/src/stdlib/models.rs | 4 +- packages/catlog/src/stdlib/theories.rs | 4 +- packages/frontend/src/stdlib/theories.tsx | 80 +++++++++++++++++++++++ 5 files changed, 90 insertions(+), 19 deletions(-) diff --git a/packages/catlog-wasm/src/analyses.rs b/packages/catlog-wasm/src/analyses.rs index 93d836f2c..731f036c8 100644 --- a/packages/catlog-wasm/src/analyses.rs +++ b/packages/catlog-wasm/src/analyses.rs @@ -16,7 +16,3 @@ pub struct LotkaVolterraModelData(pub analyses::ode::LotkaVolterraProblemData); - -#[derive(Serialize, Deserialize, Tsify)] -#[tsify(into_wasm_abi, from_wasm_abi)] -pub struct EnergeseMassActionModelData(pub analyses::ode::EnergeseMassActionProblemData); diff --git a/packages/catlog-wasm/src/theories.rs b/packages/catlog-wasm/src/theories.rs index 8665dacf3..83c179ebe 100644 --- a/packages/catlog-wasm/src/theories.rs +++ b/packages/catlog-wasm/src/theories.rs @@ -13,7 +13,7 @@ use catlog::dbl::{model, theory}; use catlog::one::Path; use catlog::stdlib::{analyses, models, theories}; -use super::model_morphism::{MotifsOptions, motifs}; +use super::model_morphism::{motifs, MotifsOptions}; use super::{analyses::*, model::DblModel, theory::DblTheory}; /// The empty or initial theory. @@ -258,7 +258,7 @@ impl ThCategoryLinks { } } -/// The theory of categories of energese. +/// The theory of categories with links and dynamic variables. #[wasm_bindgen] pub struct ThCategoryDynamicStockFlow(Rc); @@ -266,7 +266,7 @@ pub struct ThCategoryDynamicStockFlow(Rc); impl ThCategoryDynamicStockFlow { #[wasm_bindgen(constructor)] pub fn new() -> Self { - Self(Rc::new(theories::th_category_energese())) + Self(Rc::new(theories::th_category_dynamic_stockflow())) } #[wasm_bindgen] @@ -275,22 +275,17 @@ impl ThCategoryDynamicStockFlow { } /// Simulates the mass-action system derived from a model. - #[wasm_bindgen(js_name = "energese")] + #[wasm_bindgen(js_name = "mass_action")] pub fn mass_action( &self, model: &DblModel, - data: DynamicStockFlowMassActionModelData, + data: MassActionModelData, ) -> Result { let model: &model::DiscreteTabModel<_, _, _> = (&model.0) .try_into() .map_err(|_| "Mass-action simulation expects a discrete tabulator model")?; - // - // let analysis = analyses::ode::EnergeseMassActionAnalysis::default() - // .teeup_numerical_system(model, data.0.clone()); - // console::log_1(&format!("{:#?}", analysis).into()); - // Ok(ODEResult( - analyses::ode::DynamicStockFlowMassActionAnalysis::default() + analyses::ode::StockFlowMassActionAnalysis::default() .create_numerical_system(model, data.0) .solve_with_defaults() .map_err(|err| format!("{:?}", err)) diff --git a/packages/catlog/src/stdlib/models.rs b/packages/catlog/src/stdlib/models.rs index 279fcee31..57b44a38c 100644 --- a/packages/catlog/src/stdlib/models.rs +++ b/packages/catlog/src/stdlib/models.rs @@ -1,7 +1,7 @@ //! Standard library of models of double theories. use std::rc::Rc; -use ustr::{Ustr, ustr}; +use ustr::{ustr, Ustr}; use crate::dbl::{model::*, theory::*}; use crate::one::Path; @@ -145,7 +145,7 @@ pub fn water_volume(th: Rc) -> UstrDiscreteTabModel { ustr("dynVolume"), TabOb::Basic(spillover), model.tabulated_gen(flow), - TabMorType::Basic(ustr("FlowLink")), // flow + TabMorType::Basic(ustr("FlowLink")), ); // variable links model.add_mor( diff --git a/packages/catlog/src/stdlib/theories.rs b/packages/catlog/src/stdlib/theories.rs index b56fcf1a7..1c2b36811 100644 --- a/packages/catlog/src/stdlib/theories.rs +++ b/packages/catlog/src/stdlib/theories.rs @@ -3,7 +3,7 @@ use ustr::ustr; use crate::dbl::theory::*; -use crate::one::{Path, fp_category::UstrFpCategory}; +use crate::one::{fp_category::UstrFpCategory, Path}; /** The empty theory, which has a single model, the empty model. @@ -139,7 +139,7 @@ pub fn th_category_links() -> UstrDiscreteTabTheory { /** The theory of stock and flow diagrams with dynamic variables. */ -pub fn th_category_dynvar() -> UstrDiscreteTabTheory { +pub fn th_category_dynamic_stockflow() -> UstrDiscreteTabTheory { let mut th: UstrDiscreteTabTheory = Default::default(); let x = ustr("Object"); th.add_ob_type(x); diff --git a/packages/frontend/src/stdlib/theories.tsx b/packages/frontend/src/stdlib/theories.tsx index db65d8d0d..7082a2367 100644 --- a/packages/frontend/src/stdlib/theories.tsx +++ b/packages/frontend/src/stdlib/theories.tsx @@ -678,3 +678,83 @@ stdTheories.add( }); }, ); + +stdTheories.add( + { + id: "dynamic-stackflow", + name: "DynamicStockFlow", + description: "Model accumulation (stocks) and change (flows)", + group: "System Dynamics", + help: "energese", + }, + (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: "DynamicVariable" }, + name: "Variable", + description: "Variable stuff", + shortcut: ["V"], + cssClasses: [styles.box], + svgClasses: [svgStyles.box], + }, + { + tag: "MorType", + morType: { tag: "Basic", content: "FlowLink" }, + name: "Flow Link", + description: "Influence of a stock on a flow", + preferUnnamed: true, + shortcut: ["L"], + }, + { + tag: "MorType", + morType: { tag: "Basic", content: "VariableLink" }, + name: "Variable Link", + description: "Variable controlling stock quantity", + shortcut: ["F"], + arrowStyle: "flat", + }, + ], + modelAnalyses: [ + analyses.configureStockFlowDiagram({ + id: "diagram", + name: "Visualization", + description: "Visualize the stock and flow diagram", + }), + analyses.configureMassAction({ + simulate(model, data) { + return thCategoryDynamicStockFlow.mass_action(model, data); + }, + isTransition(mor) { + return mor.morType.tag === "Hom"; + }, + }), + ], + }); + }, +); From 8c85dc4cc768d3e47f4a027722d516fe5cfdd494 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Tue, 24 Jun 2025 14:30:59 -0700 Subject: [PATCH 03/12] Rendering dynamic variable as Graphviz point --- .../src/stdlib/analyses/model_graph.tsx | 2 +- .../stdlib/analyses/stock_flow_diagram.tsx | 39 +++++++++++++++++-- .../frontend/src/stdlib/styles.module.css | 10 +++++ .../src/stdlib/styles.module.css.d.ts | 2 + .../frontend/src/stdlib/svg_styles.module.css | 12 ++++++ .../src/stdlib/svg_styles.module.css.d.ts | 2 + packages/frontend/src/stdlib/theories.tsx | 8 ++-- .../src/visualization/graph_layout.ts | 3 ++ .../frontend/src/visualization/graph_svg.css | 8 ++++ .../frontend/src/visualization/graph_svg.tsx | 36 ++++++++++++++--- .../frontend/src/visualization/graphviz.ts | 4 +- .../src/visualization/graphviz_svg.tsx | 1 + 12 files changed, 112 insertions(+), 15 deletions(-) diff --git a/packages/frontend/src/stdlib/analyses/model_graph.tsx b/packages/frontend/src/stdlib/analyses/model_graph.tsx index ba240bfbb..1a6a0adda 100644 --- a/packages/frontend/src/stdlib/analyses/model_graph.tsx +++ b/packages/frontend/src/stdlib/analyses/model_graph.tsx @@ -108,7 +108,7 @@ export function modelToGraphviz( name: id, attributes: { id, - label: name, + label: name, class: GV.svgCssClasses(meta).join(" "), fontname: GV.graphvizFontname(meta), }, diff --git a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx index 21eaedc22..d154498e7 100644 --- a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx +++ b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx @@ -122,8 +122,9 @@ function StockFlowSVG(props: { const nodeMap = uniqueIndexArray(props.layout?.nodes ?? [], (node) => node.id); const edgeMap = uniqueIndexArray(props.layout?.edges ?? [], (edge) => edge.id); for (const judgment of props.model) { + console.log(judgment); match(judgment).with( - { + { tag: "morphism", dom: { tag: "Basic", @@ -136,7 +137,7 @@ function StockFlowSVG(props: { content: P.select("tgtId"), }, }, - }, + }, ({ srcId, tgtId }) => { const srcNode = nodeMap.get(srcId); const tgtEdge = edgeMap.get(tgtId); @@ -148,8 +149,38 @@ function StockFlowSVG(props: { const path = quadraticCurve(srcNode.pos, midpoint, 1.0); result.push(path.join(" ")); }, - ); - } + ); + // TODO + match(judgment).with( + { + tag: "morphism", + dom: { + tag: "Basic", + content: P.select("srcId"), + }, + cod: { + tag: "Basic", + content: P.select("tgtId"), + }, + morType: { + tag: "Basic", + content: "VariableLink" + } + }, + ({ srcId, tgtId }) => { + const srcNode = nodeMap.get(srcId); + const tgtEdge = edgeMap.get(tgtId); + if (!srcNode || !tgtEdge) { + return; + } + pathElem.setAttribute("d", tgtEdge.path); + const midpoint = pathElem.getPointAtLength(pathElem.getTotalLength() / 2); + const path = quadraticCurve(srcNode.pos, midpoint, 1.0); + result.push(path.join(" ")); + } + ); + } + console.log(result); return result; }; diff --git a/packages/frontend/src/stdlib/styles.module.css b/packages/frontend/src/stdlib/styles.module.css index 3c4ebb672..31e386d56 100644 --- a/packages/frontend/src/stdlib/styles.module.css +++ b/packages/frontend/src/stdlib/styles.module.css @@ -12,3 +12,13 @@ -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; +} diff --git a/packages/frontend/src/stdlib/styles.module.css.d.ts b/packages/frontend/src/stdlib/styles.module.css.d.ts index d19fe04e6..8d808407c 100644 --- a/packages/frontend/src/stdlib/styles.module.css.d.ts +++ b/packages/frontend/src/stdlib/styles.module.css.d.ts @@ -1,5 +1,7 @@ declare const styles: { readonly box: string; readonly cornerBox: string; + readonly circle: string; + readonly point: string; }; export = styles; diff --git a/packages/frontend/src/stdlib/svg_styles.module.css b/packages/frontend/src/stdlib/svg_styles.module.css index 6f693d0f4..ba773cfac 100644 --- a/packages/frontend/src/stdlib/svg_styles.module.css +++ b/packages/frontend/src/stdlib/svg_styles.module.css @@ -2,3 +2,15 @@ fill: white; stroke: black; } + +.circle circle { + fill: white; + stroke: black; +} + +.point point { + fill: black; + stroke: black; +} + + diff --git a/packages/frontend/src/stdlib/svg_styles.module.css.d.ts b/packages/frontend/src/stdlib/svg_styles.module.css.d.ts index e4d53134e..62dc623d8 100644 --- a/packages/frontend/src/stdlib/svg_styles.module.css.d.ts +++ b/packages/frontend/src/stdlib/svg_styles.module.css.d.ts @@ -1,4 +1,6 @@ declare const styles: { readonly box: string; + readonly circle: string; + readonly point: string; }; export = styles; diff --git a/packages/frontend/src/stdlib/theories.tsx b/packages/frontend/src/stdlib/theories.tsx index 7082a2367..7bd17a837 100644 --- a/packages/frontend/src/stdlib/theories.tsx +++ b/packages/frontend/src/stdlib/theories.tsx @@ -682,10 +682,10 @@ stdTheories.add( stdTheories.add( { id: "dynamic-stackflow", - name: "DynamicStockFlow", + name: "Dynamic Stock Flow", description: "Model accumulation (stocks) and change (flows)", group: "System Dynamics", - help: "energese", + help: "dynamic-stockflow", }, (meta) => { const thCategoryDynamicStockFlow = new catlog.ThCategoryDynamicStockFlow(); @@ -720,8 +720,8 @@ stdTheories.add( name: "Variable", description: "Variable stuff", shortcut: ["V"], - cssClasses: [styles.box], - svgClasses: [svgStyles.box], + cssClasses: [styles.point], + svgClasses: [svgStyles.point], }, { tag: "MorType", diff --git a/packages/frontend/src/visualization/graph_layout.ts b/packages/frontend/src/visualization/graph_layout.ts index 7d0cf1b68..525c6f44e 100644 --- a/packages/frontend/src/visualization/graph_layout.ts +++ b/packages/frontend/src/visualization/graph_layout.ts @@ -33,6 +33,9 @@ export interface Node extends GraphElement { /** Node label, if any. */ label?: string; + + /** Position of center of label. */ + labelPos?: Point; } export interface Edge extends GraphElement { diff --git a/packages/frontend/src/visualization/graph_svg.css b/packages/frontend/src/visualization/graph_svg.css index 3f95b6791..e368af704 100644 --- a/packages/frontend/src/visualization/graph_svg.css +++ b/packages/frontend/src/visualization/graph_svg.css @@ -8,6 +8,14 @@ fill: transparent; } +.node circle { + fill: transparent; +} + +.node circle.point { + fill: black; +} + .edge line, .edge path { fill: none; diff --git a/packages/frontend/src/visualization/graph_svg.tsx b/packages/frontend/src/visualization/graph_svg.tsx index 5b336873e..5a932c07f 100644 --- a/packages/frontend/src/visualization/graph_svg.tsx +++ b/packages/frontend/src/visualization/graph_svg.tsx @@ -37,6 +37,7 @@ export function GraphSVG(props: { /** Draw a node with a layout using SVG. */ export function NodeSVG(props: { node: GraphLayout.Node }) { + // do we use an || operator? const { node: { pos: { x, y }, @@ -47,11 +48,37 @@ export function NodeSVG(props: { node: GraphLayout.Node }) { return ( - + + {props.node.label} + }> + + + + + + + + + + - - {props.node.label} - + + + + {props.node.label} + + + + + {props.node.label} + + + + + {props.node.label} + + + ); @@ -87,7 +114,6 @@ export function EdgeSVG(props: { edge: GraphLayout.Edge }) { ); }; - return ( diff --git a/packages/frontend/src/visualization/graphviz.ts b/packages/frontend/src/visualization/graphviz.ts index 89014d02b..6ee010f75 100644 --- a/packages/frontend/src/visualization/graphviz.ts +++ b/packages/frontend/src/visualization/graphviz.ts @@ -67,7 +67,9 @@ export function parseGraphvizJSON(graphviz: GraphvizJSON.Graph): GraphLayout.Gra pos: parsePoint(node.pos), width: inchesToPoints(Number.parseFloat(node.width)), height: inchesToPoints(Number.parseFloat(node.height)), - label: node.label, + label: node.xlabel ?? node.label, + labelPos: parsePoint(node.pos), + // (node.xlp && parsePoint(node.xlp)) || (node.lp && parsePoint(node.lp)) || undefined, cssClass: node.class, }); } diff --git a/packages/frontend/src/visualization/graphviz_svg.tsx b/packages/frontend/src/visualization/graphviz_svg.tsx index 864ec9faa..6f1f6c099 100644 --- a/packages/frontend/src/visualization/graphviz_svg.tsx +++ b/packages/frontend/src/visualization/graphviz_svg.tsx @@ -35,5 +35,6 @@ function GraphvizOutputSVG(props: { graph?: GraphvizJSON.Graph; ref?: SVGRefProp; }) { + console.log(props.graph && parseGraphvizJSON(props.graph)); return ; } From b4777cdb79424d9148ebb261eb2d9b3fc6d925ee Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Wed, 25 Jun 2025 17:41:05 -0700 Subject: [PATCH 04/12] wip stylizing variable links blue --- .../src/stdlib/analyses/model_graph.tsx | 29 ++++-- .../stdlib/analyses/stock_flow_diagram.tsx | 98 +++++++++---------- .../frontend/src/stdlib/styles.module.css | 4 + .../src/stdlib/styles.module.css.d.ts | 1 + .../frontend/src/stdlib/svg_styles.module.css | 4 +- packages/frontend/src/stdlib/theories.tsx | 2 + packages/frontend/src/theory/types.ts | 2 + .../frontend/src/visualization/graph_svg.css | 5 + 8 files changed, 87 insertions(+), 58 deletions(-) diff --git a/packages/frontend/src/stdlib/analyses/model_graph.tsx b/packages/frontend/src/stdlib/analyses/model_graph.tsx index 1a6a0adda..800307425 100644 --- a/packages/frontend/src/stdlib/analyses/model_graph.tsx +++ b/packages/frontend/src/stdlib/analyses/model_graph.tsx @@ -6,10 +6,15 @@ import type { ModelJudgment } from "catlog-wasm"; import type { ModelAnalysisProps } from "../../analysis"; import { Foldable } from "../../components"; import type { ModelAnalysisMeta, Theory } from "../../theory"; -import { DownloadSVGButton, GraphvizSVG, type SVGRefProp } from "../../visualization"; +import { + DownloadSVGButton, + GraphvizSVG, + type SVGRefProp, +} from "../../visualization"; 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: { @@ -56,7 +61,10 @@ export function ModelGraph( return (
- +
@@ -83,6 +91,7 @@ export function ModelGraphviz(props: { options?: Viz.RenderOptions; ref?: SVGRefProp; }) { + return ( Record | undefined, + edgeAttributes?: (jgmt: ModelJudgment) => Record | undefined, ): Viz.Graph { const nodes = new Map["nodes"][0]>(); for (const judgment of model) { @@ -108,15 +119,17 @@ export function modelToGraphviz( name: id, attributes: { id, - label: name, class: GV.svgCssClasses(meta).join(" "), fontname: GV.graphvizFontname(meta), - }, - }); + ...(nodeAttributes?.(judgment) ?? { + label: name, + }) + }} + ) } } - const edges: Required["edges"] = []; + const edges: Required["edges"] = []; for (const judgment of model) { const matched = match(judgment) .with( @@ -150,8 +163,12 @@ export function modelToGraphviz( fontname: GV.graphvizFontname(meta), // Not recognized by Graphviz but will be passed through! arrowstyle: meta?.arrowStyle ?? "default", + ...(edgeAttributes?.(judgment).state.value ?? { + // label: judgment.name + }) }, }); + console.log(edges); } return { diff --git a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx index d154498e7..4e98f0db5 100644 --- a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx +++ b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx @@ -96,13 +96,38 @@ export function StockFlowGraphviz(props: { const vizLayout = () => { const viz = vizResource(); + const patternAuxiliaryVariable: Pattern = { + tag: "object", + obType: { + content: "DynamicVariable", + tag: "Basic", + } + }; + const patternVariableLink: Pattern = { + tag: "morphism", + morType: { + content: "VariableLink", + 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) + }, + (jgmt: ModelJudgment) => + match(jgmt) + .with(patternVariableLink, () => ({ style: "variable-link" })) + .with(P._, () => undefined) + + ), props.options, - ) + ) ); }; @@ -121,53 +146,25 @@ function StockFlowSVG(props: { const result: string[] = []; const nodeMap = uniqueIndexArray(props.layout?.nodes ?? [], (node) => node.id); const edgeMap = uniqueIndexArray(props.layout?.edges ?? [], (edge) => edge.id); + console.log(nodeMap, edgeMap); + const patternFlowLink: Pattern = { + tag: "morphism", + dom: { + tag: "Basic", + content: P.select("srcId"), + }, + cod: { + tag: "Tabulated", + content: { + tag: "Basic", + content: P.select("tgtId"), + }, + }, + }; for (const judgment of props.model) { - console.log(judgment); - match(judgment).with( - { - tag: "morphism", - dom: { - tag: "Basic", - content: P.select("srcId"), - }, - cod: { - tag: "Tabulated", - content: { - tag: "Basic", - content: P.select("tgtId"), - }, - }, - }, - ({ srcId, tgtId }) => { - const srcNode = nodeMap.get(srcId); - const tgtEdge = edgeMap.get(tgtId); - if (!srcNode || !tgtEdge) { - return; - } - pathElem.setAttribute("d", tgtEdge.path); - const midpoint = pathElem.getPointAtLength(pathElem.getTotalLength() / 2); - const path = quadraticCurve(srcNode.pos, midpoint, 1.0); - result.push(path.join(" ")); - }, - ); - // TODO - match(judgment).with( - { - tag: "morphism", - dom: { - tag: "Basic", - content: P.select("srcId"), - }, - cod: { - tag: "Basic", - content: P.select("tgtId"), - }, - morType: { - tag: "Basic", - content: "VariableLink" - } - }, - ({ srcId, tgtId }) => { + match(judgment) + .with(patternFlowLink, + ({ srcId, tgtId }) => { const srcNode = nodeMap.get(srcId); const tgtEdge = edgeMap.get(tgtId); if (!srcNode || !tgtEdge) { @@ -177,10 +174,9 @@ function StockFlowSVG(props: { const midpoint = pathElem.getPointAtLength(pathElem.getTotalLength() / 2); const path = quadraticCurve(srcNode.pos, midpoint, 1.0); result.push(path.join(" ")); - } - ); + }, + ); } - console.log(result); return result; }; diff --git a/packages/frontend/src/stdlib/styles.module.css b/packages/frontend/src/stdlib/styles.module.css index 31e386d56..07c22ae3e 100644 --- a/packages/frontend/src/stdlib/styles.module.css +++ b/packages/frontend/src/stdlib/styles.module.css @@ -22,3 +22,7 @@ border: 0px solid black; padding: 0px; } + +.link { + border: 1px solid blue; +} diff --git a/packages/frontend/src/stdlib/styles.module.css.d.ts b/packages/frontend/src/stdlib/styles.module.css.d.ts index 8d808407c..98647ec27 100644 --- a/packages/frontend/src/stdlib/styles.module.css.d.ts +++ b/packages/frontend/src/stdlib/styles.module.css.d.ts @@ -3,5 +3,6 @@ declare const styles: { readonly cornerBox: string; readonly circle: string; readonly point: string; + readonly link: string; }; export = styles; diff --git a/packages/frontend/src/stdlib/svg_styles.module.css b/packages/frontend/src/stdlib/svg_styles.module.css index ba773cfac..1d05709b0 100644 --- a/packages/frontend/src/stdlib/svg_styles.module.css +++ b/packages/frontend/src/stdlib/svg_styles.module.css @@ -13,4 +13,6 @@ stroke: black; } - +.link path { + stroke: blue; +} diff --git a/packages/frontend/src/stdlib/theories.tsx b/packages/frontend/src/stdlib/theories.tsx index 7bd17a837..891730a82 100644 --- a/packages/frontend/src/stdlib/theories.tsx +++ b/packages/frontend/src/stdlib/theories.tsx @@ -738,6 +738,8 @@ stdTheories.add( description: "Variable controlling stock quantity", shortcut: ["F"], arrowStyle: "flat", + cssClasses: [styles.link], + svgClasses: [svgStyles.link], }, ], modelAnalyses: [ diff --git a/packages/frontend/src/theory/types.ts b/packages/frontend/src/theory/types.ts index b6950bdc6..1724ef1d3 100644 --- a/packages/frontend/src/theory/types.ts +++ b/packages/frontend/src/theory/types.ts @@ -100,6 +100,7 @@ export class Theory { /** Get metadata for a morphism type as used in models. */ modelMorTypeMeta(typ: MorType): ModelMorTypeMeta | undefined { + console.log(this.modelTypeMeta.morTypeMeta(typ)); return this.modelTypeMeta.morTypeMeta(typ); } @@ -178,6 +179,7 @@ class TypeMetadata } morTypeMeta(typ: MorType): MorMeta | undefined { + console.log("META: ", this.morTypeIndex.get(typ), this.types); const i = this.morTypeIndex.get(typ); return i !== undefined ? (this.types[i] as MorMeta) : undefined; } diff --git a/packages/frontend/src/visualization/graph_svg.css b/packages/frontend/src/visualization/graph_svg.css index e368af704..29b2492b8 100644 --- a/packages/frontend/src/visualization/graph_svg.css +++ b/packages/frontend/src/visualization/graph_svg.css @@ -22,6 +22,11 @@ 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; From 71cc0ea9d49efb8b4da67d7bb0ec9fb0923db749 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Thu, 26 Jun 2025 08:53:57 -0700 Subject: [PATCH 05/12] Variable Links are now Blue --- packages/catlog/src/stdlib/models.rs | 3 +- packages/catlog/src/stdlib/theories.rs | 2 +- .../src/stdlib/analyses/model_graph.tsx | 9 ++- .../stdlib/analyses/stock_flow_diagram.tsx | 56 +++++++++++-------- .../src/stdlib/svg_styles.module.css.d.ts | 1 + packages/frontend/src/stdlib/theories.tsx | 10 ++-- packages/frontend/src/theory/types.ts | 2 - 7 files changed, 46 insertions(+), 37 deletions(-) diff --git a/packages/catlog/src/stdlib/models.rs b/packages/catlog/src/stdlib/models.rs index 57b44a38c..398eaa998 100644 --- a/packages/catlog/src/stdlib/models.rs +++ b/packages/catlog/src/stdlib/models.rs @@ -138,9 +138,8 @@ pub fn water_volume(th: Rc) -> UstrDiscreteTabModel { 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"); - let dynamible_type = TabObType::Basic(ustr("DynamicVariable")); // flow link - model.add_ob(spillover, dynamible_type.clone()); + model.add_ob(spillover, TabObType::Basic(ustr("AuxiliaryVariable"))); model.add_mor( ustr("dynVolume"), TabOb::Basic(spillover), diff --git a/packages/catlog/src/stdlib/theories.rs b/packages/catlog/src/stdlib/theories.rs index 1c2b36811..58fc320f6 100644 --- a/packages/catlog/src/stdlib/theories.rs +++ b/packages/catlog/src/stdlib/theories.rs @@ -159,7 +159,7 @@ pub fn th_category_dynamic_stockflow() -> UstrDiscreteTabTheory { TabObType::Basic(x), th.tabulator(th.hom_type(TabObType::Basic(x))), ); - let v = ustr("DynamicVariable"); + 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( diff --git a/packages/frontend/src/stdlib/analyses/model_graph.tsx b/packages/frontend/src/stdlib/analyses/model_graph.tsx index 800307425..9eda4db00 100644 --- a/packages/frontend/src/stdlib/analyses/model_graph.tsx +++ b/packages/frontend/src/stdlib/analyses/model_graph.tsx @@ -108,7 +108,7 @@ export function modelToGraphviz( theory: Theory, attributes?: GV.GraphvizAttributes, nodeAttributes?: (jgmt: ModelJudgment) => Record | undefined, - edgeAttributes?: (jgmt: ModelJudgment) => Record | undefined, + // edgeAttributes?: (jgmt: ModelJudgment) => Record | undefined, ): Viz.Graph { const nodes = new Map["nodes"][0]>(); for (const judgment of model) { @@ -163,12 +163,11 @@ export function modelToGraphviz( fontname: GV.graphvizFontname(meta), // Not recognized by Graphviz but will be passed through! arrowstyle: meta?.arrowStyle ?? "default", - ...(edgeAttributes?.(judgment).state.value ?? { - // label: judgment.name - }) + // ...(edgeAttributes?.(judgment).state.value ?? { + // // label: judgment.name + // }) }, }); - console.log(edges); } return { diff --git a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx index 4e98f0db5..7eab2236e 100644 --- a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx +++ b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx @@ -96,35 +96,34 @@ export function StockFlowGraphviz(props: { const vizLayout = () => { const viz = vizResource(); - const patternAuxiliaryVariable: Pattern = { + const patternAuxiliaryVariable: P.Pattern = { tag: "object", obType: { - content: "DynamicVariable", + content: "AuxiliaryVariable", tag: "Basic", } }; - const patternVariableLink: Pattern = { - tag: "morphism", - morType: { - content: "VariableLink", - tag: "Basic", - } - }; + // const patternVariableLink: P.Pattern = { + // tag: "morphism", + // morType: { + // content: "VariableLink", + // tag: "Basic", + // } + // }; return ( viz && vizLayoutGraph( viz, modelToGraphviz(props.model, props.theory, props.attributes, - (jgmt: ModelJudgment) => { + (jgmt: ModelJudgment) => match(jgmt) .with(patternAuxiliaryVariable, () => ({ xlabel: jgmt.name, label: "" })) .with(P._, () => undefined) - }, - (jgmt: ModelJudgment) => - match(jgmt) - .with(patternVariableLink, () => ({ style: "variable-link" })) - .with(P._, () => undefined) - + .run() + // (jgmt: ModelJudgment) => + // match(jgmt) + // .with(patternVariableLink, () => ({ style: "variable-link" })) + // .with(P._, () => undefined) ), props.options, ) @@ -146,8 +145,24 @@ function StockFlowSVG(props: { const result: string[] = []; const nodeMap = uniqueIndexArray(props.layout?.nodes ?? [], (node) => node.id); const edgeMap = uniqueIndexArray(props.layout?.edges ?? [], (edge) => edge.id); - console.log(nodeMap, edgeMap); - const patternFlowLink: Pattern = { + // const patternFlowLink: P.Pattern = { + // tag: "morphism", + // dom: { + // tag: "Basic", + // content: P.select("srcId"), + // }, + // cod: { + // tag: "Tabulated", + // content: { + // tag: "Basic", + // content: P.select("tgtId"), + // }, + // }, + // }; + for (const judgment of props.model) { + match(judgment) + .with( + { tag: "morphism", dom: { tag: "Basic", @@ -160,10 +175,7 @@ function StockFlowSVG(props: { content: P.select("tgtId"), }, }, - }; - for (const judgment of props.model) { - match(judgment) - .with(patternFlowLink, + }, ({ srcId, tgtId }) => { const srcNode = nodeMap.get(srcId); const tgtEdge = edgeMap.get(tgtId); diff --git a/packages/frontend/src/stdlib/svg_styles.module.css.d.ts b/packages/frontend/src/stdlib/svg_styles.module.css.d.ts index 62dc623d8..f20b8693a 100644 --- a/packages/frontend/src/stdlib/svg_styles.module.css.d.ts +++ b/packages/frontend/src/stdlib/svg_styles.module.css.d.ts @@ -2,5 +2,6 @@ declare const styles: { readonly box: string; readonly circle: string; readonly point: string; + readonly link: string; }; export = styles; diff --git a/packages/frontend/src/stdlib/theories.tsx b/packages/frontend/src/stdlib/theories.tsx index 891730a82..4e1f3c580 100644 --- a/packages/frontend/src/stdlib/theories.tsx +++ b/packages/frontend/src/stdlib/theories.tsx @@ -716,10 +716,10 @@ stdTheories.add( }, { tag: "ObType", - obType: { tag: "Basic", content: "DynamicVariable" }, + obType: { tag: "Basic", content: "AuxiliaryVariable" }, name: "Variable", - description: "Variable stuff", - shortcut: ["V"], + description: "A function of different stocks", + shortcut: ["A"], cssClasses: [styles.point], svgClasses: [svgStyles.point], }, @@ -727,7 +727,7 @@ stdTheories.add( tag: "MorType", morType: { tag: "Basic", content: "FlowLink" }, name: "Flow Link", - description: "Influence of a stock on a flow", + description: "Influence of an auxiliary variable on a flow", preferUnnamed: true, shortcut: ["L"], }, @@ -736,7 +736,7 @@ stdTheories.add( morType: { tag: "Basic", content: "VariableLink" }, name: "Variable Link", description: "Variable controlling stock quantity", - shortcut: ["F"], + shortcut: ["V"], arrowStyle: "flat", cssClasses: [styles.link], svgClasses: [svgStyles.link], diff --git a/packages/frontend/src/theory/types.ts b/packages/frontend/src/theory/types.ts index 1724ef1d3..b6950bdc6 100644 --- a/packages/frontend/src/theory/types.ts +++ b/packages/frontend/src/theory/types.ts @@ -100,7 +100,6 @@ export class Theory { /** Get metadata for a morphism type as used in models. */ modelMorTypeMeta(typ: MorType): ModelMorTypeMeta | undefined { - console.log(this.modelTypeMeta.morTypeMeta(typ)); return this.modelTypeMeta.morTypeMeta(typ); } @@ -179,7 +178,6 @@ class TypeMetadata } morTypeMeta(typ: MorType): MorMeta | undefined { - console.log("META: ", this.morTypeIndex.get(typ), this.types); const i = this.morTypeIndex.get(typ); return i !== undefined ? (this.types[i] as MorMeta) : undefined; } From c4e1372c7dd6491d42be824094a4ac7898a5635c Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Mon, 30 Jun 2025 15:24:17 -0700 Subject: [PATCH 06/12] format/lint, once more --- packages/catlog-wasm/src/theories.rs | 2 +- packages/catlog/src/stdlib/models.rs | 2 +- packages/catlog/src/stdlib/theories.rs | 2 +- .../src/stdlib/analyses/model_graph.tsx | 30 ++-- .../stdlib/analyses/stock_flow_diagram.tsx | 143 +++++++++--------- .../frontend/src/stdlib/styles.module.css | 6 +- .../src/stdlib/styles.module.css.d.ts | 6 +- .../frontend/src/stdlib/svg_styles.module.css | 6 +- .../src/stdlib/svg_styles.module.css.d.ts | 6 +- packages/frontend/src/stdlib/theories.tsx | 2 +- .../src/visualization/graph_layout.ts | 4 +- .../frontend/src/visualization/graph_svg.css | 6 +- .../frontend/src/visualization/graph_svg.tsx | 93 ++++++++---- .../frontend/src/visualization/graphviz.ts | 4 +- 14 files changed, 171 insertions(+), 141 deletions(-) diff --git a/packages/catlog-wasm/src/theories.rs b/packages/catlog-wasm/src/theories.rs index be4621875..423c64d59 100644 --- a/packages/catlog-wasm/src/theories.rs +++ b/packages/catlog-wasm/src/theories.rs @@ -13,7 +13,7 @@ use catlog::dbl::{model, theory}; use catlog::one::Path; use catlog::stdlib::{analyses, models, theories}; -use super::model_morphism::{motifs, MotifsOptions}; +use super::model_morphism::{MotifsOptions, motifs}; use super::{analyses::*, model::DblModel, theory::DblTheory}; /// The empty or initial theory. diff --git a/packages/catlog/src/stdlib/models.rs b/packages/catlog/src/stdlib/models.rs index 398eaa998..5011d4cf7 100644 --- a/packages/catlog/src/stdlib/models.rs +++ b/packages/catlog/src/stdlib/models.rs @@ -1,7 +1,7 @@ //! Standard library of models of double theories. use std::rc::Rc; -use ustr::{ustr, Ustr}; +use ustr::{Ustr, ustr}; use crate::dbl::{model::*, theory::*}; use crate::one::Path; diff --git a/packages/catlog/src/stdlib/theories.rs b/packages/catlog/src/stdlib/theories.rs index 58fc320f6..f7745a5bc 100644 --- a/packages/catlog/src/stdlib/theories.rs +++ b/packages/catlog/src/stdlib/theories.rs @@ -3,7 +3,7 @@ use ustr::ustr; use crate::dbl::theory::*; -use crate::one::{fp_category::UstrFpCategory, Path}; +use crate::one::{Path, fp_category::UstrFpCategory}; /** The empty theory, which has a single model, the empty model. diff --git a/packages/frontend/src/stdlib/analyses/model_graph.tsx b/packages/frontend/src/stdlib/analyses/model_graph.tsx index 9eda4db00..d79ef5099 100644 --- a/packages/frontend/src/stdlib/analyses/model_graph.tsx +++ b/packages/frontend/src/stdlib/analyses/model_graph.tsx @@ -6,11 +6,7 @@ import type { ModelJudgment } from "catlog-wasm"; import type { ModelAnalysisProps } from "../../analysis"; import { Foldable } from "../../components"; import type { ModelAnalysisMeta, Theory } from "../../theory"; -import { - DownloadSVGButton, - GraphvizSVG, - type SVGRefProp, -} from "../../visualization"; +import { DownloadSVGButton, GraphvizSVG, type SVGRefProp } from "../../visualization"; import * as GV from "./graph_visualization"; import "./graph_visualization.css"; @@ -61,10 +57,7 @@ export function ModelGraph( return (
- +
@@ -91,7 +84,6 @@ export function ModelGraphviz(props: { options?: Viz.RenderOptions; ref?: SVGRefProp; }) { - return ( Record | undefined, - // edgeAttributes?: (jgmt: ModelJudgment) => Record | undefined, + // edgeAttributes?: (jgmt: ModelJudgment) => Record | undefined, ): Viz.Graph { const nodes = new Map["nodes"][0]>(); for (const judgment of model) { @@ -121,15 +113,15 @@ export function modelToGraphviz( id, class: GV.svgCssClasses(meta).join(" "), fontname: GV.graphvizFontname(meta), - ...(nodeAttributes?.(judgment) ?? { + ...(nodeAttributes?.(judgment) ?? { label: name, - }) - }} - ) + }), + }, + }); } } - const edges: Required["edges"] = []; + const edges: Required["edges"] = []; for (const judgment of model) { const matched = match(judgment) .with( @@ -163,9 +155,9 @@ export function modelToGraphviz( fontname: GV.graphvizFontname(meta), // Not recognized by Graphviz but will be passed through! arrowstyle: meta?.arrowStyle ?? "default", - // ...(edgeAttributes?.(judgment).state.value ?? { - // // label: judgment.name - // }) + // ...(edgeAttributes?.(judgment).state.value ?? { + // // label: judgment.name + // }) }, }); } diff --git a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx index 7eab2236e..d0774647d 100644 --- a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx +++ b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx @@ -96,37 +96,43 @@ export function StockFlowGraphviz(props: { const vizLayout = () => { const viz = vizResource(); - const patternAuxiliaryVariable: P.Pattern = { - tag: "object", - obType: { - content: "AuxiliaryVariable", - tag: "Basic", - } - }; - // const patternVariableLink: P.Pattern = { - // tag: "morphism", - // morType: { - // content: "VariableLink", - // tag: "Basic", - // } - // }; + const patternAuxiliaryVariable: P.Pattern = { + tag: "object", + obType: { + content: "AuxiliaryVariable", + tag: "Basic", + }, + }; + // const patternVariableLink: P.Pattern = { + // tag: "morphism", + // morType: { + // content: "VariableLink", + // tag: "Basic", + // } + // }; return ( viz && vizLayoutGraph( viz, - modelToGraphviz(props.model, props.theory, props.attributes, - (jgmt: ModelJudgment) => - match(jgmt) - .with(patternAuxiliaryVariable, () => ({ xlabel: jgmt.name, label: "" })) - .with(P._, () => undefined) - .run() - // (jgmt: ModelJudgment) => - // match(jgmt) - // .with(patternVariableLink, () => ({ style: "variable-link" })) - // .with(P._, () => undefined) - ), + modelToGraphviz( + props.model, + props.theory, + props.attributes, + (jgmt: ModelJudgment) => + match(jgmt) + .with(patternAuxiliaryVariable, () => ({ + xlabel: jgmt.name, + label: "", + })) + .with(P._, () => undefined) + .run(), + // (jgmt: ModelJudgment) => + // match(jgmt) + // .with(patternVariableLink, () => ({ style: "variable-link" })) + // .with(P._, () => undefined) + ), props.options, - ) + ) ); }; @@ -145,50 +151,49 @@ function StockFlowSVG(props: { const result: string[] = []; const nodeMap = uniqueIndexArray(props.layout?.nodes ?? [], (node) => node.id); const edgeMap = uniqueIndexArray(props.layout?.edges ?? [], (edge) => edge.id); - // const patternFlowLink: P.Pattern = { - // tag: "morphism", - // dom: { - // tag: "Basic", - // content: P.select("srcId"), - // }, - // cod: { - // tag: "Tabulated", - // content: { - // tag: "Basic", - // content: P.select("tgtId"), - // }, - // }, - // }; + // const patternFlowLink: P.Pattern = { + // tag: "morphism", + // dom: { + // tag: "Basic", + // content: P.select("srcId"), + // }, + // cod: { + // tag: "Tabulated", + // content: { + // tag: "Basic", + // content: P.select("tgtId"), + // }, + // }, + // }; for (const judgment of props.model) { - match(judgment) - .with( - { - tag: "morphism", - dom: { - tag: "Basic", - content: P.select("srcId"), - }, - cod: { - tag: "Tabulated", - content: { - tag: "Basic", - content: P.select("tgtId"), - }, - }, - }, - ({ srcId, tgtId }) => { - const srcNode = nodeMap.get(srcId); - const tgtEdge = edgeMap.get(tgtId); - if (!srcNode || !tgtEdge) { - return; - } - pathElem.setAttribute("d", tgtEdge.path); - const midpoint = pathElem.getPointAtLength(pathElem.getTotalLength() / 2); - const path = quadraticCurve(srcNode.pos, midpoint, 1.0); - result.push(path.join(" ")); - }, - ); - } + match(judgment).with( + { + tag: "morphism", + dom: { + tag: "Basic", + content: P.select("srcId"), + }, + cod: { + tag: "Tabulated", + content: { + tag: "Basic", + content: P.select("tgtId"), + }, + }, + }, + ({ srcId, tgtId }) => { + const srcNode = nodeMap.get(srcId); + const tgtEdge = edgeMap.get(tgtId); + if (!srcNode || !tgtEdge) { + return; + } + pathElem.setAttribute("d", tgtEdge.path); + const midpoint = pathElem.getPointAtLength(pathElem.getTotalLength() / 2); + const path = quadraticCurve(srcNode.pos, midpoint, 1.0); + result.push(path.join(" ")); + }, + ); + } return result; }; diff --git a/packages/frontend/src/stdlib/styles.module.css b/packages/frontend/src/stdlib/styles.module.css index 07c22ae3e..63f9e17a0 100644 --- a/packages/frontend/src/stdlib/styles.module.css +++ b/packages/frontend/src/stdlib/styles.module.css @@ -15,14 +15,14 @@ .circle { border: 1px solid black; - padding: 2px; + padding: 2px; } .point { border: 0px solid black; - padding: 0px; + padding: 0px; } .link { - border: 1px solid blue; + border: 1px solid blue; } diff --git a/packages/frontend/src/stdlib/styles.module.css.d.ts b/packages/frontend/src/stdlib/styles.module.css.d.ts index 98647ec27..f040f1808 100644 --- a/packages/frontend/src/stdlib/styles.module.css.d.ts +++ b/packages/frontend/src/stdlib/styles.module.css.d.ts @@ -1,8 +1,8 @@ declare const styles: { readonly box: string; readonly cornerBox: string; - readonly circle: string; - readonly point: string; - readonly link: string; + readonly circle: string; + readonly point: string; + readonly link: string; }; export = styles; diff --git a/packages/frontend/src/stdlib/svg_styles.module.css b/packages/frontend/src/stdlib/svg_styles.module.css index 1d05709b0..1e31a4ad7 100644 --- a/packages/frontend/src/stdlib/svg_styles.module.css +++ b/packages/frontend/src/stdlib/svg_styles.module.css @@ -5,14 +5,14 @@ .circle circle { fill: white; - stroke: black; + stroke: black; } .point point { fill: black; - stroke: black; + stroke: black; } .link path { - stroke: blue; + stroke: blue; } diff --git a/packages/frontend/src/stdlib/svg_styles.module.css.d.ts b/packages/frontend/src/stdlib/svg_styles.module.css.d.ts index f20b8693a..4e8a37867 100644 --- a/packages/frontend/src/stdlib/svg_styles.module.css.d.ts +++ b/packages/frontend/src/stdlib/svg_styles.module.css.d.ts @@ -1,7 +1,7 @@ declare const styles: { readonly box: string; - readonly circle: string; - readonly point: string; - readonly link: string; + readonly circle: string; + readonly point: string; + readonly link: string; }; export = styles; diff --git a/packages/frontend/src/stdlib/theories.tsx b/packages/frontend/src/stdlib/theories.tsx index 7862ff045..f033f6bfc 100644 --- a/packages/frontend/src/stdlib/theories.tsx +++ b/packages/frontend/src/stdlib/theories.tsx @@ -744,7 +744,7 @@ stdTheories.add( description: "Variable controlling stock quantity", shortcut: ["V"], arrowStyle: "flat", - cssClasses: [styles.link], + cssClasses: [styles.link], svgClasses: [svgStyles.link], }, ], diff --git a/packages/frontend/src/visualization/graph_layout.ts b/packages/frontend/src/visualization/graph_layout.ts index 525c6f44e..7307d7876 100644 --- a/packages/frontend/src/visualization/graph_layout.ts +++ b/packages/frontend/src/visualization/graph_layout.ts @@ -34,8 +34,8 @@ export interface Node extends GraphElement { /** Node label, if any. */ label?: string; - /** Position of center of label. */ - labelPos?: Point; + /** Position of center of label. */ + labelPos?: Point; } export interface Edge extends GraphElement { diff --git a/packages/frontend/src/visualization/graph_svg.css b/packages/frontend/src/visualization/graph_svg.css index 29b2492b8..b5ecf9e77 100644 --- a/packages/frontend/src/visualization/graph_svg.css +++ b/packages/frontend/src/visualization/graph_svg.css @@ -23,9 +23,9 @@ stroke-width: 1.5; } .edge path.variable-link { - fill: none; - stroke: blue; - stroke-width: 1.5; + fill: none; + stroke: blue; + stroke-width: 1.5; } .edge path.double-outer { diff --git a/packages/frontend/src/visualization/graph_svg.tsx b/packages/frontend/src/visualization/graph_svg.tsx index 5a932c07f..b1c4cd2d8 100644 --- a/packages/frontend/src/visualization/graph_svg.tsx +++ b/packages/frontend/src/visualization/graph_svg.tsx @@ -48,37 +48,70 @@ export function NodeSVG(props: { node: GraphLayout.Node }) { return ( - - {props.node.label} - }> - - - - - - - - - - + + {props.node.label} + + } + > + + + + + + + + + + - - - - {props.node.label} - - - - - {props.node.label} - - - - - {props.node.label} - - - + + + + {props.node.label} + + + + + {props.node.label} + + + + + {props.node.label} + + + ); diff --git a/packages/frontend/src/visualization/graphviz.ts b/packages/frontend/src/visualization/graphviz.ts index 6ee010f75..570a00015 100644 --- a/packages/frontend/src/visualization/graphviz.ts +++ b/packages/frontend/src/visualization/graphviz.ts @@ -68,8 +68,8 @@ export function parseGraphvizJSON(graphviz: GraphvizJSON.Graph): GraphLayout.Gra width: inchesToPoints(Number.parseFloat(node.width)), height: inchesToPoints(Number.parseFloat(node.height)), label: node.xlabel ?? node.label, - labelPos: parsePoint(node.pos), - // (node.xlp && parsePoint(node.xlp)) || (node.lp && parsePoint(node.lp)) || undefined, + labelPos: parsePoint(node.pos), + // (node.xlp && parsePoint(node.xlp)) || (node.lp && parsePoint(node.lp)) || undefined, cssClass: node.class, }); } From de020f33cdb7b0446bcc01d3afdbe4e0b503ab76 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Mon, 30 Jun 2025 20:39:38 -0700 Subject: [PATCH 07/12] fixed a test --- packages/catlog-wasm/src/theories.rs | 4 ++-- packages/catlog/src/dbl/theory.rs | 2 +- packages/catlog/src/stdlib/models.rs | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/catlog-wasm/src/theories.rs b/packages/catlog-wasm/src/theories.rs index 423c64d59..3aaff8e6e 100644 --- a/packages/catlog-wasm/src/theories.rs +++ b/packages/catlog-wasm/src/theories.rs @@ -13,7 +13,7 @@ use catlog::dbl::{model, theory}; use catlog::one::Path; use catlog::stdlib::{analyses, models, theories}; -use super::model_morphism::{MotifsOptions, motifs}; +use super::model_morphism::{motifs, MotifsOptions}; use super::{analyses::*, model::DblModel, theory::DblTheory}; /// The empty or initial theory. @@ -309,7 +309,7 @@ impl ThCategoryDynamicStockFlow { analyses::ode::StockFlowMassActionAnalysis::default() .create_numerical_system(model, data.0) .solve_with_defaults() - .map_err(|err| format!("{:?}", err)) + .map_err(|err| format!("{err:?}")) .into(), )) } diff --git a/packages/catlog/src/dbl/theory.rs b/packages/catlog/src/dbl/theory.rs index 0832be637..d1f6e6aec 100644 --- a/packages/catlog/src/dbl/theory.rs +++ b/packages/catlog/src/dbl/theory.rs @@ -539,7 +539,7 @@ where self.tgt.set(e.clone(), tgt); self.make_mor_type(e) } - + /// Adds a generating morphim type without initializing its source/target. pub fn make_mor_type(&mut self, e: E) -> bool { self.mor_types.insert(e) diff --git a/packages/catlog/src/stdlib/models.rs b/packages/catlog/src/stdlib/models.rs index 5011d4cf7..8efc8adcd 100644 --- a/packages/catlog/src/stdlib/models.rs +++ b/packages/catlog/src/stdlib/models.rs @@ -199,8 +199,8 @@ mod tests { } #[test] - fn categories_dynvar() { - let th = Rc::new(th_category_dynvar()); + fn categories_dynamic_stockflow() { + let th = Rc::new(th_category_dynamic_stockflow()); assert!(water_volume(th).validate().is_ok()); } } From e60fcd6f966f4e65119d292e8c9c31e6c76607bc Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Mon, 30 Jun 2025 21:08:08 -0700 Subject: [PATCH 08/12] one more line needed formatting --- packages/catlog-wasm/src/theories.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catlog-wasm/src/theories.rs b/packages/catlog-wasm/src/theories.rs index 3aaff8e6e..523c85c4a 100644 --- a/packages/catlog-wasm/src/theories.rs +++ b/packages/catlog-wasm/src/theories.rs @@ -13,7 +13,7 @@ use catlog::dbl::{model, theory}; use catlog::one::Path; use catlog::stdlib::{analyses, models, theories}; -use super::model_morphism::{motifs, MotifsOptions}; +use super::model_morphism::{MotifsOptions, motifs}; use super::{analyses::*, model::DblModel, theory::DblTheory}; /// The empty or initial theory. From 64c709a50c95e2edd73ac87f5f71cd226c870275 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Tue, 1 Jul 2025 09:56:39 -0700 Subject: [PATCH 09/12] removed superfluous comments in code --- .../src/stdlib/analyses/model_graph.tsx | 6 ++--- .../stdlib/analyses/stock_flow_diagram.tsx | 25 ------------------- .../frontend/src/visualization/graph_svg.tsx | 1 - .../src/visualization/graphviz_svg.tsx | 1 - 4 files changed, 2 insertions(+), 31 deletions(-) diff --git a/packages/frontend/src/stdlib/analyses/model_graph.tsx b/packages/frontend/src/stdlib/analyses/model_graph.tsx index d79ef5099..7fc3f540a 100644 --- a/packages/frontend/src/stdlib/analyses/model_graph.tsx +++ b/packages/frontend/src/stdlib/analyses/model_graph.tsx @@ -100,7 +100,7 @@ export function modelToGraphviz( theory: Theory, attributes?: GV.GraphvizAttributes, nodeAttributes?: (jgmt: ModelJudgment) => Record | undefined, - // edgeAttributes?: (jgmt: ModelJudgment) => Record | undefined, + edgeAttributes?: (jgmt: ModelJudgment) => Record | undefined, ): Viz.Graph { const nodes = new Map["nodes"][0]>(); for (const judgment of model) { @@ -155,9 +155,7 @@ export function modelToGraphviz( fontname: GV.graphvizFontname(meta), // Not recognized by Graphviz but will be passed through! arrowstyle: meta?.arrowStyle ?? "default", - // ...(edgeAttributes?.(judgment).state.value ?? { - // // label: judgment.name - // }) + ...(edgeAttributes?.(judgment) ?? {}), }, }); } diff --git a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx index d0774647d..0c86b7126 100644 --- a/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx +++ b/packages/frontend/src/stdlib/analyses/stock_flow_diagram.tsx @@ -103,13 +103,6 @@ export function StockFlowGraphviz(props: { tag: "Basic", }, }; - // const patternVariableLink: P.Pattern = { - // tag: "morphism", - // morType: { - // content: "VariableLink", - // tag: "Basic", - // } - // }; return ( viz && vizLayoutGraph( @@ -126,10 +119,6 @@ export function StockFlowGraphviz(props: { })) .with(P._, () => undefined) .run(), - // (jgmt: ModelJudgment) => - // match(jgmt) - // .with(patternVariableLink, () => ({ style: "variable-link" })) - // .with(P._, () => undefined) ), props.options, ) @@ -151,20 +140,6 @@ function StockFlowSVG(props: { const result: string[] = []; const nodeMap = uniqueIndexArray(props.layout?.nodes ?? [], (node) => node.id); const edgeMap = uniqueIndexArray(props.layout?.edges ?? [], (edge) => edge.id); - // const patternFlowLink: P.Pattern = { - // tag: "morphism", - // dom: { - // tag: "Basic", - // content: P.select("srcId"), - // }, - // cod: { - // tag: "Tabulated", - // content: { - // tag: "Basic", - // content: P.select("tgtId"), - // }, - // }, - // }; for (const judgment of props.model) { match(judgment).with( { diff --git a/packages/frontend/src/visualization/graph_svg.tsx b/packages/frontend/src/visualization/graph_svg.tsx index b1c4cd2d8..6066bddc6 100644 --- a/packages/frontend/src/visualization/graph_svg.tsx +++ b/packages/frontend/src/visualization/graph_svg.tsx @@ -37,7 +37,6 @@ export function GraphSVG(props: { /** Draw a node with a layout using SVG. */ export function NodeSVG(props: { node: GraphLayout.Node }) { - // do we use an || operator? const { node: { pos: { x, y }, diff --git a/packages/frontend/src/visualization/graphviz_svg.tsx b/packages/frontend/src/visualization/graphviz_svg.tsx index 6f1f6c099..864ec9faa 100644 --- a/packages/frontend/src/visualization/graphviz_svg.tsx +++ b/packages/frontend/src/visualization/graphviz_svg.tsx @@ -35,6 +35,5 @@ function GraphvizOutputSVG(props: { graph?: GraphvizJSON.Graph; ref?: SVGRefProp; }) { - console.log(props.graph && parseGraphvizJSON(props.graph)); return ; } From aa608bc557e02af223458825c4be259869a7e0b8 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Wed, 9 Jul 2025 08:37:33 -0700 Subject: [PATCH 10/12] FEEDBACK: removed stockflow analysis from dynamic stockflow --- packages/catlog-wasm/src/theories.rs | 19 ------------------- packages/catlog/src/stdlib/theories.rs | 11 ----------- packages/frontend/src/stdlib/theories.tsx | 10 +--------- 3 files changed, 1 insertion(+), 39 deletions(-) diff --git a/packages/catlog-wasm/src/theories.rs b/packages/catlog-wasm/src/theories.rs index 523c85c4a..da79ee2ef 100644 --- a/packages/catlog-wasm/src/theories.rs +++ b/packages/catlog-wasm/src/theories.rs @@ -294,25 +294,6 @@ impl ThCategoryDynamicStockFlow { pub fn theory(&self) -> DblTheory { DblTheory(self.0.clone().into()) } - - /// Simulates the mass-action system derived from a model. - #[wasm_bindgen(js_name = "mass_action")] - pub fn mass_action( - &self, - model: &DblModel, - data: MassActionModelData, - ) -> Result { - let model: &model::DiscreteTabModel<_, _, _> = (&model.0) - .try_into() - .map_err(|_| "Mass-action simulation expects a discrete tabulator model")?; - Ok(ODEResult( - analyses::ode::StockFlowMassActionAnalysis::default() - .create_numerical_system(model, data.0) - .solve_with_defaults() - .map_err(|err| format!("{err:?}")) - .into(), - )) - } } #[cfg(test)] diff --git a/packages/catlog/src/stdlib/theories.rs b/packages/catlog/src/stdlib/theories.rs index f7745a5bc..b08199ff6 100644 --- a/packages/catlog/src/stdlib/theories.rs +++ b/packages/catlog/src/stdlib/theories.rs @@ -143,17 +143,6 @@ pub fn th_category_dynamic_stockflow() -> UstrDiscreteTabTheory { let mut th: UstrDiscreteTabTheory = Default::default(); let x = ustr("Object"); th.add_ob_type(x); - // there are two proarrows from stocks to flows. they correspond to inflows and outflows. - th.add_mor_type( - ustr("Inflow"), - TabObType::Basic(x), - th.tabulator(th.hom_type(TabObType::Basic(x))), - ); - th.add_mor_type( - ustr("Outflow"), - TabObType::Basic(x), - th.tabulator(th.hom_type(TabObType::Basic(x))), - ); th.add_mor_type( ustr("Link"), TabObType::Basic(x), diff --git a/packages/frontend/src/stdlib/theories.tsx b/packages/frontend/src/stdlib/theories.tsx index f033f6bfc..8770091c4 100644 --- a/packages/frontend/src/stdlib/theories.tsx +++ b/packages/frontend/src/stdlib/theories.tsx @@ -741,7 +741,7 @@ stdTheories.add( tag: "MorType", morType: { tag: "Basic", content: "VariableLink" }, name: "Variable Link", - description: "Variable controlling stock quantity", + description: "Variable referencing a stock quantity", shortcut: ["V"], arrowStyle: "flat", cssClasses: [styles.link], @@ -754,14 +754,6 @@ stdTheories.add( name: "Visualization", description: "Visualize the stock and flow diagram", }), - analyses.configureMassAction({ - simulate(model, data) { - return thCategoryDynamicStockFlow.mass_action(model, data); - }, - isTransition(mor) { - return mor.morType.tag === "Hom"; - }, - }), ], }); }, From 722067027bc3862c4afdfbfd5265534b9cc9fb96 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Wed, 9 Jul 2025 17:10:19 -0700 Subject: [PATCH 11/12] CICD: formatted --- packages/catlog/src/stdlib/theories.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/catlog/src/stdlib/theories.rs b/packages/catlog/src/stdlib/theories.rs index c86065318..f6f8c2f92 100644 --- a/packages/catlog/src/stdlib/theories.rs +++ b/packages/catlog/src/stdlib/theories.rs @@ -3,7 +3,7 @@ use ustr::ustr; use crate::dbl::theory::*; -use crate::one::{Path, fp_category::UstrFpCategory}; +use crate::one::{fp_category::UstrFpCategory, Path}; /** The empty theory, which has a single model, the empty model. @@ -136,9 +136,7 @@ pub fn th_category_links() -> UstrDiscreteTabTheory { th } -/** The theory of stock and flow diagrams with dynamic variables. - -*/ +/// 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"); @@ -160,7 +158,7 @@ pub fn th_category_dynamic_stockflow() -> UstrDiscreteTabTheory { 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_monad_algebra(Mode::List) From 93cc10a5c490d14e976b74b8cda116460e7ac7c0 Mon Sep 17 00:00:00 2001 From: Matt Cuffaro Date: Wed, 16 Jul 2025 08:06:18 -0700 Subject: [PATCH 12/12] CICD: formatted --- packages/catlog/src/stdlib/theories.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/catlog/src/stdlib/theories.rs b/packages/catlog/src/stdlib/theories.rs index 844dabb4a..6449c1e69 100644 --- a/packages/catlog/src/stdlib/theories.rs +++ b/packages/catlog/src/stdlib/theories.rs @@ -3,7 +3,7 @@ use ustr::ustr; use crate::dbl::theory::*; -use crate::one::{fp_category::UstrFpCategory, Path}; +use crate::one::{Path, fp_category::UstrFpCategory}; /** The empty theory, which has a single model, the empty model.