From 81c9329ab2611e1da138b8c15195a177d9b94edd Mon Sep 17 00:00:00 2001 From: Tim Hosgood Date: Wed, 17 Dec 2025 15:46:24 +0000 Subject: [PATCH 1/6] first draft --- packages/catlog/src/stdlib/analyses/mod.rs | 1 + .../analyses/signed_links_to_signed_cat.rs | 93 +++++++++++++++++++ 2 files changed, 94 insertions(+) create mode 100644 packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs diff --git a/packages/catlog/src/stdlib/analyses/mod.rs b/packages/catlog/src/stdlib/analyses/mod.rs index ed10465d1..9b933c39d 100644 --- a/packages/catlog/src/stdlib/analyses/mod.rs +++ b/packages/catlog/src/stdlib/analyses/mod.rs @@ -4,3 +4,4 @@ pub mod ode; pub mod reachability; +pub mod signed_links_to_signed_cat; diff --git a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs new file mode 100644 index 000000000..2da18dd7d --- /dev/null +++ b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs @@ -0,0 +1,93 @@ +//! Migration for going from categories with signed links to signed categories +// (e.g. from signed stock-flow diagrams to causal loop diagrams) + +use crate::dbl::discrete_tabulator::theory::TabMorType; +use crate::dbl::discrete_tabulator::theory::TabObType; +use std::rc::Rc; + +use crate::dbl::discrete::model::DiscreteDblModel; +use crate::dbl::discrete_tabulator::model::DiscreteTabModel; +use crate::dbl::model::{FgDblModel, MutDblModel}; +use crate::one::category::FgCategory; +use crate::one::path::Path; +use crate::stdlib::theories; +use crate::zero::name; + +/** Span-migration for categories with signed links. + * + * We create a CLD from a category with signed links from the query defined on + * objects as + * + * V |-> stock : S | flow : F + * E+ |-> out : F | link : L+ + * E- |-> in : F | link : L- + * + * and on morphisms as (...) something that will be written up eventually, but + * in short can be described quite simply in words: + * + * 1. For each stock, create a vertex + * 2. For each flow, create a (+,-)-span, where the apex is a new vertex + * corresponding to the flow, and there is a negative arrow to the (vertex + * corresponding to the) source of the flow, and a positive arrow to the + * (vertex corresponding to the) target of the flow + * 3. For each (signed) link, create an arrow (of the same sign) from the + * (vertex corresponding to the) source stock to the (vertex corresponding + * to the) target flow + */ +pub fn migrate(model: DiscreteTabModel) -> DiscreteDblModel { + let mut migrated_model: DiscreteDblModel = + DiscreteDblModel::new(Rc::new(theories::th_signed_category())); + + let stock_type = TabObType::Basic(name("Object")); + let flow_type = TabMorType::Hom(Box::new(stock_type.clone())); + let pos_link_type = TabMorType::Basic(name("Link")); + let neg_link_type = TabMorType::Basic(name("NegativeLink")); + + // Create an object for each stock (a "stock-object") + for s in model.ob_generators() { + migrated_model.add_ob(s.clone(), name("Object")); + } + + // Create a span for each flow + for f in model.mor_generators_with_type(&flow_type) { + // An object for each flow (a "flow-object") + migrated_model.add_ob(f.clone(), name("Object")); + // A negative link from the flow object to the flow-source object + migrated_model.add_mor( + format!("{}_in", f).as_str().into(), + f.clone(), + model.mor_generator_dom(&f).unwrap_basic(), + Path::Id(name("Object")), + ); + // A positive link from the flow object to the flow-target object + migrated_model.add_mor( + format!("{}_in", f).as_str().into(), + f.clone(), + model.mor_generator_cod(&f).unwrap_basic(), + name("Negative").into(), + ); + } + + // Create a positive arrow for each positive link + for pl in model.mor_generators_with_type(&pos_link_type) { + migrated_model.add_mor( + pl.clone(), + model.mor_generator_dom(&pl).unwrap_basic(), + model.mor_generator_cod(&pl).unwrap_basic(), + Path::Id(name("Object")), + ); + } + // Create a negative arrow for each negative link + for nl in model.mor_generators_with_type(&neg_link_type) { + migrated_model.add_mor( + nl.clone(), + model.mor_generator_dom(&nl).unwrap_basic(), + model.mor_generator_cod(&nl).unwrap_basic(), + name("Negative").into(), + ); + } + + migrated_model +} + +// TO-DO: add test ! From 9bc99ebd21d920a5c156649a8a78af0b15463e65 Mon Sep 17 00:00:00 2001 From: Tim Hosgood Date: Thu, 18 Dec 2025 11:10:52 +0000 Subject: [PATCH 2/6] 99% of a test --- .../analyses/signed_links_to_signed_cat.rs | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs index 2da18dd7d..4d9428f6c 100644 --- a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs +++ b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs @@ -57,14 +57,14 @@ pub fn migrate(model: DiscreteTabModel) -> DiscreteDblModel { format!("{}_in", f).as_str().into(), f.clone(), model.mor_generator_dom(&f).unwrap_basic(), - Path::Id(name("Object")), + name("Negative").into(), ); // A positive link from the flow object to the flow-target object migrated_model.add_mor( - format!("{}_in", f).as_str().into(), + format!("{}_out", f).as_str().into(), f.clone(), model.mor_generator_cod(&f).unwrap_basic(), - name("Negative").into(), + Path::Id(name("Object")), ); } @@ -91,3 +91,31 @@ pub fn migrate(model: DiscreteTabModel) -> DiscreteDblModel { } // TO-DO: add test ! +#[cfg(test)] +mod tests { + use super::*; + use crate::dbl::model::MutDblModel; + use crate::stdlib::{negative_backward_link}; + use std::rc::Rc; + + #[test] + fn negative_backward_link_to_cld() { + // Build the negative backwards link stock-flow diagram + let csl_th = Rc::new(theories::th_category_signed_links()); + let _sf_model = negative_backward_link(csl_th); + + // Manually construct the correct migration + let sc_th = Rc::new(theories::th_signed_category()); + let mut cld_model = DiscreteDblModel::new(sc_th); + cld_model.add_ob(name("x"), name("Object")); + cld_model.add_ob(name("y"), name("Object")); + cld_model.add_ob(name("f"), name("Object")); + cld_model.add_mor(name("f_in"), name("f"), name("x"), name("Negative").into()); + cld_model.add_mor(name("f_out"), name("f"), name("y"), Path::Id(name("Object"))); + cld_model.add_mor(name("link"), name("y"), name("f"), name("Negative").into()); + + // Test the putative migration against the correct one + // TO-DO: I don't think you can use assert_eq!() on models + // assert_eq!(migrate(sf_model), cld_model); + } +} From 96e0a7eebb51e612f60669b17fdef1f6c827948f Mon Sep 17 00:00:00 2001 From: Tim Hosgood Date: Thu, 18 Dec 2025 18:18:14 +0000 Subject: [PATCH 3/6] broken test --- .../src/stdlib/analyses/signed_links_to_signed_cat.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs index 4d9428f6c..1ec59ce4b 100644 --- a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs +++ b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs @@ -34,7 +34,7 @@ use crate::zero::name; * (vertex corresponding to the) source stock to the (vertex corresponding * to the) target flow */ -pub fn migrate(model: DiscreteTabModel) -> DiscreteDblModel { +pub fn span_migrate(model: DiscreteTabModel) -> DiscreteDblModel { let mut migrated_model: DiscreteDblModel = DiscreteDblModel::new(Rc::new(theories::th_signed_category())); @@ -102,7 +102,7 @@ mod tests { fn negative_backward_link_to_cld() { // Build the negative backwards link stock-flow diagram let csl_th = Rc::new(theories::th_category_signed_links()); - let _sf_model = negative_backward_link(csl_th); + let sf_model = negative_backward_link(csl_th); // Manually construct the correct migration let sc_th = Rc::new(theories::th_signed_category()); @@ -115,7 +115,7 @@ mod tests { cld_model.add_mor(name("link"), name("y"), name("f"), name("Negative").into()); // Test the putative migration against the correct one - // TO-DO: I don't think you can use assert_eq!() on models - // assert_eq!(migrate(sf_model), cld_model); + // TO-DO: I don't think you can use assert_eq!() on models? + assert_eq!(span_migrate(sf_model), cld_model); } } From 99b2aa2f5e070d40f5f608f69faa601e4ff2c7da Mon Sep 17 00:00:00 2001 From: Tim Hosgood Date: Thu, 18 Dec 2025 19:37:40 +0000 Subject: [PATCH 4/6] a test that now *runs*... but fails --- .../src/dbl/discrete_tabulator/model.rs | 15 +++ .../analyses/signed_links_to_signed_cat.rs | 91 +++++++++++-------- 2 files changed, 68 insertions(+), 38 deletions(-) diff --git a/packages/catlog/src/dbl/discrete_tabulator/model.rs b/packages/catlog/src/dbl/discrete_tabulator/model.rs index baa10da70..fe113dd39 100644 --- a/packages/catlog/src/dbl/discrete_tabulator/model.rs +++ b/packages/catlog/src/dbl/discrete_tabulator/model.rs @@ -74,6 +74,21 @@ pub enum TabEdge { }, } +impl TabEdge { + /// Extracts a basic object or nothing. + pub fn basic(self) -> Option { + match self { + TabEdge::Basic(id) => Some(id), + _ => None, + } + } + + /// Unwraps a basic object, or panics. + pub fn unwrap_basic(self) -> QualifiedName { + self.basic().expect("Morphism should be a basic Morphism") + } +} + /// Morphism in a model of a discrete tabulator theory. pub type TabMor = Path; diff --git a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs index 1ec59ce4b..8127a084e 100644 --- a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs +++ b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs @@ -1,8 +1,10 @@ //! Migration for going from categories with signed links to signed categories // (e.g. from signed stock-flow diagrams to causal loop diagrams) +// +// Note that this migration expects a DiscreteTabModel whose underlying theory +// is th_category_signed_links, otherwise it will panic -use crate::dbl::discrete_tabulator::theory::TabMorType; -use crate::dbl::discrete_tabulator::theory::TabObType; +use crate::dbl::discrete_tabulator::theory::{TabMorType, TabObType}; use std::rc::Rc; use crate::dbl::discrete::model::DiscreteDblModel; @@ -13,27 +15,26 @@ use crate::one::path::Path; use crate::stdlib::theories; use crate::zero::name; -/** Span-migration for categories with signed links. - * - * We create a CLD from a category with signed links from the query defined on - * objects as - * - * V |-> stock : S | flow : F - * E+ |-> out : F | link : L+ - * E- |-> in : F | link : L- - * - * and on morphisms as (...) something that will be written up eventually, but - * in short can be described quite simply in words: - * - * 1. For each stock, create a vertex - * 2. For each flow, create a (+,-)-span, where the apex is a new vertex - * corresponding to the flow, and there is a negative arrow to the (vertex - * corresponding to the) source of the flow, and a positive arrow to the - * (vertex corresponding to the) target of the flow - * 3. For each (signed) link, create an arrow (of the same sign) from the - * (vertex corresponding to the) source stock to the (vertex corresponding - * to the) target flow - */ +/// Span-migration for categories with signed links. +/// +/// We create a CLD from a category with signed links from the query defined on +/// objects as +/// +/// V |-> stock : S | flow : F +/// E+ |-> out : F | link : L+ +/// E- |-> in : F | link : L- +/// +/// and on morphisms as (...) something that will be written up eventually, but +/// in short can be described quite simply in words: +/// +/// 1. For each stock, create a vertex +/// 2. For each flow, create a (+,-)-span, where the apex is a new vertex +/// corresponding to the flow, and there is a negative arrow to the (vertex +/// corresponding to the) source of the flow, and a positive arrow to the +/// (vertex corresponding to the) target of the flow +/// 3. For each (signed) link, create an arrow (of the same sign) from the +/// (vertex corresponding to the) source stock to the (vertex corresponding +/// to the) target flow pub fn span_migrate(model: DiscreteTabModel) -> DiscreteDblModel { let mut migrated_model: DiscreteDblModel = DiscreteDblModel::new(Rc::new(theories::th_signed_category())); @@ -44,45 +45,61 @@ pub fn span_migrate(model: DiscreteTabModel) -> DiscreteDblModel { let neg_link_type = TabMorType::Basic(name("NegativeLink")); // Create an object for each stock (a "stock-object") - for s in model.ob_generators() { - migrated_model.add_ob(s.clone(), name("Object")); + for stock in model.ob_generators() { + migrated_model.add_ob(stock.clone(), name("Object")); } // Create a span for each flow - for f in model.mor_generators_with_type(&flow_type) { + for flow in model.mor_generators_with_type(&flow_type) { // An object for each flow (a "flow-object") - migrated_model.add_ob(f.clone(), name("Object")); + migrated_model.add_ob(flow.clone(), name("Object")); // A negative link from the flow object to the flow-source object migrated_model.add_mor( - format!("{}_in", f).as_str().into(), - f.clone(), - model.mor_generator_dom(&f).unwrap_basic(), + format!("{}_in", flow).as_str().into(), + flow.clone(), + model.mor_generator_dom(&flow).unwrap_basic(), name("Negative").into(), ); // A positive link from the flow object to the flow-target object migrated_model.add_mor( - format!("{}_out", f).as_str().into(), - f.clone(), - model.mor_generator_cod(&f).unwrap_basic(), + format!("{}_out", flow).as_str().into(), + flow.clone(), + model.mor_generator_cod(&flow).unwrap_basic(), Path::Id(name("Object")), ); } // Create a positive arrow for each positive link for pl in model.mor_generators_with_type(&pos_link_type) { + // We know, by design, that the codomain of a morphism generator of type + // pos_link_type will be of type TabOb::Tabulated and furthermore will + // contain (in its box) a path (TabMor) consisting of a *single* TabEdge + // which will *furthermore* itself be of type TabEdge::Basic + let pl_cod = model + .mor_generator_cod(&pl) + .unwrap_tabulated() + .only() + .expect("Morphism should be a singleton path of type TabEdge") + .unwrap_basic(); migrated_model.add_mor( pl.clone(), model.mor_generator_dom(&pl).unwrap_basic(), - model.mor_generator_cod(&pl).unwrap_basic(), + pl_cod, Path::Id(name("Object")), ); } // Create a negative arrow for each negative link for nl in model.mor_generators_with_type(&neg_link_type) { + let nl_cod = model + .mor_generator_cod(&nl) + .unwrap_tabulated() + .only() + .expect("Morphism should be a singleton path of type TabEdge") + .unwrap_basic(); migrated_model.add_mor( nl.clone(), model.mor_generator_dom(&nl).unwrap_basic(), - model.mor_generator_cod(&nl).unwrap_basic(), + nl_cod, name("Negative").into(), ); } @@ -90,12 +107,11 @@ pub fn span_migrate(model: DiscreteTabModel) -> DiscreteDblModel { migrated_model } -// TO-DO: add test ! #[cfg(test)] mod tests { use super::*; use crate::dbl::model::MutDblModel; - use crate::stdlib::{negative_backward_link}; + use crate::stdlib::negative_backward_link; use std::rc::Rc; #[test] @@ -115,7 +131,6 @@ mod tests { cld_model.add_mor(name("link"), name("y"), name("f"), name("Negative").into()); // Test the putative migration against the correct one - // TO-DO: I don't think you can use assert_eq!() on models? assert_eq!(span_migrate(sf_model), cld_model); } } From 370dcfe42caa03fae7bd0738fa9f14fe1d021780 Mon Sep 17 00:00:00 2001 From: Tim Hosgood Date: Thu, 18 Dec 2025 21:23:57 +0000 Subject: [PATCH 5/6] trying very hard to match CI cargo fmt --- .../catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs index 8127a084e..7b99f93c1 100644 --- a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs +++ b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs @@ -16,10 +16,10 @@ use crate::stdlib::theories; use crate::zero::name; /// Span-migration for categories with signed links. -/// +/// /// We create a CLD from a category with signed links from the query defined on /// objects as -/// +/// /// V |-> stock : S | flow : F /// E+ |-> out : F | link : L+ /// E- |-> in : F | link : L- From 45006a8896b9c5e6e3a4d61dcc61561ac3b4f01b Mon Sep 17 00:00:00 2001 From: Tim Hosgood Date: Fri, 19 Dec 2025 21:04:23 +0000 Subject: [PATCH 6/6] passing test --- .../src/stdlib/analyses/signed_links_to_signed_cat.rs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs index 7b99f93c1..0900d71ad 100644 --- a/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs +++ b/packages/catlog/src/stdlib/analyses/signed_links_to_signed_cat.rs @@ -112,6 +112,7 @@ mod tests { use super::*; use crate::dbl::model::MutDblModel; use crate::stdlib::negative_backward_link; + use crate::zero::QualifiedName; use std::rc::Rc; #[test] @@ -131,6 +132,13 @@ mod tests { cld_model.add_mor(name("link"), name("y"), name("f"), name("Negative").into()); // Test the putative migration against the correct one - assert_eq!(span_migrate(sf_model), cld_model); + // assert_eq!(span_migrate(sf_model), cld_model); + let migrated_model = span_migrate(sf_model); + let mig_obs: Vec = migrated_model.ob_generators().collect(); + let cld_obs: Vec = cld_model.ob_generators().collect(); + assert_eq!(mig_obs, cld_obs); + let mig_mors: Vec = migrated_model.mor_generators().collect(); + let cld_mors: Vec = cld_model.mor_generators().collect(); + assert_eq!(mig_mors, cld_mors); } }