1use std::collections::HashSet;
22use std::hash::Hash;
23use std::sync::Arc;
24
25use derivative::Derivative;
26use nonempty::NonEmpty;
27use thiserror::Error;
28
29#[cfg(feature = "serde")]
30use serde::{Deserialize, Serialize};
31#[cfg(feature = "serde-wasm")]
32use tsify_next::Tsify;
33
34use crate::one::graph_algorithms::{bounded_simple_paths, simple_paths, spec_order};
35use crate::one::*;
36use crate::validate::{self, Validate};
37use crate::zero::{Column, HashColumn, Mapping, MutMapping};
38
39use super::model::*;
40
41pub trait DblModelMapping {
48 type DomOb: Eq + Clone;
50
51 type DomMor: Eq + Clone;
53
54 type CodOb: Eq + Clone;
56
57 type CodMor: Eq + Clone;
59
60 fn apply_ob(&self, x: &Self::DomOb) -> Option<Self::CodOb>;
62
63 fn apply_mor(&self, m: &Self::DomMor) -> Option<Self::CodMor>;
65
66 fn is_ob_assigned(&self, x: &Self::DomOb) -> bool {
68 self.apply_ob(x).is_some()
69 }
70
71 fn is_mor_assigned(&self, m: &Self::DomMor) -> bool {
73 self.apply_mor(m).is_some()
74 }
75}
76
77#[derive(Clone, Debug, Derivative)]
83#[derivative(Default(bound = ""))]
84#[derivative(PartialEq(bound = "DomId: Eq + Hash, CodId: PartialEq"))]
85pub struct DiscreteDblModelMapping<DomId, CodId> {
86 ob_map: HashColumn<DomId, CodId>,
87 mor_map: HashColumn<DomId, Path<CodId, CodId>>,
88}
89
90impl<DomId, CodId> DiscreteDblModelMapping<DomId, CodId>
91where
92 DomId: Clone + Eq + Hash,
93 CodId: Clone + Eq + Hash,
94{
95 pub fn apply_basic_mor(&self, e: &DomId) -> Option<Path<CodId, CodId>> {
97 self.mor_map.apply(e)
98 }
99
100 pub fn is_basic_mor_assigned(&self, e: &DomId) -> bool {
102 self.mor_map.is_set(e)
103 }
104
105 pub fn assign_ob(&mut self, x: DomId, y: CodId) -> Option<CodId> {
107 self.ob_map.set(x, y)
108 }
109
110 pub fn assign_basic_mor(
112 &mut self,
113 e: DomId,
114 n: Path<CodId, CodId>,
115 ) -> Option<Path<CodId, CodId>> {
116 self.mor_map.set(e, n)
117 }
118
119 pub fn unassign_ob(&mut self, x: &DomId) -> Option<CodId> {
121 self.ob_map.unset(x)
122 }
123
124 pub fn unassign_basic_mor(&mut self, e: &DomId) -> Option<Path<CodId, CodId>> {
126 self.mor_map.unset(e)
127 }
128
129 pub fn syntactic_image<Cat>(
140 &self,
141 cod: &DiscreteDblModel<CodId, Cat>,
142 ) -> DiscreteDblModel<CodId, Cat>
143 where
144 Cat: FgCategory,
145 Cat::Ob: Hash,
146 Cat::Mor: Hash,
147 {
148 assert!(cod.is_free(), "Codomain model should be free");
151
152 let mut im = DiscreteDblModel::new(cod.theory_arc());
153 for x in self.ob_map.values() {
154 im.add_ob(x.clone(), cod.ob_type(x));
155 }
156 for path in self.mor_map.values() {
157 for e in path.iter() {
158 let (x, y) = (cod.mor_generator_dom(e), cod.mor_generator_cod(e));
159 im.add_ob(x.clone(), cod.ob_type(&x));
160 im.add_ob(y.clone(), cod.ob_type(&y));
161 im.add_mor(e.clone(), x, y, cod.mor_generator_type(e));
162 }
163 }
164 im
165 }
166
167 pub fn morphisms<'a, Cat>(
169 dom: &'a DiscreteDblModel<DomId, Cat>,
170 cod: &'a DiscreteDblModel<CodId, Cat>,
171 ) -> DiscreteDblModelMorphismFinder<'a, DomId, CodId, Cat>
172 where
173 Cat: FgCategory,
174 Cat::Ob: Hash,
175 Cat::Mor: Hash,
176 {
177 DiscreteDblModelMorphismFinder::new(dom, cod)
178 }
179}
180
181impl<DomId, CodId> DblModelMapping for DiscreteDblModelMapping<DomId, CodId>
182where
183 DomId: Clone + Eq + Hash,
184 CodId: Clone + Eq + Hash,
185{
186 type DomOb = DomId;
187 type DomMor = Path<DomId, DomId>;
188 type CodOb = CodId;
189 type CodMor = Path<CodId, CodId>;
190
191 fn apply_ob(&self, x: &Self::DomOb) -> Option<Self::CodOb> {
192 self.ob_map.apply(x)
193 }
194
195 fn apply_mor(&self, m: &Self::DomMor) -> Option<Self::CodMor> {
196 m.clone()
197 .partial_map(|x| self.apply_ob(&x), |e| self.apply_basic_mor(&e))
198 .map(|path| path.flatten())
199 }
200
201 fn is_ob_assigned(&self, x: &Self::DomOb) -> bool {
202 self.ob_map.is_set(x)
203 }
204
205 fn is_mor_assigned(&self, m: &Self::DomMor) -> bool {
206 match m {
207 Path::Id(x) => self.is_ob_assigned(x),
208 Path::Seq(edges) => edges.iter().all(|e| self.is_basic_mor_assigned(e)),
209 }
210 }
211}
212
213pub struct DblModelMorphism<'a, Map, Dom, Cod>(pub &'a Map, pub &'a Dom, pub &'a Cod);
220
221pub type DiscreteDblModelMorphism<'a, DomId, CodId, Cat> = DblModelMorphism<
223 'a,
224 DiscreteDblModelMapping<DomId, CodId>,
225 DiscreteDblModel<DomId, Cat>,
226 DiscreteDblModel<CodId, Cat>,
227>;
228
229impl<'a, DomId, CodId, Cat> DiscreteDblModelMorphism<'a, DomId, CodId, Cat>
230where
231 DomId: Eq + Clone + Hash,
232 CodId: Eq + Clone + Hash,
233 Cat: FgCategory,
234 Cat::Ob: Hash,
235 Cat::Mor: Hash,
236{
237 pub fn iter_invalid(
239 &self,
240 ) -> impl Iterator<Item = InvalidDblModelMorphism<DomId, DomId>> + 'a + use<'a, DomId, CodId, Cat>
241 {
242 let DblModelMorphism(mapping, dom, cod) = *self;
243 assert!(dom.is_free(), "Domain model should be free");
246
247 let ob_errors = dom.ob_generators().filter_map(|v| {
248 if let Some(f_v) = mapping.apply_ob(&v) {
249 if !cod.has_ob(&f_v) {
250 Some(InvalidDblModelMorphism::Ob(v))
251 } else if dom.ob_type(&v) != cod.ob_type(&f_v) {
252 Some(InvalidDblModelMorphism::ObType(v))
253 } else {
254 None
255 }
256 } else {
257 Some(InvalidDblModelMorphism::MissingOb(v))
258 }
259 });
260
261 let mor_errors = dom.mor_generators().flat_map(|f| {
262 if let Some(f_f) = mapping.apply_basic_mor(&f) {
263 if !cod.has_mor(&f_f) {
264 [InvalidDblModelMorphism::Mor(f)].to_vec()
265 } else {
266 let dom_f = mapping.apply_ob(&dom.mor_generator_dom(&f));
267 let cod_f = mapping.apply_ob(&dom.mor_generator_cod(&f));
268 let f_type = dom.mor_generator_type(&f);
269 let ff_type = cod.mor_type(&f_f);
270
271 let mut errs = vec![];
272 if Some(cod.dom(&f_f)) != dom_f {
273 errs.push(InvalidDblModelMorphism::Dom(f.clone()));
274 }
275 if Some(cod.cod(&f_f)) != cod_f {
276 errs.push(InvalidDblModelMorphism::Cod(f.clone()));
277 }
278 if f_type != ff_type {
279 errs.push(InvalidDblModelMorphism::MorType(f));
280 }
281 errs
282 }
283 } else {
284 [InvalidDblModelMorphism::MissingMor(f)].to_vec()
285 }
286 });
287 ob_errors.chain(mor_errors)
288 }
289
290 fn is_simple(&self) -> bool {
293 let DblModelMorphism(mapping, dom, _) = *self;
294 dom.mor_generators()
295 .all(|e| mapping.apply_basic_mor(&e).map(|p| p.is_simple()).unwrap_or(true))
296 }
297
298 pub fn is_injective_objects(&self) -> bool {
300 let DblModelMorphism(mapping, dom, _) = *self;
301 let mut seen_obs: HashSet<_> = HashSet::new();
302 for x in dom.ob_generators() {
303 if let Some(f_x) = mapping.apply_ob(&x) {
304 if seen_obs.contains(&f_x) {
305 return false; } else {
307 seen_obs.insert(f_x);
308 }
309 }
310 }
311 true
312 }
313
314 pub fn is_free_simple_faithful(&self) -> bool {
323 let DblModelMorphism(mapping, dom, cod) = *self;
324
325 assert!(dom.is_free(), "Domain model should be free");
326 assert!(cod.is_free(), "Codomain model should be free");
327 assert!(self.is_simple(), "Morphism assignments should be simple");
328
329 for x in dom.ob_generators() {
330 for y in dom.ob_generators() {
331 let mut seen: HashSet<_> = HashSet::new();
332 for path in simple_paths(dom.generating_graph(), &x, &y) {
333 if let Some(f_path) = mapping.apply_mor(&path) {
334 if seen.contains(&f_path) {
335 return false; } else {
337 seen.insert(f_path);
338 }
339 }
340 }
341 }
342 }
343 true
344 }
345
346 pub fn is_free_simple_monic(&self) -> bool {
354 self.is_injective_objects() && self.is_free_simple_faithful()
355 }
356}
357
358impl<DomId, CodId, Cat> Validate for DiscreteDblModelMorphism<'_, DomId, CodId, Cat>
359where
360 DomId: Eq + Clone + Hash,
361 CodId: Eq + Clone + Hash,
362 Cat: FgCategory,
363 Cat::Ob: Hash,
364 Cat::Mor: Hash,
365{
366 type ValidationError = InvalidDblModelMorphism<DomId, DomId>;
367
368 fn validate(&self) -> Result<(), NonEmpty<Self::ValidationError>> {
369 validate::wrap_errors(self.iter_invalid())
370 }
371}
372
373#[derive(Clone, Debug, Error, PartialEq)]
380#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
381#[cfg_attr(feature = "serde", serde(tag = "tag", content = "content"))]
382#[cfg_attr(feature = "serde-wasm", derive(Tsify))]
383#[cfg_attr(feature = "serde-wasm", tsify(into_wasm_abi, from_wasm_abi))]
384pub enum InvalidDblModelMorphism<Ob, Mor> {
385 #[error("Object `{0}` is mapped to an object not in the codomain")]
387 Ob(Ob),
388
389 #[error("Morphism `{0}` is mapped to a morphism not in the codomain")]
391 Mor(Mor),
392
393 #[error("Object `{0}` is not mapped to an anything in the codomain")]
395 MissingOb(Ob),
396
397 #[error("Morphism `{0}` is not mapped to anything in the codomain")]
399 MissingMor(Mor),
400
401 #[error("Object `{0}` is not mapped to an object of the same type in the codomain")]
403 ObType(Ob),
404
405 #[error("Morphism `{0}` is not mapped to a morphism of the same type in the codomain")]
407 MorType(Mor),
408
409 #[error("Morphism `{0}` has domain not preserved by the mapping")]
411 Dom(Mor),
412
413 #[error("Morphism `{0}` has codomain not preserved by the mapping")]
415 Cod(Mor),
416}
417
418pub struct DiscreteDblModelMorphismFinder<'a, DomId, CodId, Cat: FgCategory> {
427 dom: &'a DiscreteDblModel<DomId, Cat>,
428 cod: &'a DiscreteDblModel<CodId, Cat>,
429 map: DiscreteDblModelMapping<DomId, CodId>,
430 results: Vec<DiscreteDblModelMapping<DomId, CodId>>,
431 var_order: Vec<GraphElem<DomId, DomId>>,
432 max_path_len: Option<usize>,
433 injective_ob: bool,
434 faithful: bool,
435 ob_init: HashColumn<DomId, CodId>,
436 mor_init: HashColumn<DomId, Path<CodId, CodId>>,
437 ob_inv: HashColumn<CodId, DomId>,
438}
439
440impl<'a, DomId, CodId, Cat> DiscreteDblModelMorphismFinder<'a, DomId, CodId, Cat>
441where
442 DomId: Clone + Eq + Hash,
443 CodId: Clone + Eq + Hash,
444 Cat: FgCategory,
445 Cat::Ob: Hash,
446 Cat::Mor: Hash,
447{
448 fn new(dom: &'a DiscreteDblModel<DomId, Cat>, cod: &'a DiscreteDblModel<CodId, Cat>) -> Self {
449 assert!(
450 Arc::ptr_eq(&dom.theory_arc(), &cod.theory_arc()),
451 "Domain and codomain model should have the same theory"
452 );
453 assert!(dom.is_free(), "Domain model should be free");
454
455 let dom_graph = dom.generating_graph();
460 let mut vertices: Vec<_> = dom_graph.vertices().collect();
461 vertices.sort_by_key(|v| std::cmp::Reverse(dom_graph.degree(v)));
462 let var_order = spec_order(dom_graph, vertices.into_iter());
463
464 Self {
465 dom,
466 cod,
467 map: Default::default(),
468 results: Default::default(),
469 var_order,
470 max_path_len: None,
471 injective_ob: false,
472 faithful: false,
473 ob_init: Default::default(),
474 mor_init: Default::default(),
475 ob_inv: Default::default(),
476 }
477 }
478
479 pub fn max_path_len(&mut self, n: usize) -> &mut Self {
481 self.max_path_len = Some(n);
482 self
483 }
484
485 pub fn monic(&mut self) -> &mut Self {
487 self.injective_ob = true;
488 self.faithful = true;
489 self
490 }
491
492 pub fn injective_ob(&mut self) -> &mut Self {
494 self.injective_ob = true;
495 self
496 }
497
498 pub fn faithful(&mut self) -> &mut Self {
508 self.faithful = true;
509 self
510 }
511
512 pub fn initialize_ob(&mut self, ob: DomId, val: CodId) -> &mut Self {
514 self.ob_init.set(ob, val);
515 self
516 }
517
518 pub fn initialize_mor(&mut self, m: DomId, val: Path<CodId, CodId>) -> &mut Self {
520 self.mor_init.set(m, val);
521 self
522 }
523
524 pub fn find_all(&mut self) -> Vec<DiscreteDblModelMapping<DomId, CodId>> {
526 self.search(0);
527 std::mem::take(&mut self.results)
528 }
529
530 fn search(&mut self, depth: usize) {
531 if depth >= self.var_order.len() {
532 if !self.faithful
533 || DblModelMorphism(&self.map, self.dom, self.cod).is_free_simple_faithful()
534 {
535 self.results.push(self.map.clone());
536 }
537 return;
538 }
539 let var = &self.var_order[depth];
540 match var.clone() {
541 GraphElem::Vertex(x) => {
542 if self.ob_init.is_set(&x) {
543 let y = self.ob_init.apply(&x).unwrap();
544 let can_assign = self.assign_ob(x.clone(), y.clone());
545 if can_assign {
546 self.search(depth + 1);
547 self.unassign_ob(x, y)
548 }
549 } else {
550 for y in self.cod.ob_generators_with_type(&self.dom.ob_type(&x)) {
551 let can_assign = self.assign_ob(x.clone(), y.clone());
552 if can_assign {
553 self.search(depth + 1);
554 self.unassign_ob(x.clone(), y)
555 }
556 }
557 }
558 }
559 GraphElem::Edge(m) => {
560 if self.mor_init.is_set(&m) {
561 let path = self.mor_init.apply(&m).unwrap();
562 self.map.assign_basic_mor(m, path);
563 self.search(depth + 1);
564 } else {
565 let mor_type = self.dom.mor_generator_type(&m);
566 let w = self
567 .map
568 .apply_ob(&self.dom.mor_generator_dom(&m))
569 .expect("Domain should already be assigned");
570 let z = self
571 .map
572 .apply_ob(&self.dom.mor_generator_cod(&m))
573 .expect("Codomain should already be assigned");
574
575 let cod_graph = self.cod.generating_graph();
576 for path in bounded_simple_paths(cod_graph, &w, &z, self.max_path_len) {
577 if self.cod.mor_type(&path) == mor_type
578 && !(self.faithful && path.is_empty())
579 {
580 self.map.assign_basic_mor(m.clone(), path);
581 self.search(depth + 1);
582 }
583 }
584 }
585 }
586 }
587 }
588
589 fn assign_ob(&mut self, x: DomId, y: CodId) -> bool {
591 if self.injective_ob {
592 if let Some(y_inv) = self.ob_inv.get(&y) {
593 if *y_inv != x {
594 return false;
595 }
596 }
597 }
598 self.map.assign_ob(x.clone(), y.clone());
599 self.ob_inv.set(y, x);
600 true
601 }
602
603 fn unassign_ob(&mut self, _: DomId, y: CodId) {
605 self.ob_inv.unset(&y);
606 }
607}
608
609#[cfg(test)]
610mod tests {
611 use super::*;
612
613 use crate::dbl::model::UstrDiscreteDblModel;
614 use crate::one::fin_category::FinMor;
615 use crate::stdlib::*;
616 use crate::validate::Validate;
617
618 use std::collections::HashMap;
619 use ustr::ustr;
620
621 #[test]
622 fn discrete_model_mapping() {
623 let mut f: DiscreteDblModelMapping<_, _> = Default::default();
624 f.assign_ob('a', 'x');
625 f.assign_ob('b', 'y');
626 assert!(f.is_ob_assigned(&'a'));
627 assert_eq!(f.apply_ob(&'b'), Some('y'));
628 f.assign_basic_mor('f', Path::pair('p', 'q'));
629 f.assign_basic_mor('g', Path::pair('r', 's'));
630 assert!(f.is_mor_assigned(&Path::single('f')));
631 assert_eq!(f.apply_mor(&Path::pair('f', 'g')), Path::from_vec(vec!['p', 'q', 'r', 's']));
632 }
633
634 #[test]
635 fn find_positive_loops() {
636 let th = Arc::new(th_signed_category());
637 let positive_loop = positive_loop(th.clone());
638 let pos = positive_loop.mor_generators().next().unwrap().into();
639
640 let maps = DiscreteDblModelMapping::morphisms(&positive_loop, &positive_loop).find_all();
641 assert_eq!(maps.len(), 2);
642 let mors: Vec<_> = maps.into_iter().map(|mor| mor.apply_mor(&pos)).collect();
643 assert!(mors.iter().any(|mor| matches!(mor, Some(Path::Id(_)))));
644 assert!(mors.iter().any(|mor| matches!(mor, Some(Path::Seq(_)))));
645
646 let maps = DiscreteDblModelMapping::morphisms(&positive_loop, &positive_loop)
647 .monic()
648 .find_all();
649 assert_eq!(maps.len(), 1);
650 assert!(matches!(maps[0].apply_mor(&pos), Some(Path::Seq(_))));
651 }
652
653 #[test]
657 fn find_simple_paths() {
658 let th = Arc::new(th_signed_category());
659
660 let mut walking = UstrDiscreteDblModel::new(th.clone());
661 let (a, b) = (ustr("A"), ustr("B"));
662 walking.add_ob(a, ustr("Object"));
663 walking.add_ob(b, ustr("Object"));
664 walking.add_mor(ustr("f"), a, b, FinMor::Id(ustr("Object")));
665 let w = Path::single(ustr("f"));
666
667 let mut model = UstrDiscreteDblModel::new(th);
671 let (x, y, z) = (ustr("X"), ustr("Y"), ustr("Z"));
672 model.add_ob(x, ustr("Object"));
673 model.add_ob(y, ustr("Object"));
674 model.add_ob(z, ustr("Object"));
675 model.add_mor(ustr("xy"), x, y, FinMor::Id(ustr("Object")));
676 model.add_mor(ustr("yz"), y, z, FinMor::Id(ustr("Object")));
677 model.add_mor(ustr("zx"), z, x, FinMor::Id(ustr("Object")));
678 model.add_mor(ustr("xz"), x, z, FinMor::Id(ustr("Object")));
679 model.add_mor(ustr("xx"), x, x, FinMor::Id(ustr("Object")));
680
681 for i in model.ob_generators() {
682 for j in model.ob_generators() {
683 let maps: HashSet<_> = DiscreteDblModelMapping::morphisms(&walking, &model)
684 .initialize_ob(ustr("A"), i)
685 .initialize_ob(ustr("B"), j)
686 .find_all()
687 .into_iter()
688 .map(|f| f.apply_mor(&w).unwrap())
689 .collect();
690 let spaths: HashSet<_> = simple_paths(model.generating_graph(), &i, &j).collect();
691 assert_eq!(maps, spaths);
692 }
693 }
694 }
695
696 #[test]
697 fn find_negative_loops() {
698 let th = Arc::new(th_signed_category());
699 let negative_loop = negative_loop(th.clone());
700 let base_pt = negative_loop.ob_generators().next().unwrap();
701
702 let negative_feedback = negative_feedback(th);
703 let maps = DiscreteDblModelMapping::morphisms(&negative_loop, &negative_feedback)
704 .max_path_len(2)
705 .find_all();
706 assert_eq!(maps.len(), 2);
707 let obs: Vec<_> = maps.iter().map(|mor| mor.apply_ob(&base_pt)).collect();
708 assert!(obs.contains(&Some(ustr("x"))));
709 assert!(obs.contains(&Some(ustr("y"))));
710
711 let im = maps[0].syntactic_image(&negative_feedback);
712 assert!(im.validate().is_ok());
713 assert!(im.has_mor(&Path::single(ustr("positive"))));
714 assert!(im.has_mor(&Path::single(ustr("negative"))));
715
716 let maps = DiscreteDblModelMapping::morphisms(&negative_loop, &negative_feedback)
717 .max_path_len(1)
718 .find_all();
719 assert!(maps.is_empty());
720 }
721
722 #[test]
723 fn validate_model_morphism() {
724 let theory = Arc::new(th_signed_category());
725 let negloop = negative_loop(theory.clone());
726 let posfeed = positive_feedback(theory.clone());
727
728 let f = DiscreteDblModelMapping {
729 ob_map: HashMap::from([(ustr("x"), ustr("x"))]).into(),
730 mor_map: HashMap::from([(ustr(""), Path::Id(ustr("negative")))]).into(),
731 };
732 let dmm = DblModelMorphism(&f, &negloop, &negloop);
733 assert!(dmm.validate().is_err());
734
735 let f = DiscreteDblModelMapping {
739 ob_map: HashMap::from([(ustr("x"), ustr("y"))]).into(),
740 mor_map: HashMap::from([(ustr("y"), Path::Id(ustr("y")))]).into(),
741 };
742 let dmm = DblModelMorphism(&f, &negloop, &negloop);
743 let errs: Vec<_> = dmm.validate().expect_err("should be invalid").into();
744 assert!(
745 errs == vec![
746 InvalidDblModelMorphism::Ob(ustr("x")),
747 InvalidDblModelMorphism::MissingMor(ustr("loop")),
748 ]
749 );
750
751 let f = DiscreteDblModelMapping {
753 ob_map: HashMap::from([(ustr("x"), ustr("x"))]).into(),
754 mor_map: HashMap::from([(ustr("loop"), Path::single(ustr("positive1")))]).into(),
755 };
756 let dmm = DblModelMorphism(&f, &negloop, &posfeed);
757 let errs: Vec<_> = dmm.validate().expect_err("should be invalid").into();
758 assert!(
759 errs == vec![
760 InvalidDblModelMorphism::Cod(ustr("loop")),
761 InvalidDblModelMorphism::MorType(ustr("loop")),
762 ]
763 );
764
765 let f = DiscreteDblModelMapping {
767 ob_map: HashMap::from([(ustr("x"), ustr("x"))]).into(),
768 mor_map: HashMap::from([(ustr("loop"), Path::single(ustr("positive2")))]).into(),
769 };
770 let dmm = DblModelMorphism(&f, &negloop, &posfeed);
771 let errs: Vec<_> = dmm.validate().expect_err("should be invalid").into();
772 assert!(
773 errs == vec![
774 InvalidDblModelMorphism::Dom(ustr("loop")),
775 InvalidDblModelMorphism::MorType(ustr("loop")),
776 ]
777 );
778 }
779
780 #[test]
781 fn validate_is_free_simple_monic() {
782 let theory = Arc::new(th_signed_category());
783 let negloop = positive_loop(theory.clone());
784
785 let f = DiscreteDblModelMapping {
787 ob_map: HashMap::from([(ustr("x"), ustr("x"))]).into(),
788 mor_map: HashMap::from([(ustr("loop"), Path::single(ustr("loop")))]).into(),
789 };
790 let dmm = DblModelMorphism(&f, &negloop, &negloop);
791 assert!(dmm.validate().is_ok());
792 assert!(dmm.is_free_simple_monic());
793
794 let f = DiscreteDblModelMapping {
796 ob_map: HashMap::from([(ustr("x"), ustr("x"))]).into(),
797 mor_map: HashMap::from([(ustr("loop"), Path::Id(ustr("x")))]).into(),
798 };
799 let dmm = DblModelMorphism(&f, &negloop, &negloop);
800 assert!(dmm.validate().is_ok());
801 assert!(!dmm.is_free_simple_monic());
802 }
803
804 #[test]
805 fn monic_constraint() {
806 let theory = Arc::new(th_signed_category());
808 let mut model = UstrDiscreteDblModel::new(theory.clone());
809 let (q, x, y, z) = (ustr("Q"), ustr("X"), ustr("Y"), ustr("Z"));
810 let ob = ustr("Object");
811 model.add_ob(q, ob);
812 model.add_ob(x, ob);
813 model.add_ob(y, ob);
814 model.add_ob(z, ob);
815 assert_eq!(
816 DiscreteDblModelMapping::morphisms(&model, &model)
817 .monic()
818 .find_all()
819 .into_iter()
820 .len(),
821 4 * 3 * 2
822 );
823
824 let (f, g, h, i, j) = (ustr("f"), ustr("g"), ustr("h"), ustr("i"), ustr("j"));
827 let mut freetri = UstrDiscreteDblModel::new(theory.clone());
828 freetri.add_ob(x, ob);
829 freetri.add_ob(y, ob);
830 freetri.add_ob(z, ob);
831 freetri.add_mor(f, x, y, FinMor::Id(ob));
832 freetri.add_mor(g, y, z, FinMor::Id(ob));
833 freetri.add_mor(h, x, z, FinMor::Id(ob));
834
835 let mut quad = UstrDiscreteDblModel::new(theory);
836 quad.add_ob(q, ob);
837 quad.add_ob(x, ob);
838 quad.add_ob(y, ob);
839 quad.add_ob(z, ob);
840 quad.add_mor(f, x, y, FinMor::Id(ob));
841 quad.add_mor(g, y, z, FinMor::Id(ob));
842 quad.add_mor(i, y, q, FinMor::Id(ob));
843 quad.add_mor(j, x, q, FinMor::Id(ob));
844
845 assert_eq!(
846 DiscreteDblModelMapping::morphisms(&freetri, &quad)
847 .faithful()
848 .find_all()
849 .into_iter()
850 .len(),
851 1
852 );
853 }
854}