1use std::collections::HashSet;
4use std::rc::Rc;
5
6use nonempty::NonEmpty;
7
8use crate::dbl::{model::*, model_morphism::*};
9use crate::one::graph_algorithms::{bounded_simple_paths, simple_paths, spec_order};
10use crate::one::*;
11use crate::validate::{self, Validate};
12use crate::zero::{HashColumn, Mapping, MutMapping, QualifiedName};
13
14#[derive(Clone, Debug, Default, PartialEq, Eq)]
19pub struct DiscreteDblModelMapping(pub DiscreteDblModelMappingData);
20
21type DiscreteDblModelMappingData = FpFunctorData<
22 HashColumn<QualifiedName, QualifiedName>,
23 HashColumn<QualifiedName, QualifiedPath>,
24>;
25
26impl DiscreteDblModelMapping {
27 pub fn new(
29 ob_pairs: impl IntoIterator<Item = (QualifiedName, QualifiedName)>,
30 mor_pairs: impl IntoIterator<Item = (QualifiedName, QualifiedPath)>,
31 ) -> Self {
32 Self(FpFunctorData::new(
33 ob_pairs.into_iter().collect(),
34 mor_pairs.into_iter().collect(),
35 ))
36 }
37
38 pub fn assign_ob(&mut self, x: QualifiedName, y: QualifiedName) -> Option<QualifiedName> {
40 self.0.ob_generator_map.set(x, y)
41 }
42
43 pub fn assign_mor(&mut self, e: QualifiedName, n: QualifiedPath) -> Option<QualifiedPath> {
45 self.0.mor_generator_map.set(e, n)
46 }
47
48 pub fn unassign_ob(&mut self, x: &QualifiedName) -> Option<QualifiedName> {
50 self.0.ob_generator_map.unset(x)
51 }
52
53 pub fn unassign_mor(&mut self, e: &QualifiedName) -> Option<QualifiedPath> {
55 self.0.mor_generator_map.unset(e)
56 }
57
58 pub fn functor_into<'a>(
60 &'a self,
61 cod: &'a DiscreteDblModel,
62 ) -> FpFunctor<'a, DiscreteDblModelMappingData, QualifiedFpCategory> {
63 self.0.functor_into(&cod.category)
64 }
65
66 pub fn morphisms<'a>(
68 dom: &'a DiscreteDblModel,
69 cod: &'a DiscreteDblModel,
70 ) -> DiscreteDblModelMorphismFinder<'a> {
71 DiscreteDblModelMorphismFinder::new(dom, cod)
72 }
73}
74
75pub struct DblModelMorphism<'a, Map, Dom, Cod>(pub &'a Map, pub &'a Dom, pub &'a Cod);
81
82pub type DiscreteDblModelMorphism<'a> =
84 DblModelMorphism<'a, DiscreteDblModelMapping, DiscreteDblModel, DiscreteDblModel>;
85
86impl<'a> DiscreteDblModelMorphism<'a> {
87 pub fn iter_invalid(
89 &self,
90 ) -> impl Iterator<Item = InvalidDblModelMorphism<QualifiedName, QualifiedName>> + 'a + use<'a>
91 {
92 let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, cod) = *self;
93 let category_errors: Vec<_> = mapping
94 .functor_into(&cod.category)
95 .iter_invalid_on(&dom.category)
96 .map(|err| match err {
97 InvalidFpFunctor::ObGen(x) => InvalidDblModelMorphism::Ob(x),
98 InvalidFpFunctor::MorGen(m) => InvalidDblModelMorphism::Mor(m),
99 InvalidFpFunctor::Dom(m) => InvalidDblModelMorphism::Dom(m),
100 InvalidFpFunctor::Cod(m) => InvalidDblModelMorphism::Cod(m),
101 InvalidFpFunctor::Eq(id) => InvalidDblModelMorphism::Eq(id),
102 })
103 .collect();
104 let ob_type_errors = dom.ob_generators().filter_map(|x| {
105 if let Some(y) = mapping.ob_generator_map.get(&x)
106 && cod.has_ob(y)
107 && dom.ob_type(&x) != cod.ob_type(y)
108 {
109 Some(InvalidDblModelMorphism::ObType(x))
110 } else {
111 None
112 }
113 });
114 let th_cat = cod.theory();
115 let mor_type_errors = dom.mor_generators().filter_map(move |f| {
116 if let Some(g) = mapping.mor_generator_map.get(&f)
117 && cod.has_mor(g)
118 && !th_cat.0.morphisms_are_equal(dom.mor_generator_type(&f), cod.mor_type(g))
119 {
120 Some(InvalidDblModelMorphism::MorType(f))
121 } else {
122 None
123 }
124 });
125 category_errors.into_iter().chain(ob_type_errors).chain(mor_type_errors)
126 }
127
128 fn is_simple(&self) -> bool {
131 let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, _) = *self;
132 dom.mor_generators()
133 .all(|e| mapping.apply_edge(e).map(|p| p.is_simple()).unwrap_or(true))
134 }
135
136 pub fn is_injective_objects(&self) -> bool {
138 let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, _) = *self;
139 let mut seen_obs: HashSet<_> = HashSet::new();
140 for x in dom.ob_generators() {
141 if let Some(f_x) = mapping.apply_vertex(x) {
142 if seen_obs.contains(&f_x) {
143 return false; } else {
145 seen_obs.insert(f_x);
146 }
147 }
148 }
149 true
150 }
151
152 pub fn is_free_simple_faithful(&self) -> bool {
160 let DblModelMorphism(DiscreteDblModelMapping(mapping), dom, cod) = *self;
161
162 assert!(dom.is_free(), "Domain model should be free");
163 assert!(cod.is_free(), "Codomain model should be free");
164 assert!(self.is_simple(), "Morphism assignments should be simple");
165
166 let functor = mapping.functor_into(&cod.category);
167 for x in dom.ob_generators() {
168 for y in dom.ob_generators() {
169 let mut seen: HashSet<_> = HashSet::new();
170 for path in simple_paths(dom.generating_graph(), &x, &y) {
171 if let Some(f_path) = functor.apply_mor(path) {
172 if seen.contains(&f_path) {
173 return false; } else {
175 seen.insert(f_path);
176 }
177 }
178 }
179 }
180 }
181 true
182 }
183
184 pub fn is_free_simple_monic(&self) -> bool {
191 self.is_injective_objects() && self.is_free_simple_faithful()
192 }
193}
194
195impl Validate for DiscreteDblModelMorphism<'_> {
196 type ValidationError = InvalidDblModelMorphism<QualifiedName, QualifiedName>;
197
198 fn validate(&self) -> Result<(), NonEmpty<Self::ValidationError>> {
199 validate::wrap_errors(self.iter_invalid())
200 }
201}
202
203pub struct DiscreteDblModelMorphismFinder<'a> {
211 dom: &'a DiscreteDblModel,
212 cod: &'a DiscreteDblModel,
213 map: DiscreteDblModelMapping,
214 results: Vec<DiscreteDblModelMapping>,
215 var_order: Vec<GraphElem<QualifiedName, QualifiedName>>,
216 max_path_len: Option<usize>,
217 injective_ob: bool,
218 faithful: bool,
219 ob_init: HashColumn<QualifiedName, QualifiedName>,
220 mor_init: HashColumn<QualifiedName, QualifiedPath>,
221 ob_inv: HashColumn<QualifiedName, QualifiedName>,
222}
223
224impl<'a> DiscreteDblModelMorphismFinder<'a> {
225 fn new(dom: &'a DiscreteDblModel, cod: &'a DiscreteDblModel) -> Self {
226 assert!(
227 Rc::ptr_eq(&dom.theory(), &cod.theory()),
228 "Domain and codomain model should have the same theory"
229 );
230 assert!(dom.is_free(), "Domain model should be free");
231
232 let dom_graph = dom.generating_graph();
237 let mut vertices: Vec<_> = dom_graph.vertices().collect();
238 vertices.sort_by_key(|v| std::cmp::Reverse(dom_graph.degree(v)));
239 let var_order = spec_order(dom_graph, vertices.into_iter());
240
241 Self {
242 dom,
243 cod,
244 map: Default::default(),
245 results: Default::default(),
246 var_order,
247 max_path_len: None,
248 injective_ob: false,
249 faithful: false,
250 ob_init: Default::default(),
251 mor_init: Default::default(),
252 ob_inv: Default::default(),
253 }
254 }
255
256 pub fn max_path_len(&mut self, n: usize) -> &mut Self {
258 self.max_path_len = Some(n);
259 self
260 }
261
262 pub fn monic(&mut self) -> &mut Self {
264 self.injective_ob = true;
265 self.faithful = true;
266 self
267 }
268
269 pub fn injective_ob(&mut self) -> &mut Self {
271 self.injective_ob = true;
272 self
273 }
274
275 pub fn faithful(&mut self) -> &mut Self {
284 self.faithful = true;
285 self
286 }
287
288 pub fn initialize_ob(&mut self, ob: QualifiedName, val: QualifiedName) -> &mut Self {
290 self.ob_init.set(ob, val);
291 self
292 }
293
294 pub fn initialize_mor(&mut self, m: QualifiedName, val: QualifiedPath) -> &mut Self {
296 self.mor_init.set(m, val);
297 self
298 }
299
300 pub fn find_all(&mut self) -> Vec<DiscreteDblModelMapping> {
302 self.search(0);
303 std::mem::take(&mut self.results)
304 }
305
306 fn search(&mut self, depth: usize) {
307 if depth >= self.var_order.len() {
308 if !self.faithful
309 || DblModelMorphism(&self.map, self.dom, self.cod).is_free_simple_faithful()
310 {
311 self.results.push(self.map.clone());
312 }
313 return;
314 }
315 let var = &self.var_order[depth];
316 match var.clone() {
317 GraphElem::Vertex(x) => {
318 if let Some(y) = self.ob_init.apply_to_ref(&x) {
319 let can_assign = self.assign_ob(x.clone(), y.clone());
320 if can_assign {
321 self.search(depth + 1);
322 self.unassign_ob(x, y)
323 }
324 } else {
325 for y in self.cod.ob_generators_with_type(&self.dom.ob_type(&x)) {
326 let can_assign = self.assign_ob(x.clone(), y.clone());
327 if can_assign {
328 self.search(depth + 1);
329 self.unassign_ob(x.clone(), y)
330 }
331 }
332 }
333 }
334 GraphElem::Edge(m) => {
335 if let Some(path) = self.mor_init.apply_to_ref(&m) {
336 self.map.assign_mor(m, path);
337 self.search(depth + 1);
338 } else {
339 let functor = self.map.0.functor_into(&self.cod.category);
340 let mor_type = self.dom.mor_generator_type(&m);
341 let w = functor
342 .apply_ob(self.dom.mor_generator_dom(&m))
343 .expect("Domain should already be assigned");
344 let z = functor
345 .apply_ob(self.dom.mor_generator_cod(&m))
346 .expect("Codomain should already be assigned");
347
348 let cod_graph = self.cod.generating_graph();
349 let th_cat = &self.cod.theory().0;
350 for path in bounded_simple_paths(cod_graph, &w, &z, self.max_path_len) {
351 if th_cat.morphisms_are_equal(self.cod.mor_type(&path), mor_type.clone())
352 && !(self.faithful && path.is_empty())
353 {
354 self.map.assign_mor(m.clone(), path);
355 self.search(depth + 1);
356 }
357 }
358 }
359 }
360 }
361 }
362
363 fn assign_ob(&mut self, x: QualifiedName, y: QualifiedName) -> bool {
365 if self.injective_ob && self.ob_inv.get(&y).is_some_and(|y_inv| *y_inv != x) {
366 return false;
367 }
368 self.map.assign_ob(x.clone(), y.clone());
369 self.ob_inv.set(y, x);
370 true
371 }
372
373 fn unassign_ob(&mut self, _: QualifiedName, y: QualifiedName) {
375 self.ob_inv.unset(&y);
376 }
377}
378
379#[cfg(test)]
380mod tests {
381 use super::*;
382 use crate::stdlib::*;
383 use crate::validate::Validate;
384 use crate::{one::Path, zero::name};
385
386 #[test]
387 fn find_positive_loops() {
388 let th = Rc::new(th_signed_category());
389 let positive_loop = positive_loop(th.clone());
390 let pos = positive_loop.mor_generators().next().unwrap().into();
391
392 let maps = DiscreteDblModelMapping::morphisms(&positive_loop, &positive_loop).find_all();
393 assert_eq!(maps.len(), 2);
394 let mors: Vec<_> = maps
395 .into_iter()
396 .map(|map| map.functor_into(&positive_loop).mor_map().apply_to_ref(&pos))
397 .collect();
398 assert!(mors.iter().any(|mor| matches!(mor, Some(Path::Id(_)))));
399 assert!(mors.iter().any(|mor| matches!(mor, Some(Path::Seq(_)))));
400
401 let maps = DiscreteDblModelMapping::morphisms(&positive_loop, &positive_loop)
402 .monic()
403 .find_all();
404 assert_eq!(maps.len(), 1);
405 assert!(matches!(
406 maps[0].functor_into(&positive_loop).apply_mor(pos),
407 Some(Path::Seq(_))
408 ));
409 }
410
411 #[test]
415 fn find_simple_paths() {
416 let th = Rc::new(th_signed_category());
417
418 let mut walking = DiscreteDblModel::new(th.clone());
419 walking.add_ob(name("A"), name("Object"));
420 walking.add_ob(name("B"), name("Object"));
421 walking.add_mor(name("f"), name("A"), name("B"), Path::Id(name("Object")));
422
423 let mut model = DiscreteDblModel::new(th);
427 model.add_ob(name("X"), name("Object"));
428 model.add_ob(name("Y"), name("Object"));
429 model.add_ob(name("Z"), name("Object"));
430 model.add_mor(name("xy"), name("X"), name("Y"), Path::Id(name("Object")));
431 model.add_mor(name("yz"), name("Y"), name("Z"), Path::Id(name("Object")));
432 model.add_mor(name("zx"), name("Z"), name("X"), Path::Id(name("Object")));
433 model.add_mor(name("xz"), name("X"), name("Z"), Path::Id(name("Object")));
434 model.add_mor(name("xx"), name("X"), name("X"), Path::Id(name("Object")));
435
436 for i in model.ob_generators() {
437 for j in model.ob_generators() {
438 let maps: HashSet<_> = DiscreteDblModelMapping::morphisms(&walking, &model)
439 .initialize_ob(name("A"), i.clone())
440 .initialize_ob(name("B"), j.clone())
441 .find_all()
442 .into_iter()
443 .map(|map| map.functor_into(&model).apply_mor_generator(name("f")).unwrap())
444 .collect();
445 let spaths: HashSet<_> = simple_paths(model.generating_graph(), &i, &j).collect();
446 assert_eq!(maps, spaths);
447 }
448 }
449 }
450
451 #[test]
452 fn find_negative_loops() {
453 let th = Rc::new(th_signed_category());
454 let negative_loop = negative_loop(th.clone());
455 let base_pt = negative_loop.ob_generators().next().unwrap();
456
457 let negative_feedback = negative_feedback(th);
458 let maps = DiscreteDblModelMapping::morphisms(&negative_loop, &negative_feedback)
459 .max_path_len(2)
460 .find_all();
461 assert_eq!(maps.len(), 2);
462 let obs: Vec<_> = maps
463 .iter()
464 .map(|map| map.functor_into(&negative_feedback).apply_ob(base_pt.clone()))
465 .collect();
466 assert!(obs.contains(&Some(name("x"))));
467 assert!(obs.contains(&Some(name("y"))));
468
469 let maps = DiscreteDblModelMapping::morphisms(&negative_loop, &negative_feedback)
470 .max_path_len(1)
471 .find_all();
472 assert!(maps.is_empty());
473 }
474
475 #[test]
476 fn validate_model_morphism() {
477 let theory = Rc::new(th_signed_category());
478 let negloop = negative_loop(theory.clone());
479 let posfeed = positive_feedback(theory.clone());
480
481 let f = DiscreteDblModelMapping::new(
482 [(name("x"), name("x"))],
483 [(name(""), Path::Id(name("negative")))],
484 );
485 let dmm = DblModelMorphism(&f, &negloop, &negloop);
486 assert!(dmm.validate().is_err());
487
488 let f = DiscreteDblModelMapping::new(
492 [(name("x"), name("y"))],
493 [(name("y"), Path::Id(name("y")))],
494 );
495 let dmm = DblModelMorphism(&f, &negloop, &negloop);
496 let errs: Vec<_> = dmm.validate().unwrap_err().into();
497 assert!(
498 errs == vec![
499 InvalidDblModelMorphism::Ob(name("x")),
500 InvalidDblModelMorphism::Mor(name("loop")),
501 ]
502 );
503
504 let f = DiscreteDblModelMapping::new(
506 [(name("x"), name("x"))],
507 [(name("loop"), Path::single(name("positive1")))],
508 );
509 let dmm = DblModelMorphism(&f, &negloop, &posfeed);
510 let errs: Vec<_> = dmm.validate().unwrap_err().into();
511 assert!(
512 errs == vec![
513 InvalidDblModelMorphism::Cod(name("loop")),
514 InvalidDblModelMorphism::MorType(name("loop")),
515 ]
516 );
517
518 let f = DiscreteDblModelMapping::new(
520 [(name("x"), name("x"))],
521 [(name("loop"), Path::single(name("positive2")))],
522 );
523 let dmm = DblModelMorphism(&f, &negloop, &posfeed);
524 let errs: Vec<_> = dmm.validate().unwrap_err().into();
525 assert!(
526 errs == vec![
527 InvalidDblModelMorphism::Dom(name("loop")),
528 InvalidDblModelMorphism::MorType(name("loop")),
529 ]
530 );
531 }
532
533 #[test]
534 fn validate_is_free_simple_monic() {
535 let theory = Rc::new(th_signed_category());
536 let negloop = positive_loop(theory.clone());
537
538 let f = DiscreteDblModelMapping::new(
540 [(name("x"), name("x"))],
541 [(name("loop"), Path::single(name("loop")))],
542 );
543 let dmm = DblModelMorphism(&f, &negloop, &negloop);
544 assert!(dmm.validate().is_ok());
545 assert!(dmm.is_free_simple_monic());
546
547 let f = DiscreteDblModelMapping::new(
549 [(name("x"), name("x"))],
550 [(name("loop"), Path::Id(name("x")))],
551 );
552 let dmm = DblModelMorphism(&f, &negloop, &negloop);
553 assert!(dmm.validate().is_ok());
554 assert!(!dmm.is_free_simple_monic());
555 }
556
557 #[test]
558 fn monic_constraint() {
559 let theory = Rc::new(th_signed_category());
561 let mut model = DiscreteDblModel::new(theory.clone());
562 for id in ["Q", "X", "Y", "Z"] {
563 model.add_ob(name(id), name("Object"));
564 }
565 let mors = DiscreteDblModelMapping::morphisms(&model, &model).monic().find_all();
566 assert_eq!(mors.into_iter().len(), 4 * 3 * 2);
567
568 let mut freetri = DiscreteDblModel::new(theory.clone());
571 for id in ["X", "Y", "Z"] {
572 freetri.add_ob(name(id), name("Object"));
573 }
574 freetri.add_mor(name("f"), name("X"), name("Y"), Path::Id(name("Object")));
575 freetri.add_mor(name("g"), name("Y"), name("Z"), Path::Id(name("Object")));
576 freetri.add_mor(name("h"), name("X"), name("Z"), Path::Id(name("Object")));
577
578 let mut quad = DiscreteDblModel::new(theory);
579 for id in ["Q", "X", "Y", "Z"] {
580 quad.add_ob(name(id), name("Object"));
581 }
582 quad.add_mor(name("f"), name("X"), name("Y"), Path::Id(name("Object")));
583 quad.add_mor(name("g"), name("Y"), name("Z"), Path::Id(name("Object")));
584 quad.add_mor(name("i"), name("Y"), name("Q"), Path::Id(name("Object")));
585 quad.add_mor(name("j"), name("X"), name("Q"), Path::Id(name("Object")));
586 let mors = DiscreteDblModelMapping::morphisms(&freetri, &quad).faithful().find_all();
587 assert_eq!(mors.into_iter().len(), 1);
588 }
589}