1use std::collections::HashMap;
9
10use panproto_gat::Name;
11use smallvec::SmallVec;
12
13use crate::error::SchemaError;
14use crate::morphism::SchemaMorphism;
15use crate::schema::{Edge, Schema, Vertex};
16
17#[derive(Clone, Debug, Default)]
23pub struct SchemaOverlap {
24 pub vertex_pairs: Vec<(Name, Name)>,
26 pub edge_pairs: Vec<(Edge, Edge)>,
28}
29
30fn remap_edge(edge: &Edge, vmap: &HashMap<Name, Name>) -> Edge {
32 Edge {
33 src: vmap
34 .get(&edge.src)
35 .cloned()
36 .unwrap_or_else(|| edge.src.clone()),
37 tgt: vmap
38 .get(&edge.tgt)
39 .cloned()
40 .unwrap_or_else(|| edge.tgt.clone()),
41 kind: edge.kind.clone(),
42 name: edge.name.clone(),
43 }
44}
45
46pub fn schema_pushout(
56 left: &Schema,
57 right: &Schema,
58 overlap: &SchemaOverlap,
59) -> Result<(Schema, SchemaMorphism, SchemaMorphism), SchemaError> {
60 let right_vertex_rename = build_vertex_rename(left, right, overlap)?;
61
62 let (merged_vertices, left_vertex_map, right_vertex_map) =
63 build_merged_vertices(left, right, &right_vertex_rename);
64
65 let (merged_edges, left_edge_map, right_edge_map) =
66 build_merged_edges(left, right, overlap, &right_vertex_rename);
67
68 let pushout = assemble_pushout(
69 left,
70 right,
71 &right_vertex_rename,
72 merged_vertices,
73 merged_edges,
74 );
75
76 let left_morphism = SchemaMorphism {
77 name: "left→pushout".into(),
78 src_protocol: left.protocol.clone(),
79 tgt_protocol: pushout.protocol.clone(),
80 vertex_map: left_vertex_map,
81 edge_map: left_edge_map,
82 renames: vec![],
83 };
84
85 let right_morphism = SchemaMorphism {
86 name: "right→pushout".into(),
87 src_protocol: right.protocol.clone(),
88 tgt_protocol: pushout.protocol.clone(),
89 vertex_map: right_vertex_map,
90 edge_map: right_edge_map,
91 renames: vec![],
92 };
93
94 Ok((pushout, left_morphism, right_morphism))
95}
96
97fn build_vertex_rename(
103 left: &Schema,
104 right: &Schema,
105 overlap: &SchemaOverlap,
106) -> Result<HashMap<Name, Name>, SchemaError> {
107 let mut rename: HashMap<Name, Name> = HashMap::new();
108
109 for (left_id, right_id) in &overlap.vertex_pairs {
110 if !left.vertices.contains_key(left_id) {
111 return Err(SchemaError::VertexNotFound(left_id.to_string()));
112 }
113 if !right.vertices.contains_key(right_id) {
114 return Err(SchemaError::VertexNotFound(right_id.to_string()));
115 }
116 rename.insert(right_id.clone(), left_id.clone());
117 }
118
119 for right_id in right.vertices.keys() {
120 if rename.contains_key(right_id) {
121 continue;
122 }
123 if left.vertices.contains_key(right_id) {
124 let merged_id = Name::from(format!("right.{right_id}"));
125 rename.insert(right_id.clone(), merged_id);
126 } else {
127 rename.insert(right_id.clone(), right_id.clone());
128 }
129 }
130
131 Ok(rename)
132}
133
134fn build_merged_vertices(
136 left: &Schema,
137 right: &Schema,
138 right_rename: &HashMap<Name, Name>,
139) -> (
140 HashMap<Name, Vertex>,
141 HashMap<Name, Name>,
142 HashMap<Name, Name>,
143) {
144 let mut merged: HashMap<Name, Vertex> = HashMap::new();
145
146 for (id, v) in &left.vertices {
147 merged.insert(id.clone(), v.clone());
148 }
149
150 for (right_id, v) in &right.vertices {
151 let merged_id = right_rename
152 .get(right_id)
153 .cloned()
154 .unwrap_or_else(|| right_id.clone());
155 merged.entry(merged_id.clone()).or_insert_with(|| Vertex {
156 id: merged_id,
157 kind: v.kind.clone(),
158 nsid: v.nsid.clone(),
159 });
160 }
161
162 let left_map: HashMap<Name, Name> = left
163 .vertices
164 .keys()
165 .map(|id| (id.clone(), id.clone()))
166 .collect();
167
168 let right_map: HashMap<Name, Name> = right_rename.clone();
169
170 (merged, left_map, right_map)
171}
172
173fn build_merged_edges(
175 left: &Schema,
176 right: &Schema,
177 overlap: &SchemaOverlap,
178 right_rename: &HashMap<Name, Name>,
179) -> (
180 HashMap<Edge, Name>,
181 HashMap<Edge, Edge>,
182 HashMap<Edge, Edge>,
183) {
184 let right_edge_to_left: HashMap<Edge, Edge> = overlap
185 .edge_pairs
186 .iter()
187 .map(|(l, r)| (r.clone(), l.clone()))
188 .collect();
189
190 let mut merged: HashMap<Edge, Name> = HashMap::new();
191 let mut left_map: HashMap<Edge, Edge> = HashMap::new();
192 let mut right_map: HashMap<Edge, Edge> = HashMap::new();
193
194 for (edge, kind) in &left.edges {
195 merged.insert(edge.clone(), kind.clone());
196 left_map.insert(edge.clone(), edge.clone());
197 }
198
199 for (edge, kind) in &right.edges {
200 if let Some(left_edge) = right_edge_to_left.get(edge) {
201 right_map.insert(edge.clone(), left_edge.clone());
202 } else {
203 let remapped = remap_edge(edge, right_rename);
204 if !merged.contains_key(&remapped) {
205 merged.insert(remapped.clone(), kind.clone());
206 }
207 right_map.insert(edge.clone(), remapped);
208 }
209 }
210
211 (merged, left_map, right_map)
212}
213
214fn resolve(right_rename: &HashMap<Name, Name>, id: &Name) -> Name {
216 right_rename.get(id).cloned().unwrap_or_else(|| id.clone())
217}
218
219fn merge_vertex_keyed(
221 left: &Schema,
222 right: &Schema,
223 right_rename: &HashMap<Name, Name>,
224) -> MergedVertexKeyed {
225 let mut constraints = left.constraints.clone();
227 for (rid, rcs) in &right.constraints {
228 let mid = resolve(right_rename, rid);
229 let entry = constraints.entry(mid).or_default();
230 for c in rcs {
231 if !entry.contains(c) {
232 entry.push(c.clone());
233 }
234 }
235 }
236
237 let mut required = left.required.clone();
239 for (rid, rreqs) in &right.required {
240 let mid = resolve(right_rename, rid);
241 let entry = required.entry(mid).or_default();
242 for req in rreqs {
243 let remapped = remap_edge(req, right_rename);
244 if !entry.contains(&remapped) {
245 entry.push(remapped);
246 }
247 }
248 }
249
250 let mut nsids = left.nsids.clone();
252 for (rid, nsid) in &right.nsids {
253 let mid = resolve(right_rename, rid);
254 nsids.entry(mid).or_insert_with(|| nsid.clone());
255 }
256
257 let mut variants = left.variants.clone();
259 for (rid, vs) in &right.variants {
260 let mid = resolve(right_rename, rid);
261 let entry = variants.entry(mid).or_default();
262 for v in vs {
263 let mut v2 = v.clone();
264 v2.parent_vertex = resolve(right_rename, &v2.parent_vertex);
265 if !entry.contains(&v2) {
266 entry.push(v2);
267 }
268 }
269 }
270
271 let mut nominal = left.nominal.clone();
273 for (rid, &nom) in &right.nominal {
274 let mid = resolve(right_rename, rid);
275 nominal.entry(mid).or_insert(nom);
276 }
277
278 MergedVertexKeyed {
279 constraints,
280 required,
281 nsids,
282 variants,
283 nominal,
284 }
285}
286
287struct MergedVertexKeyed {
289 constraints: HashMap<Name, Vec<crate::schema::Constraint>>,
290 required: HashMap<Name, Vec<Edge>>,
291 nsids: HashMap<Name, Name>,
292 variants: HashMap<Name, Vec<crate::schema::Variant>>,
293 nominal: HashMap<Name, bool>,
294}
295
296fn assemble_pushout(
299 left: &Schema,
300 right: &Schema,
301 right_rename: &HashMap<Name, Name>,
302 merged_vertices: HashMap<Name, Vertex>,
303 merged_edges: HashMap<Edge, Name>,
304) -> Schema {
305 let vk = merge_vertex_keyed(left, right, right_rename);
306
307 let mut hyper_edges = left.hyper_edges.clone();
309 for (id, he) in &right.hyper_edges {
310 let mid = if hyper_edges.contains_key(id) {
311 Name::from(format!("right.{id}"))
312 } else {
313 id.clone()
314 };
315 let mut he2 = he.clone();
316 he2.id = mid.clone();
317 he2.signature = he2
318 .signature
319 .into_iter()
320 .map(|(label, vid)| {
321 let new_vid = right_rename.get(&vid).cloned().unwrap_or(vid);
322 (label, new_vid)
323 })
324 .collect();
325 hyper_edges.insert(mid, he2);
326 }
327
328 let mut orderings = left.orderings.clone();
330 for (edge, pos) in &right.orderings {
331 let remapped = remap_edge(edge, right_rename);
332 orderings.entry(remapped).or_insert(*pos);
333 }
334
335 let mut recursion_points = left.recursion_points.clone();
337 for (id, rp) in &right.recursion_points {
338 let mid = resolve(right_rename, id);
339 recursion_points.entry(mid.clone()).or_insert_with(|| {
340 let mut rp2 = rp.clone();
341 rp2.mu_id = mid;
342 rp2.target_vertex = resolve(right_rename, &rp2.target_vertex);
343 rp2
344 });
345 }
346
347 let mut spans = left.spans.clone();
349 for (id, sp) in &right.spans {
350 let mid = if spans.contains_key(id) {
351 Name::from(format!("right.{id}"))
352 } else {
353 id.clone()
354 };
355 let mut sp2 = sp.clone();
356 sp2.id = mid.clone();
357 sp2.left = resolve(right_rename, &sp2.left);
358 sp2.right = resolve(right_rename, &sp2.right);
359 spans.insert(mid, sp2);
360 }
361
362 let mut usage_modes = left.usage_modes.clone();
364 for (edge, mode) in &right.usage_modes {
365 let remapped = remap_edge(edge, right_rename);
366 usage_modes.entry(remapped).or_insert_with(|| mode.clone());
367 }
368
369 let mut coercions = left.coercions.clone();
371 for (key, spec) in &right.coercions {
372 let merged_key = (resolve(right_rename, &key.0), resolve(right_rename, &key.1));
373 coercions.entry(merged_key).or_insert_with(|| spec.clone());
374 }
375
376 let mut mergers = left.mergers.clone();
378 for (rid, expr) in &right.mergers {
379 let mid = resolve(right_rename, rid);
380 mergers.entry(mid).or_insert_with(|| expr.clone());
381 }
382
383 let mut defaults = left.defaults.clone();
385 for (rid, expr) in &right.defaults {
386 let mid = resolve(right_rename, rid);
387 defaults.entry(mid).or_insert_with(|| expr.clone());
388 }
389
390 let mut policies = left.policies.clone();
392 for (rid, expr) in &right.policies {
393 let mid = resolve(right_rename, rid);
394 policies.entry(mid).or_insert_with(|| expr.clone());
395 }
396
397 let idx = build_indices(&merged_edges);
399
400 Schema {
401 protocol: left.protocol.clone(),
402 vertices: merged_vertices,
403 edges: merged_edges,
404 hyper_edges,
405 constraints: vk.constraints,
406 required: vk.required,
407 nsids: vk.nsids,
408 variants: vk.variants,
409 orderings,
410 recursion_points,
411 spans,
412 usage_modes,
413 nominal: vk.nominal,
414 coercions,
415 mergers,
416 defaults,
417 policies,
418 outgoing: idx.outgoing,
419 incoming: idx.incoming,
420 between: idx.between,
421 }
422}
423
424struct AdjacencyIndices {
426 outgoing: HashMap<Name, SmallVec<Edge, 4>>,
428 incoming: HashMap<Name, SmallVec<Edge, 4>>,
430 between: HashMap<(Name, Name), SmallVec<Edge, 2>>,
432}
433
434fn build_indices(edges: &HashMap<Edge, Name>) -> AdjacencyIndices {
436 let mut outgoing: HashMap<Name, SmallVec<Edge, 4>> = HashMap::new();
437 let mut incoming: HashMap<Name, SmallVec<Edge, 4>> = HashMap::new();
438 let mut between: HashMap<(Name, Name), SmallVec<Edge, 2>> = HashMap::new();
439
440 for edge in edges.keys() {
441 outgoing
442 .entry(edge.src.clone())
443 .or_default()
444 .push(edge.clone());
445 incoming
446 .entry(edge.tgt.clone())
447 .or_default()
448 .push(edge.clone());
449 between
450 .entry((edge.src.clone(), edge.tgt.clone()))
451 .or_default()
452 .push(edge.clone());
453 }
454
455 AdjacencyIndices {
456 outgoing,
457 incoming,
458 between,
459 }
460}
461
462#[cfg(test)]
463#[allow(clippy::unwrap_used)]
464mod tests {
465 use super::*;
466 use crate::{Protocol, SchemaBuilder};
467
468 fn test_protocol() -> Protocol {
469 Protocol {
470 name: "test".into(),
471 schema_theory: "ThTest".into(),
472 instance_theory: "ThWType".into(),
473 edge_rules: vec![],
474 obj_kinds: vec!["object".into(), "string".into(), "integer".into()],
475 constraint_sorts: vec![],
476 ..Protocol::default()
477 }
478 }
479
480 fn build_schema(vertices: &[(&str, &str)], edges: &[(&str, &str, &str, &str)]) -> Schema {
481 let proto = test_protocol();
482 let mut builder = SchemaBuilder::new(&proto);
483 for (id, kind) in vertices {
484 builder = builder.vertex(id, kind, None::<&str>).unwrap();
485 }
486 for (src, tgt, kind, name) in edges {
487 builder = builder.edge(src, tgt, kind, Some(*name)).unwrap();
488 }
489 builder.build().unwrap()
490 }
491
492 #[test]
493 fn pushout_of_identical_schemas_is_itself() {
494 let s = build_schema(
495 &[("root", "object"), ("root.x", "string")],
496 &[("root", "root.x", "prop", "x")],
497 );
498 let overlap = SchemaOverlap {
499 vertex_pairs: vec![
500 (Name::from("root"), Name::from("root")),
501 (Name::from("root.x"), Name::from("root.x")),
502 ],
503 edge_pairs: vec![(
504 Edge {
505 src: Name::from("root"),
506 tgt: Name::from("root.x"),
507 kind: Name::from("prop"),
508 name: Some(Name::from("x")),
509 },
510 Edge {
511 src: Name::from("root"),
512 tgt: Name::from("root.x"),
513 kind: Name::from("prop"),
514 name: Some(Name::from("x")),
515 },
516 )],
517 };
518
519 let (pushout, left_m, right_m) = schema_pushout(&s, &s, &overlap).unwrap();
520 assert_eq!(pushout.vertex_count(), s.vertex_count());
521 assert_eq!(pushout.edge_count(), s.edge_count());
522
523 for (src, tgt) in &left_m.vertex_map {
524 assert_eq!(src, tgt, "left morphism should be identity");
525 }
526 for (src, tgt) in &right_m.vertex_map {
527 assert_eq!(src, tgt, "right morphism should be identity");
528 }
529 }
530
531 #[test]
532 fn pushout_of_disjoint_schemas_is_union() {
533 let left = build_schema(
534 &[("a", "object"), ("a.x", "string")],
535 &[("a", "a.x", "prop", "x")],
536 );
537 let right = build_schema(
538 &[("b", "object"), ("b.y", "integer")],
539 &[("b", "b.y", "prop", "y")],
540 );
541
542 let overlap = SchemaOverlap::default();
543 let (pushout, _left_m, _right_m) = schema_pushout(&left, &right, &overlap).unwrap();
544
545 assert_eq!(pushout.vertex_count(), 4);
546 assert_eq!(pushout.edge_count(), 2);
547
548 assert!(pushout.has_vertex("a"));
549 assert!(pushout.has_vertex("a.x"));
550 assert!(pushout.has_vertex("b"));
551 assert!(pushout.has_vertex("b.y"));
552 }
553
554 #[test]
555 fn pushout_with_vertex_overlap_merges_vertices() {
556 let left = build_schema(
557 &[("root", "object"), ("root.x", "string")],
558 &[("root", "root.x", "prop", "x")],
559 );
560 let right = build_schema(
561 &[("base", "object"), ("base.y", "integer")],
562 &[("base", "base.y", "prop", "y")],
563 );
564
565 let overlap = SchemaOverlap {
566 vertex_pairs: vec![(Name::from("root"), Name::from("base"))],
567 edge_pairs: vec![],
568 };
569
570 let (pushout, left_m, right_m) = schema_pushout(&left, &right, &overlap).unwrap();
571
572 assert_eq!(pushout.vertex_count(), 3);
573 assert!(pushout.has_vertex("root"));
574 assert!(pushout.has_vertex("root.x"));
575 assert!(pushout.has_vertex("base.y"));
576
577 assert_eq!(
578 left_m.vertex_map.get("root").map(Name::as_str),
579 Some("root")
580 );
581 assert_eq!(
582 right_m.vertex_map.get("base").map(Name::as_str),
583 Some("root")
584 );
585 assert_eq!(
586 right_m.vertex_map.get("base.y").map(Name::as_str),
587 Some("base.y")
588 );
589 }
590
591 #[test]
592 fn pushout_with_edge_overlap_merges_edges() {
593 let left = build_schema(
594 &[("root", "object"), ("root.x", "string")],
595 &[("root", "root.x", "prop", "x")],
596 );
597 let right = build_schema(
598 &[("root", "object"), ("root.x", "string")],
599 &[("root", "root.x", "prop", "x")],
600 );
601
602 let overlap = SchemaOverlap {
603 vertex_pairs: vec![
604 (Name::from("root"), Name::from("root")),
605 (Name::from("root.x"), Name::from("root.x")),
606 ],
607 edge_pairs: vec![(
608 Edge {
609 src: Name::from("root"),
610 tgt: Name::from("root.x"),
611 kind: Name::from("prop"),
612 name: Some(Name::from("x")),
613 },
614 Edge {
615 src: Name::from("root"),
616 tgt: Name::from("root.x"),
617 kind: Name::from("prop"),
618 name: Some(Name::from("x")),
619 },
620 )],
621 };
622
623 let (pushout, _left_m, right_m) = schema_pushout(&left, &right, &overlap).unwrap();
624
625 assert_eq!(pushout.edge_count(), 1);
626
627 let right_edge = Edge {
628 src: Name::from("root"),
629 tgt: Name::from("root.x"),
630 kind: Name::from("prop"),
631 name: Some(Name::from("x")),
632 };
633 assert!(
634 right_m.edge_map.contains_key(&right_edge),
635 "right morphism should map the overlapping edge"
636 );
637 }
638
639 #[test]
640 fn morphisms_into_pushout_are_valid() {
641 let left = build_schema(
642 &[("root", "object"), ("root.a", "string")],
643 &[("root", "root.a", "prop", "a")],
644 );
645 let right = build_schema(
646 &[("root", "object"), ("root.b", "integer")],
647 &[("root", "root.b", "prop", "b")],
648 );
649
650 let overlap = SchemaOverlap {
651 vertex_pairs: vec![(Name::from("root"), Name::from("root"))],
652 edge_pairs: vec![],
653 };
654
655 let (pushout, left_m, right_m) = schema_pushout(&left, &right, &overlap).unwrap();
656
657 for (src, tgt) in &left_m.vertex_map {
658 assert!(
659 pushout.has_vertex(tgt),
660 "left morphism target `{tgt}` (from `{src}`) should exist in pushout"
661 );
662 }
663
664 for (src, tgt) in &right_m.vertex_map {
665 assert!(
666 pushout.has_vertex(tgt),
667 "right morphism target `{tgt}` (from `{src}`) should exist in pushout"
668 );
669 }
670
671 for tgt_e in left_m.edge_map.values() {
672 assert!(
673 pushout.edges.contains_key(tgt_e),
674 "left morphism edge target should exist in pushout"
675 );
676 }
677
678 for tgt_e in right_m.edge_map.values() {
679 assert!(
680 pushout.edges.contains_key(tgt_e),
681 "right morphism edge target should exist in pushout"
682 );
683 }
684 }
685
686 #[test]
687 fn pushout_conflicting_vertex_ids_are_prefixed() {
688 let left = build_schema(
689 &[("v", "object"), ("v.x", "string")],
690 &[("v", "v.x", "prop", "x")],
691 );
692 let right = build_schema(
693 &[("v", "object"), ("v.y", "integer")],
694 &[("v", "v.y", "prop", "y")],
695 );
696
697 let overlap = SchemaOverlap::default();
698 let (pushout, _left_m, right_m) = schema_pushout(&left, &right, &overlap).unwrap();
699
700 assert!(pushout.has_vertex("v"));
701 assert!(pushout.has_vertex("right.v"));
702 assert_eq!(
703 right_m.vertex_map.get("v").map(Name::as_str),
704 Some("right.v")
705 );
706 }
707
708 #[test]
709 fn overlap_with_missing_vertex_returns_error() {
710 let s = build_schema(&[("a", "object")], &[]);
711 let overlap = SchemaOverlap {
712 vertex_pairs: vec![(Name::from("nonexistent"), Name::from("a"))],
713 edge_pairs: vec![],
714 };
715 let result = schema_pushout(&s, &s, &overlap);
716 assert!(result.is_err());
717 }
718}