diff --git a/packages/catlog-wasm/src/model.rs b/packages/catlog-wasm/src/model.rs index 7393986e6..4979822ed 100644 --- a/packages/catlog-wasm/src/model.rs +++ b/packages/catlog-wasm/src/model.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use tsify_next::Tsify; use wasm_bindgen::prelude::*; -use catlog::dbl::model::{self as dbl_model, FgDblModel, InvalidDiscreteDblModel}; +use catlog::dbl::model::{self as dbl_model, FgDblModel, InvalidDblModel}; use catlog::one::fin_category::UstrFinCategory; use catlog::one::{Category as _, FgCategory, Path}; use catlog::validate::Validate; @@ -198,7 +198,7 @@ impl DblModel { #[wasm_bindgen] pub fn objects(&self) -> Vec { all_the_same!(match &self.0 { - DblModelBox::[Discrete](model) => model.object_generators().map(|x| x.into()).collect() + DblModelBox::[Discrete](model) => model.objects().map(|x| x.into()).collect() }) } @@ -206,7 +206,7 @@ impl DblModel { #[wasm_bindgen] pub fn morphisms(&self) -> Vec { all_the_same!(match &self.0 { - DblModelBox::[Discrete](model) => model.morphism_generators().map(Mor::Basic).collect() + DblModelBox::[Discrete](model) => model.morphisms().map(|f| f.into()).collect() }) } @@ -216,7 +216,7 @@ impl DblModel { all_the_same!(match &self.0 { DblModelBox::[Discrete](model) => { let ob_type = ob_type.try_into()?; - Ok(model.object_generators_with_type(&ob_type).map(Ob::Basic).collect()) + Ok(model.objects_with_type(&ob_type).map(|ob| ob.into()).collect()) } }) } @@ -227,7 +227,7 @@ impl DblModel { all_the_same!(match &self.0 { DblModelBox::[Discrete](model) => { let mor_type = mor_type.try_into()?; - Ok(model.morphism_generators_with_type(&mor_type).map(Mor::Basic).collect()) + Ok(model.morphisms_with_type(&mor_type).map(|mor| mor.into()).collect()) } }) } @@ -247,7 +247,7 @@ impl DblModel { /// Result of validating a model of a double theory. #[derive(Serialize, Deserialize, Tsify)] #[tsify(into_wasm_abi, from_wasm_abi)] -pub struct ModelValidationResult(pub JsResult<(), Vec>>); +pub struct ModelValidationResult(pub JsResult<(), Vec>>); #[cfg(test)] pub(crate) mod tests { diff --git a/packages/catlog-wasm/src/model_diagram.rs b/packages/catlog-wasm/src/model_diagram.rs index e7300642a..33a8f160b 100644 --- a/packages/catlog-wasm/src/model_diagram.rs +++ b/packages/catlog-wasm/src/model_diagram.rs @@ -121,7 +121,7 @@ impl DblModelDiagram { all_the_same!(match &self.0 { DblModelDiagramBox::[Discrete](diagram) => { let (_, model) = diagram.into(); - model.object_generators().map(|x| x.into()).collect() + model.objects().map(|x| x.into()).collect() } }) } @@ -132,7 +132,7 @@ impl DblModelDiagram { all_the_same!(match &self.0 { DblModelDiagramBox::[Discrete](diagram) => { let (_, model) = diagram.into(); - model.morphism_generators().map(Mor::Basic).collect() + model.morphisms().map(|f| f.into()).collect() } }) } @@ -144,7 +144,7 @@ impl DblModelDiagram { DblModelDiagramBox::[Discrete](diagram) => { let (_, model) = diagram.into(); let ob_type = ob_type.try_into()?; - Ok(model.object_generators_with_type(&ob_type).map(Ob::Basic).collect()) + Ok(model.objects_with_type(&ob_type).map(|x| x.into()).collect()) } }) } @@ -156,7 +156,7 @@ impl DblModelDiagram { DblModelDiagramBox::[Discrete](diagram) => { let (_, model) = diagram.into(); let mor_type = mor_type.try_into()?; - Ok(model.morphism_generators_with_type(&mor_type).map(Mor::Basic).collect()) + Ok(model.morphisms_with_type(&mor_type).map(|f| f.into()).collect()) } }) } @@ -167,10 +167,10 @@ impl DblModelDiagram { all_the_same!(match &self.0 { DblModelDiagramBox::[Discrete](diagram) => { let (mapping, model) = diagram.into(); - let decls = model.object_generators().map(|x| { + let decls = model.ob_generators().map(|x| { DiagramObDecl { id: x, - ob_type: model.ob_gen_type(&x).into(), + ob_type: model.ob_generator_type(&x).into(), over: mapping.apply_ob(&x).map(|ob| ob.into()) } }); @@ -185,10 +185,10 @@ impl DblModelDiagram { all_the_same!(match &self.0 { DblModelDiagramBox::[Discrete](diagram) => { let (mapping, model) = diagram.into(); - let decls = model.morphism_generators().map(|f| { + let decls = model.mor_generators().map(|f| { DiagramMorDecl { id: f, - mor_type: model.mor_gen_type(&f).into(), + mor_type: model.mor_generator_type(&f).into(), over: mapping.apply_basic_mor(&f).map(|mor| mor.into()), dom: model.get_dom(&f).cloned().map(|ob| ob.into()), cod: model.get_cod(&f).cloned().map(|ob| ob.into()), diff --git a/packages/catlog-wasm/src/model_morphism.rs b/packages/catlog-wasm/src/model_morphism.rs index f1626893a..037c64306 100644 --- a/packages/catlog-wasm/src/model_morphism.rs +++ b/packages/catlog-wasm/src/model_morphism.rs @@ -28,7 +28,7 @@ where .collect(); // Order motifs from small to large. - images.sort_by_key(|im| (im.object_generators().count(), im.morphism_generators().count())); + images.sort_by_key(|im| (im.ob_generators().count(), im.mor_generators().count())); // Remove duplicates: different morphisms can have the same image. retain_unique(&mut images); diff --git a/packages/catlog/src/dbl/model.rs b/packages/catlog/src/dbl/model.rs index 2ea9b118f..b864d22ed 100644 --- a/packages/catlog/src/dbl/model.rs +++ b/packages/catlog/src/dbl/model.rs @@ -103,13 +103,13 @@ pub trait DblModel: Category { MorOp = Self::MorOp, >; - /// The underlying theory that this is a model of + /// The double theory that this model is a model of. fn theory(&self) -> &Self::Theory; - /// Type of object. + /// Type of an object. fn ob_type(&self, x: &Self::Ob) -> Self::ObType; - /// Type of morphism. + /// Type of a morphism. fn mor_type(&self, m: &Self::Mor) -> Self::MorType; /// Acts on an object with an object operation. @@ -119,28 +119,35 @@ pub trait DblModel: Category { fn mor_act(&self, m: Self::Mor, α: &Self::MorOp) -> Self::Mor; } -/// A finitely-generated double model +/// A finitely-generated model of a double theory. pub trait FgDblModel: DblModel + FgCategory { /// Type of an object generator. - fn ob_gen_type(&self, ob: &Self::ObGen) -> Self::ObType; + fn ob_generator_type(&self, ob: &Self::ObGen) -> Self::ObType; /// Type of a morphism generator. - fn mor_gen_type(&self, mor: &Self::MorGen) -> Self::MorType; + fn mor_generator_type(&self, mor: &Self::MorGen) -> Self::MorType; - /// Iterates over object generators in the model of a given object type. - fn object_generators_with_type( - &self, - obtype: &Self::ObType, - ) -> impl Iterator { - self.object_generators().filter(move |ob| self.ob_gen_type(ob) == *obtype) + /// Iterates over object generators with the given object type. + fn ob_generators_with_type(&self, obtype: &Self::ObType) -> impl Iterator { + self.ob_generators().filter(|ob| self.ob_generator_type(ob) == *obtype) } - /// Iterates over morphism generators in the model of a given morphism type. - fn morphism_generators_with_type( + /// Iterates over morphism generators with the given morphism type. + fn mor_generators_with_type( &self, mortype: &Self::MorType, ) -> impl Iterator { - self.morphism_generators().filter(move |mor| self.mor_gen_type(mor) == *mortype) + self.mor_generators().filter(|mor| self.mor_generator_type(mor) == *mortype) + } + + /// Iterators over basic objects with the given object type. + fn objects_with_type(&self, obtype: &Self::ObType) -> impl Iterator { + self.ob_generators_with_type(obtype).map(|ob_gen| ob_gen.into()) + } + + /// Iterates over basic morphisms with the given morphism type. + fn morphisms_with_type(&self, mortype: &Self::MorType) -> impl Iterator { + self.mor_generators_with_type(mortype).map(|mor_gen| mor_gen.into()) } } @@ -244,9 +251,9 @@ where self.category.set_cod(f, x) } - /// Iterates over failures to be well-defined model. - pub fn iter_invalid(&self) -> impl Iterator> + '_ { - type Invalid = InvalidDiscreteDblModel; + /// Iterates over failures of model to be well defined. + pub fn iter_invalid(&self) -> impl Iterator> + '_ { + type Invalid = InvalidDblModel; let category_errors = self.category.iter_invalid().map(|err| match err { InvalidFpCategory::Dom(e) => Invalid::Dom(e), InvalidFpCategory::Cod(e) => Invalid::Cod(e), @@ -255,16 +262,16 @@ where InvalidFpCategory::EqSrc(eq) => Invalid::EqSrc(eq), InvalidFpCategory::EqTgt(eq) => Invalid::EqTgt(eq), }); - let ob_type_errors = self.category.object_generators().filter_map(|x| { + let ob_type_errors = self.category.ob_generators().filter_map(|x| { if self.theory.has_ob_type(&self.ob_type(&x)) { None } else { Some(Invalid::ObType(x)) } }); - let mor_type_errors = self.category.morphism_generators().flat_map(|e| { + let mor_type_errors = self.category.mor_generators().flat_map(|e| { let mut errs = Vec::new(); - let mor_type = self.mor_gen_type(&e); + let mor_type = self.mor_generator_type(&e); if self.theory.has_mor_type(&mor_type) { if self.category.get_dom(&e).map_or(false, |x| { self.has_ob(x) && self.ob_type(x) != self.theory.src(&mor_type) @@ -292,14 +299,14 @@ where the model even after calling this method. */ pub fn infer_missing(&mut self) { - let edges: Vec<_> = self.morphism_generators().collect(); + let edges: Vec<_> = self.mor_generators().collect(); for e in edges { if let Some(x) = self.get_dom(&e).filter(|x| !self.has_ob(x)) { - let ob_type = self.theory.src(&self.mor_gen_type(&e)); + let ob_type = self.theory.src(&self.mor_generator_type(&e)); self.add_ob(x.clone(), ob_type); } if let Some(x) = self.get_cod(&e).filter(|x| !self.has_ob(x)) { - let ob_type = self.theory.tgt(&self.mor_gen_type(&e)); + let ob_type = self.theory.tgt(&self.mor_generator_type(&e)); self.add_ob(x.clone(), ob_type); } } @@ -343,20 +350,20 @@ where type ObGen = Id; type MorGen = Id; - fn object_generators(&self) -> impl Iterator { - self.category.object_generators() + fn ob_generators(&self) -> impl Iterator { + self.category.ob_generators() } - fn morphism_generators(&self) -> impl Iterator { - self.category.morphism_generators() + fn mor_generators(&self) -> impl Iterator { + self.category.mor_generators() } - fn morphism_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { - self.category.morphism_generator_dom(f) + fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { + self.category.mor_generator_dom(f) } - fn morphism_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { - self.category.morphism_generator_cod(f) + fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { + self.category.mor_generator_cod(f) } } @@ -385,10 +392,11 @@ where } fn ob_type(&self, ob: &Self::Ob) -> Self::ObType { - self.ob_gen_type(ob) + self.ob_generator_type(ob) } fn mor_type(&self, mor: &Self::Mor) -> Self::MorType { - let types = mor.clone().map(|x| self.ob_gen_type(&x), |m| self.mor_gen_type(&m)); + let types = + mor.clone().map(|x| self.ob_generator_type(&x), |m| self.mor_generator_type(&m)); self.theory.compose_types(types) } } @@ -400,20 +408,17 @@ where Cat::Ob: Hash, Cat::Mor: Hash, { - fn ob_gen_type(&self, ob: &Self::ObGen) -> Self::ObType { + fn ob_generator_type(&self, ob: &Self::ObGen) -> Self::ObType { self.ob_types.apply(ob).cloned().expect("Object should have type") } - fn mor_gen_type(&self, mor: &Self::MorGen) -> Self::MorType { + fn mor_generator_type(&self, mor: &Self::MorGen) -> Self::MorType { self.mor_types.apply(mor).cloned().expect("Morphism should have type") } - fn object_generators_with_type(&self, typ: &Self::ObType) -> impl Iterator { + fn ob_generators_with_type(&self, typ: &Self::ObType) -> impl Iterator { self.ob_types.preimage(typ) } - fn morphism_generators_with_type( - &self, - typ: &Self::MorType, - ) -> impl Iterator { + fn mor_generators_with_type(&self, typ: &Self::MorType) -> impl Iterator { self.mor_types.preimage(typ) } } @@ -425,24 +430,27 @@ where Cat::Ob: Hash, Cat::Mor: Hash, { - type ValidationError = InvalidDiscreteDblModel; + type ValidationError = InvalidDblModel; fn validate(&self) -> Result<(), nonempty::NonEmpty> { validate::wrap_errors(self.iter_invalid()) } } -/** A failure of a model of a discrete double theory to be well defined. +/** A failure of a model of a double theory to be well defined. -TODO: Missing case that equation has different composite morphism types on left -and right hand sides. +We currently are encompassing error variants for all kinds of double theories in +a single enum. That will likely become unviable but it's convenient for now. + +TODO: We are missing the case that an equation has different composite morphism +types on left and right hand sides. */ #[derive(Clone, Debug, PartialEq, Eq)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "serde", serde(tag = "tag", content = "content"))] #[cfg_attr(feature = "serde-wasm", derive(Tsify))] #[cfg_attr(feature = "serde-wasm", tsify(into_wasm_abi, from_wasm_abi))] -pub enum InvalidDiscreteDblModel { +pub enum InvalidDblModel { /// Domain of basic morphism is undefined or invalid. Dom(Id), @@ -558,7 +566,11 @@ where fn has_edge(&self, edge: &Self::E) -> bool { match edge { - TabEdge::Basic(e) => self.morphisms.contains(e), + TabEdge::Basic(e) => { + self.morphisms.contains(e) + && self.dom.apply(e).map_or(false, |x| self.has_vertex(x)) + && self.cod.apply(e).map_or(false, |x| self.has_vertex(x)) + } TabEdge::Square { dom, cod, @@ -662,9 +674,46 @@ where self.generators.cod.set(f.clone(), cod); self.generators.morphisms.insert(f) } + + /// Iterates over failures of model to be well defined. + pub fn iter_invalid(&self) -> impl Iterator> + '_ { + type Invalid = InvalidDblModel; + let ob_errors = self.generators.objects.iter().filter_map(|x| { + if self.ob_types.apply(&x).map_or(false, |typ| self.theory.has_ob_type(typ)) { + None + } else { + Some(Invalid::ObType(x)) + } + }); + let mor_errors = self.generators.morphisms.iter().flat_map(|e| { + let mut errs = Vec::new(); + let dom = self.generators.dom.apply(&e).filter(|x| self.has_ob(x)); + let cod = self.generators.cod.apply(&e).filter(|x| self.has_ob(x)); + if dom.is_none() { + errs.push(Invalid::Dom(e.clone())); + } + if cod.is_none() { + errs.push(Invalid::Cod(e.clone())); + } + if let Some(mor_type) = + self.mor_types.apply(&e).filter(|typ| self.theory.has_mor_type(typ)) + { + if dom.map_or(false, |x| self.ob_type(x) != self.theory.src(mor_type)) { + errs.push(Invalid::DomType(e.clone())); + } + if cod.map_or(false, |x| self.ob_type(x) != self.theory.tgt(mor_type)) { + errs.push(Invalid::CodType(e.clone())); + } + } else { + errs.push(Invalid::MorType(e)); + } + errs.into_iter() + }); + ob_errors.chain(mor_errors) + } } -impl Category for DiscreteTabModel +impl Category for DiscreteTabModel where Id: Eq + Clone + Hash, { @@ -689,38 +738,39 @@ where } } -impl FgCategory for DiscreteTabModel +impl FgCategory for DiscreteTabModel where Id: Eq + Clone + Hash, { type ObGen = Id; type MorGen = Id; - fn object_generators(&self) -> impl Iterator { + fn ob_generators(&self) -> impl Iterator { self.generators.objects.iter() } - fn morphism_generators(&self) -> impl Iterator { + fn mor_generators(&self) -> impl Iterator { self.generators.morphisms.iter() } - fn morphism_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { self.generators.dom.apply(f).cloned().expect("Domain should be defined") } - fn morphism_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { self.generators.cod.apply(f).cloned().expect("Codomain should be defined") } } -impl DblModel for DiscreteTabModel +impl DblModel for DiscreteTabModel where Id: Eq + Clone + Hash, ThId: Eq + Clone + Hash, + S: BuildHasher, { type ObType = TabObType; type MorType = TabMorType; type ObOp = TabObOp; type MorOp = TabMorOp; - type Theory = DiscreteTabTheory; + type Theory = DiscreteTabTheory; fn theory(&self) -> &Self::Theory { &self.theory @@ -728,7 +778,7 @@ where fn ob_type(&self, ob: &Self::Ob) -> Self::ObType { match ob { - TabOb::Basic(x) => self.ob_gen_type(x), + TabOb::Basic(x) => self.ob_generator_type(x), TabOb::Tabulated(m) => TabObType::Tabulator(Box::new(self.mor_type(m))), } } @@ -737,7 +787,7 @@ where let types = mor.clone().map( |x| self.ob_type(&x), |edge| match edge { - TabEdge::Basic(f) => self.mor_gen_type(&f), + TabEdge::Basic(f) => self.mor_generator_type(&f), TabEdge::Square { dom, .. } => { let typ = self.mor_type(&dom); // == self.mor_type(&cod) TabMorType::Hom(Box::new(TabObType::Tabulator(Box::new(typ)))) @@ -765,21 +815,46 @@ where } } -impl FgDblModel for DiscreteTabModel +impl FgDblModel for DiscreteTabModel where Id: Eq + Clone + Hash, ThId: Eq + Clone + Hash, + S: BuildHasher, { - fn ob_gen_type(&self, ob: &Self::ObGen) -> Self::ObType { + fn ob_generator_type(&self, ob: &Self::ObGen) -> Self::ObType { self.ob_types.apply(ob).cloned().expect("Object should have type") } - fn mor_gen_type(&self, mor: &Self::MorGen) -> Self::MorType { + fn mor_generator_type(&self, mor: &Self::MorGen) -> Self::MorType { self.mor_types.apply(mor).cloned().expect("Morphism should have type") } + + fn ob_generators_with_type(&self, obtype: &Self::ObType) -> impl Iterator { + self.ob_types.preimage(obtype) + } + fn mor_generators_with_type( + &self, + mortype: &Self::MorType, + ) -> impl Iterator { + self.mor_types.preimage(mortype) + } +} + +impl Validate for DiscreteTabModel +where + Id: Eq + Clone + Hash, + ThId: Eq + Clone + Hash, + S: BuildHasher, +{ + type ValidationError = InvalidDblModel; + + fn validate(&self) -> Result<(), nonempty::NonEmpty> { + validate::wrap_errors(self.iter_invalid()) + } } #[cfg(test)] mod tests { + use nonempty::nonempty; use ustr::ustr; use super::*; @@ -792,12 +867,12 @@ mod tests { let mut model = DiscreteDblModel::new(th.clone()); let entity = ustr("entity"); model.add_ob(entity, ustr("NotObType")); - assert_eq!(model.validate().unwrap_err().len(), 1); + assert_eq!(model.validate(), Err(nonempty![InvalidDblModel::ObType(entity)])); let mut model = DiscreteDblModel::new(th.clone()); model.add_ob(entity, ustr("Entity")); model.add_mor(ustr("map"), entity, entity, FinMor::Generator(ustr("NotMorType"))); - assert_eq!(model.validate().unwrap_err().len(), 1); + assert_eq!(model.validate(), Err(nonempty![InvalidDblModel::MorType(ustr("map"))])); let mut model = DiscreteDblModel::new(th); model.add_ob(entity, ustr("Entity")); @@ -805,12 +880,7 @@ mod tests { model.add_mor(ustr("a"), entity, ustr("type"), FinMor::Generator(ustr("Attr"))); assert!(model.validate().is_ok()); model.add_mor(ustr("b"), entity, ustr("type"), FinMor::Id(ustr("Entity"))); - assert_eq!(model.validate().unwrap_err().len(), 1); - - assert!(model.is_free()); - let peq = PathEq::new(Path::single(ustr("a")), Path::single(ustr("b"))); - model.add_equation(ustr("e"), peq); - assert!(!model.is_free()); + assert_eq!(model.validate(), Err(nonempty![InvalidDblModel::CodType(ustr("b"))])); } #[test] @@ -821,4 +891,14 @@ mod tests { model.infer_missing(); assert_eq!(model, walking_attr(th)); } + + #[test] + fn validate_discrete_tab_model() { + let th = Arc::new(th_category_links()); + let mut model = DiscreteTabModel::new(th); + let (x, f) = (ustr("x"), ustr("f")); + model.add_ob(x, TabObType::Basic(ustr("Object"))); + model.add_mor(f, TabOb::Basic(x), TabOb::Basic(x), TabMorType::Basic(ustr("Link"))); + assert_eq!(model.validate(), Err(nonempty![InvalidDblModel::CodType(f)])); + } } diff --git a/packages/catlog/src/dbl/model_diagram.rs b/packages/catlog/src/dbl/model_diagram.rs index ca26815d7..ace643d58 100644 --- a/packages/catlog/src/dbl/model_diagram.rs +++ b/packages/catlog/src/dbl/model_diagram.rs @@ -24,7 +24,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "serde-wasm")] use tsify_next::{declare, Tsify}; -use super::model::{DiscreteDblModel, InvalidDiscreteDblModel}; +use super::model::{DiscreteDblModel, InvalidDblModel}; use super::model_morphism::*; use crate::one::{Category, FgCategory}; use crate::validate; @@ -74,7 +74,7 @@ pub type DiscreteDblModelDiagram = /// A failure to be valid in a diagram in a model of a discrete double theory. #[cfg_attr(feature = "serde-wasm", declare)] pub type InvalidDiscreteDblModelDiagram = - InvalidDblModelDiagram, InvalidDblModelMorphism>; + InvalidDblModelDiagram, InvalidDblModelMorphism>; impl DiscreteDblModelDiagram where @@ -116,7 +116,7 @@ where pub fn infer_missing_from(&mut self, model: &DiscreteDblModel) { let (mapping, domain) = self.into(); domain.infer_missing(); - for e in domain.morphism_generators() { + for e in domain.mor_generators() { let Some(g) = mapping.apply_basic_mor(&e) else { continue; }; diff --git a/packages/catlog/src/dbl/model_morphism.rs b/packages/catlog/src/dbl/model_morphism.rs index efe077c19..3a87ccc41 100644 --- a/packages/catlog/src/dbl/model_morphism.rs +++ b/packages/catlog/src/dbl/model_morphism.rs @@ -242,7 +242,7 @@ where // Equations in the domain induce equations to check in the codomain. assert!(dom.is_free(), "Domain model should be free"); - let ob_errors = dom.object_generators().filter_map(|v| { + let ob_errors = dom.ob_generators().filter_map(|v| { if let Some(f_v) = mapping.apply_ob(&v) { if !cod.has_ob(&f_v) { Some(InvalidDblModelMorphism::Ob(v)) @@ -256,14 +256,14 @@ where } }); - let mor_errors = dom.morphism_generators().flat_map(|f| { + let mor_errors = dom.mor_generators().flat_map(|f| { if let Some(f_f) = mapping.apply_basic_mor(&f) { if !cod.has_mor(&f_f) { [InvalidDblModelMorphism::Mor(f)].to_vec() } else { - let dom_f = mapping.apply_ob(&dom.morphism_generator_dom(&f)); - let cod_f = mapping.apply_ob(&dom.morphism_generator_cod(&f)); - let f_type = dom.mor_gen_type(&f); + let dom_f = mapping.apply_ob(&dom.mor_generator_dom(&f)); + let cod_f = mapping.apply_ob(&dom.mor_generator_cod(&f)); + let f_type = dom.mor_generator_type(&f); let ff_type = cod.mor_type(&f_f); let mut errs = vec![]; @@ -289,7 +289,7 @@ where /// codomain? fn is_simple(&self) -> bool { let DblModelMorphism(mapping, dom, _) = *self; - dom.morphism_generators() + dom.mor_generators() .all(|e| mapping.apply_basic_mor(&e).map(|p| p.is_simple()).unwrap_or(true)) } @@ -297,7 +297,7 @@ where pub fn is_injective_objects(&self) -> bool { let DblModelMorphism(mapping, dom, _) = *self; let mut seen_obs: HashSet<_> = HashSet::new(); - for x in dom.object_generators() { + for x in dom.ob_generators() { if let Some(f_x) = mapping.apply_ob(&x) { if seen_obs.contains(&f_x) { return false; // not monic @@ -324,8 +324,8 @@ where assert!(cod.is_free(), "Codomain model should be free"); assert!(&self.is_simple(), "Morphism assignments should be simple"); - for x in dom.object_generators() { - for y in dom.object_generators() { + for x in dom.ob_generators() { + for y in dom.ob_generators() { let mut seen: HashSet<_> = HashSet::new(); for path in simple_paths(dom.generating_graph(), &x, &y) { if let Some(f_path) = mapping.apply_mor(&path) { @@ -537,7 +537,7 @@ where self.unassign_ob(x, y) } } else { - for y in self.cod.object_generators_with_type(&self.dom.ob_type(&x)) { + for y in self.cod.ob_generators_with_type(&self.dom.ob_type(&x)) { let can_assign = self.assign_ob(x.clone(), y.clone()); if can_assign { self.search(depth + 1); @@ -627,7 +627,7 @@ mod tests { fn find_positive_loops() { let th = Arc::new(th_signed_category()); let positive_loop = positive_loop(th.clone()); - let pos = positive_loop.morphism_generators().next().unwrap().into(); + let pos = positive_loop.mor_generators().next().unwrap().into(); let maps = DiscreteDblModelMapping::morphisms(&positive_loop, &positive_loop).find_all(); assert_eq!(maps.len(), 2); @@ -670,8 +670,8 @@ mod tests { model.add_mor(ustr("xz"), x, z, FinMor::Id(ustr("Object"))); model.add_mor(ustr("xx"), x, x, FinMor::Id(ustr("Object"))); - for i in model.object_generators() { - for j in model.object_generators() { + for i in model.ob_generators() { + for j in model.ob_generators() { let maps: HashSet<_> = DiscreteDblModelMapping::morphisms(&walking, &model) .initialize_ob(ustr("A"), i) .initialize_ob(ustr("B"), j) @@ -689,7 +689,7 @@ mod tests { fn find_negative_loops() { let th = Arc::new(th_signed_category()); let negative_loop = negative_loop(th.clone()); - let base_pt = negative_loop.object_generators().next().unwrap(); + let base_pt = negative_loop.ob_generators().next().unwrap(); let negative_feedback = negative_feedback(th); let maps = diff --git a/packages/catlog/src/one/category.rs b/packages/catlog/src/one/category.rs index 90e7dc9da..20a334bfe 100644 --- a/packages/catlog/src/one/category.rs +++ b/packages/catlog/src/one/category.rs @@ -172,28 +172,45 @@ impl Category for FreeCategory { /** A finitely generated category with specified object and morphism generators. -Such a category has finitely many objects, which usually coincide with the -object generators (unless there are nontrivial equations between objects), but -can have infinitely many morphisms. +Unless the category has extra structure, a finitely generated (f.g.) category +has finitely many objects, which coincide with the object generators so long as +there are no equations between objects. On the other hand, a f.g. category can +have infinitely many morphisms and often does. */ pub trait FgCategory: Category { - /// The type of object generators. + /** Type of an object generator. + + In simple cases, `Ob = ObGen`. + */ type ObGen: Eq + Clone + Into; - /// The type of morphism generators. Often Mor = Path. + /** Type of a morphism generator + + Often `Mor = Path`. + */ type MorGen: Eq + Clone + Into; - /// An iterator over object generators. - fn object_generators(&self) -> impl Iterator; + /// Iterates over object generators. + fn ob_generators(&self) -> impl Iterator; - /// An iterator over morphism generators. - fn morphism_generators(&self) -> impl Iterator; + /// Iterates over morphism generators. + fn mor_generators(&self) -> impl Iterator; /// The domain of a morphism generator - fn morphism_generator_dom(&self, f: &Self::MorGen) -> Self::Ob; + fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob; /// The codomain of a morphism generator - fn morphism_generator_cod(&self, f: &Self::MorGen) -> Self::Ob; + fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob; + + /// Iterates over basic objects. + fn objects(&self) -> impl Iterator { + self.ob_generators().map(|ob_gen| ob_gen.into()) + } + + /// Iterates over basic morphisms. + fn morphisms(&self) -> impl Iterator { + self.mor_generators().map(|mor_gen| mor_gen.into()) + } } impl Graph for DiscreteCategory { @@ -251,19 +268,19 @@ impl FgCategory for DiscreteCategory { type ObGen = S::Elem; type MorGen = S::Elem; - fn object_generators(&self) -> impl Iterator { + fn ob_generators(&self) -> impl Iterator { self.0.iter() } - fn morphism_generators(&self) -> impl Iterator { + fn mor_generators(&self) -> impl Iterator { self.0.iter() } - fn morphism_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { f.clone() } - fn morphism_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { f.clone() } } @@ -272,19 +289,19 @@ impl FgCategory for FreeCategory { type ObGen = G::V; type MorGen = G::E; - fn object_generators(&self) -> impl Iterator { + fn ob_generators(&self) -> impl Iterator { self.0.vertices() } - fn morphism_generators(&self) -> impl Iterator { + fn mor_generators(&self) -> impl Iterator { self.0.edges() } - fn morphism_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { self.0.src(f) } - fn morphism_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { self.0.tgt(f) } } @@ -312,8 +329,8 @@ mod tests { fn free_category() { let cat = FreeCategory::from(SkelGraph::triangle()); assert!(cat.has_ob(&2)); - assert_eq!(cat.object_generators().count(), 3); - assert_eq!(cat.morphism_generators().count(), 3); + assert_eq!(cat.ob_generators().count(), 3); + assert_eq!(cat.mor_generators().count(), 3); let id = Path::Id(1); assert!(cat.has_mor(&id)); diff --git a/packages/catlog/src/one/fin_category.rs b/packages/catlog/src/one/fin_category.rs index 8243324be..5df7d9993 100644 --- a/packages/catlog/src/one/fin_category.rs +++ b/packages/catlog/src/one/fin_category.rs @@ -192,19 +192,19 @@ where type ObGen = V; type MorGen = E; - fn object_generators(&self) -> impl Iterator { + fn ob_generators(&self) -> impl Iterator { self.generators.vertices() } - fn morphism_generators(&self) -> impl Iterator { + fn mor_generators(&self) -> impl Iterator { self.generators.edges() } - fn morphism_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { self.generators.src(f) } - fn morphism_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { self.generators.tgt(f) } } @@ -406,19 +406,19 @@ where type ObGen = V; type MorGen = E; - fn object_generators(&self) -> impl Iterator { + fn ob_generators(&self) -> impl Iterator { self.generators.vertices() } - fn morphism_generators(&self) -> impl Iterator { + fn mor_generators(&self) -> impl Iterator { self.generators.edges() } - fn morphism_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_dom(&self, f: &Self::MorGen) -> Self::Ob { self.generators.src(f) } - fn morphism_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { + fn mor_generator_cod(&self, f: &Self::MorGen) -> Self::Ob { self.generators.tgt(f) } } @@ -465,8 +465,8 @@ mod tests { sch_sgraph.add_mor_generator('s', 'E', 'V'); sch_sgraph.add_mor_generator('t', 'E', 'V'); sch_sgraph.add_mor_generator('i', 'E', 'E'); - assert_eq!(sch_sgraph.object_generators().count(), 2); - assert_eq!(sch_sgraph.morphism_generators().count(), 3); + assert_eq!(sch_sgraph.ob_generators().count(), 2); + assert_eq!(sch_sgraph.mor_generators().count(), 3); assert_eq!(sch_sgraph.dom(&Mor::Generator('t')), 'E'); assert_eq!(sch_sgraph.cod(&Mor::Generator('t')), 'V'); assert_eq!(sch_sgraph.validate().unwrap_err().len(), 3); @@ -494,8 +494,8 @@ mod tests { sch_sgraph.add_mor_generator('t', 'E', 'V'); sch_sgraph.add_mor_generator('i', 'E', 'E'); assert!(sch_sgraph.is_free()); - assert_eq!(sch_sgraph.object_generators().count(), 2); - assert_eq!(sch_sgraph.morphism_generators().count(), 3); + assert_eq!(sch_sgraph.ob_generators().count(), 2); + assert_eq!(sch_sgraph.mor_generators().count(), 3); assert_eq!(sch_sgraph.dom(&Path::single('t')), 'E'); assert_eq!(sch_sgraph.cod(&Path::single('t')), 'V'); assert!(sch_sgraph.validate().is_ok()); diff --git a/packages/catlog/src/stdlib/analyses/ode/lotka_volterra.rs b/packages/catlog/src/stdlib/analyses/ode/lotka_volterra.rs index 1b6e9872c..743bd7f20 100644 --- a/packages/catlog/src/stdlib/analyses/ode/lotka_volterra.rs +++ b/packages/catlog/src/stdlib/analyses/ode/lotka_volterra.rs @@ -96,7 +96,7 @@ impl LotkaVolterraAnalysis { where Id: Eq + Clone + Hash + Ord, { - let mut objects: Vec<_> = model.object_generators_with_type(&self.var_ob_type).collect(); + let mut objects: Vec<_> = model.ob_generators_with_type(&self.var_ob_type).collect(); objects.sort(); let ob_index: HashMap<_, _> = objects.iter().enumerate().map(|(i, x)| (x.clone(), i)).collect(); @@ -105,16 +105,16 @@ impl LotkaVolterraAnalysis { let mut A = DMatrix::from_element(n, n, 0.0f32); for mor_type in self.positive_mor_types.iter() { - for mor in model.morphism_generators_with_type(mor_type) { - let i = *ob_index.get(&model.morphism_generator_dom(&mor)).unwrap(); - let j = *ob_index.get(&model.morphism_generator_cod(&mor)).unwrap(); + for mor in model.mor_generators_with_type(mor_type) { + let i = *ob_index.get(&model.mor_generator_dom(&mor)).unwrap(); + let j = *ob_index.get(&model.mor_generator_cod(&mor)).unwrap(); A[(j, i)] += data.interaction_coeffs.get(&mor).copied().unwrap_or_default(); } } for mor_type in self.negative_mor_types.iter() { - for mor in model.morphism_generators_with_type(mor_type) { - let i = *ob_index.get(&model.morphism_generator_dom(&mor)).unwrap(); - let j = *ob_index.get(&model.morphism_generator_cod(&mor)).unwrap(); + for mor in model.mor_generators_with_type(mor_type) { + let i = *ob_index.get(&model.mor_generator_dom(&mor)).unwrap(); + let j = *ob_index.get(&model.mor_generator_cod(&mor)).unwrap(); A[(j, i)] -= data.interaction_coeffs.get(&mor).copied().unwrap_or_default(); } } diff --git a/packages/catlog/src/stdlib/analyses/ode/stock_flow.rs b/packages/catlog/src/stdlib/analyses/ode/stock_flow.rs index f26b49111..7199b6669 100644 --- a/packages/catlog/src/stdlib/analyses/ode/stock_flow.rs +++ b/packages/catlog/src/stdlib/analyses/ode/stock_flow.rs @@ -96,7 +96,7 @@ impl StockFlowODEAnalysis { ) -> Result, Vec> { let mut graph: SkelGraph = Default::default(); - let mut stocks = model.object_generators_with_type(&self.stock_object).collect::>(); + let mut stocks = model.ob_generators_with_type(&self.stock_object).collect::>(); stocks.sort(); let vertex_lookup = stocks .iter() @@ -105,13 +105,12 @@ impl StockFlowODEAnalysis { .collect::>(); graph.add_vertices(stocks.len()); - let mut flows = - model.morphism_generators_with_type(&self.flow_morphism).collect::>(); + let mut flows = model.mor_generators_with_type(&self.flow_morphism).collect::>(); flows.sort(); for flow in flows.iter() { graph.add_edge( - *vertex_lookup.get(&model.morphism_generator_dom(flow)).unwrap(), - *vertex_lookup.get(&model.morphism_generator_cod(flow)).unwrap(), + *vertex_lookup.get(&model.mor_generator_dom(flow)).unwrap(), + *vertex_lookup.get(&model.mor_generator_cod(flow)).unwrap(), ); } diff --git a/packages/catlog/src/stdlib/models.rs b/packages/catlog/src/stdlib/models.rs index 0ca240c2c..5b9411ad3 100644 --- a/packages/catlog/src/stdlib/models.rs +++ b/packages/catlog/src/stdlib/models.rs @@ -122,7 +122,6 @@ mod tests { #[test] fn categories_with_links() { let th = Arc::new(th_category_links()); - // TODO: Implement validation for models of tabulator theories. - backward_link(th); + assert!(backward_link(th).validate().is_ok()); } }