1use std::collections::HashMap;
48
49use panproto_gat::Name;
50use panproto_schema::{Edge, Schema};
51
52use crate::error::RestrictError;
53use crate::functor::FInstance;
54use crate::instance_hom::{FInstanceHom, WInstanceHom};
55use crate::value::Value;
56use crate::wtype::{CompiledMigration, WInstance, wtype_extend};
57
58#[derive(Debug, thiserror::Error)]
61#[non_exhaustive]
62pub enum AdjunctionError {
63 #[error(
68 "vertex map is not injective: source vertices `{first}` and `{second}` \
69 both map to `{target}`, so the W-type pullback is undefined"
70 )]
71 NonInjectiveVertexMap {
72 first: Name,
74 second: Name,
76 target: Name,
78 },
79
80 #[error(
83 "edge map is not injective: two source edges both map to \
84 `{src} -> {tgt}`, so the W-type pullback is undefined"
85 )]
86 NonInjectiveEdgeMap {
87 src: Name,
89 tgt: Name,
91 },
92
93 #[error(
96 "anchor `{0}` lies outside the image of the migration; the pullback is undefined there"
97 )]
98 AnchorOutsideImage(Name),
99
100 #[error("Sigma (left Kan extension) failed: {0}")]
102 Sigma(#[from] RestrictError),
103}
104
105fn map_vertex(migration: &CompiledMigration, vertex: &Name) -> Name {
112 migration
113 .vertex_remap
114 .get(vertex)
115 .cloned()
116 .unwrap_or_else(|| vertex.clone())
117}
118
119fn map_vertex_str(migration: &CompiledMigration, vertex: &str) -> String {
121 migration
122 .vertex_remap
123 .get(vertex)
124 .map_or_else(|| vertex.to_owned(), ToString::to_string)
125}
126
127fn map_edge(migration: &CompiledMigration, edge: &Edge) -> Edge {
130 migration
131 .edge_remap
132 .get(edge)
133 .cloned()
134 .unwrap_or_else(|| Edge {
135 src: map_vertex(migration, &edge.src),
136 tgt: map_vertex(migration, &edge.tgt),
137 kind: edge.kind.clone(),
138 name: edge.name.clone(),
139 })
140}
141
142fn invert_vertex_map(
144 migration: &CompiledMigration,
145) -> Result<HashMap<Name, Name>, AdjunctionError> {
146 let mut inverse: HashMap<Name, Name> = HashMap::with_capacity(migration.vertex_remap.len());
147 for (src, tgt) in &migration.vertex_remap {
148 if let Some(existing) = inverse.insert(tgt.clone(), src.clone()) {
149 if existing != *src {
150 return Err(AdjunctionError::NonInjectiveVertexMap {
151 first: existing,
152 second: src.clone(),
153 target: tgt.clone(),
154 });
155 }
156 }
157 }
158 Ok(inverse)
159}
160
161fn invert_edge_map(migration: &CompiledMigration) -> Result<HashMap<Edge, Edge>, AdjunctionError> {
163 let mut inverse: HashMap<Edge, Edge> = HashMap::with_capacity(migration.edge_remap.len());
164 for (src, tgt) in &migration.edge_remap {
165 if let Some(existing) = inverse.insert(tgt.clone(), src.clone()) {
166 if existing != *src {
167 return Err(AdjunctionError::NonInjectiveEdgeMap {
168 src: tgt.src.clone(),
169 tgt: tgt.tgt.clone(),
170 });
171 }
172 }
173 }
174 Ok(inverse)
175}
176
177pub fn w_sigma(
194 x: &WInstance,
195 tgt_schema: &Schema,
196 migration: &CompiledMigration,
197) -> Result<WInstance, AdjunctionError> {
198 Ok(wtype_extend(x, tgt_schema, migration)?)
199}
200
201pub fn w_delta(y: &WInstance, migration: &CompiledMigration) -> Result<WInstance, AdjunctionError> {
215 let inverse_vertex = invert_vertex_map(migration)?;
216 let inverse_edge = invert_edge_map(migration)?;
217
218 let mut nodes = HashMap::with_capacity(y.nodes.len());
219 for (&id, node) in &y.nodes {
220 let source_anchor = inverse_vertex
221 .get(&node.anchor)
222 .ok_or_else(|| AdjunctionError::AnchorOutsideImage(node.anchor.clone()))?;
223 let mut reindexed = node.clone();
224 reindexed.anchor = source_anchor.clone();
225 nodes.insert(id, reindexed);
226 }
227
228 let mut arcs = Vec::with_capacity(y.arcs.len());
229 for (parent, child, edge) in &y.arcs {
230 let source_edge = inverse_edge.get(edge).cloned().unwrap_or_else(|| Edge {
231 src: inverse_vertex
232 .get(&edge.src)
233 .cloned()
234 .unwrap_or_else(|| edge.src.clone()),
235 tgt: inverse_vertex
236 .get(&edge.tgt)
237 .cloned()
238 .unwrap_or_else(|| edge.tgt.clone()),
239 kind: edge.kind.clone(),
240 name: edge.name.clone(),
241 });
242 arcs.push((*parent, *child, source_edge));
243 }
244
245 let schema_root = inverse_vertex
246 .get(&y.schema_root)
247 .ok_or_else(|| AdjunctionError::AnchorOutsideImage(y.schema_root.clone()))?
248 .clone();
249
250 Ok(WInstance::new(
251 nodes,
252 arcs,
253 y.fans.clone(),
254 y.root,
255 schema_root,
256 ))
257}
258
259#[must_use]
265pub fn w_unit(x: &WInstance) -> WInstanceHom {
266 WInstanceHom::identity(x)
267}
268
269#[must_use]
275pub fn w_counit(y: &WInstance) -> WInstanceHom {
276 WInstanceHom::identity(y)
277}
278
279#[must_use]
285pub fn w_transpose_left(g: &WInstanceHom) -> WInstanceHom {
286 g.clone()
287}
288
289#[must_use]
292pub fn w_transpose_right(f: &WInstanceHom) -> WInstanceHom {
293 f.clone()
294}
295
296struct SigmaImage {
303 instance: FInstance,
305 offset: HashMap<String, usize>,
308 groups: HashMap<String, Vec<(String, usize)>>,
311}
312
313fn sigma_layout(x: &FInstance, migration: &CompiledMigration) -> SigmaImage {
318 let mut source_vertices: Vec<&String> = x.tables.keys().collect();
319 source_vertices.sort();
320
321 let mut tables: HashMap<String, Vec<HashMap<String, Value>>> = HashMap::new();
322 let mut offset: HashMap<String, usize> = HashMap::with_capacity(source_vertices.len());
323 let mut groups: HashMap<String, Vec<(String, usize)>> = HashMap::new();
324
325 for source in source_vertices {
326 let target = map_vertex_str(migration, source);
327 let rows = &x.tables[source];
328 let block = tables.entry(target.clone()).or_default();
329 offset.insert(source.clone(), block.len());
330 groups
331 .entry(target)
332 .or_default()
333 .push((source.clone(), rows.len()));
334 block.extend(rows.iter().cloned());
335 }
336
337 let mut foreign_keys: HashMap<Edge, Vec<(usize, usize)>> = HashMap::new();
338 for (edge, pairs) in &x.foreign_keys {
339 let target_edge = map_edge(migration, edge);
340 let src_offset = offset.get(edge.src.as_str()).copied().unwrap_or(0);
341 let tgt_offset = offset.get(edge.tgt.as_str()).copied().unwrap_or(0);
342 foreign_keys
343 .entry(target_edge)
344 .or_default()
345 .extend(pairs.iter().map(|(i, j)| (i + src_offset, j + tgt_offset)));
346 }
347
348 SigmaImage {
349 instance: FInstance {
350 tables,
351 foreign_keys,
352 },
353 offset,
354 groups,
355 }
356}
357
358#[must_use]
363pub fn f_sigma(x: &FInstance, migration: &CompiledMigration) -> FInstance {
364 sigma_layout(x, migration).instance
365}
366
367#[must_use]
374pub fn f_delta(y: &FInstance, migration: &CompiledMigration) -> FInstance {
375 let mut tables = HashMap::with_capacity(migration.vertex_remap.len());
376 for (source, target) in &migration.vertex_remap {
377 let rows = y.tables.get(target.as_str()).cloned().unwrap_or_default();
378 tables.insert(source.to_string(), rows);
379 }
380
381 let mut foreign_keys = HashMap::with_capacity(migration.edge_remap.len());
382 for (source_edge, target_edge) in &migration.edge_remap {
383 let pairs = y.foreign_keys.get(target_edge).cloned().unwrap_or_default();
384 foreign_keys.insert(source_edge.clone(), pairs);
385 }
386
387 FInstance {
388 tables,
389 foreign_keys,
390 }
391}
392
393#[must_use]
398pub fn f_unit(x: &FInstance, migration: &CompiledMigration) -> FInstanceHom {
399 let sigma = sigma_layout(x, migration);
400 let row_maps = x
401 .tables
402 .iter()
403 .map(|(source, rows)| {
404 let base = sigma.offset.get(source).copied().unwrap_or(0);
405 (source.clone(), (0..rows.len()).map(|i| base + i).collect())
406 })
407 .collect();
408 FInstanceHom::new(row_maps)
409}
410
411#[must_use]
417pub fn f_counit(y: &FInstance, migration: &CompiledMigration) -> FInstanceHom {
418 let delta = f_delta(y, migration);
419 let sigma = sigma_layout(&delta, migration);
420 let row_maps = sigma
421 .instance
422 .tables
423 .iter()
424 .map(|(target, rows)| {
425 let height = y.tables.get(target).map_or(0, Vec::len);
426 let map = (0..rows.len())
427 .map(|r| if height == 0 { 0 } else { r % height })
428 .collect();
429 (target.clone(), map)
430 })
431 .collect();
432 FInstanceHom::new(row_maps)
433}
434
435#[must_use]
441pub fn f_transpose_left(
442 g: &FInstanceHom,
443 x: &FInstance,
444 migration: &CompiledMigration,
445) -> FInstanceHom {
446 let sigma = sigma_layout(x, migration);
447 let row_maps = x
448 .tables
449 .iter()
450 .map(|(source, rows)| {
451 let target = map_vertex_str(migration, source);
452 let base = sigma.offset.get(source).copied().unwrap_or(0);
453 let image = g.row_maps.get(&target);
454 let map = (0..rows.len())
455 .map(|i| {
456 image
457 .and_then(|rows| rows.get(base + i))
458 .copied()
459 .unwrap_or(0)
460 })
461 .collect();
462 (source.clone(), map)
463 })
464 .collect();
465 FInstanceHom::new(row_maps)
466}
467
468#[must_use]
475pub fn f_transpose_right(
476 f: &FInstanceHom,
477 x: &FInstance,
478 migration: &CompiledMigration,
479) -> FInstanceHom {
480 let sigma = sigma_layout(x, migration);
481 let row_maps = sigma
482 .instance
483 .tables
484 .keys()
485 .map(|target| {
486 let mut map: Vec<usize> = Vec::new();
487 if let Some(blocks) = sigma.groups.get(target) {
488 for (source, length) in blocks {
489 let image = f.row_maps.get(source);
490 map.extend(
491 (0..*length)
492 .map(|i| image.and_then(|rows| rows.get(i)).copied().unwrap_or(0)),
493 );
494 }
495 }
496 (target.clone(), map)
497 })
498 .collect();
499 FInstanceHom::new(row_maps)
500}
501
502#[cfg(test)]
503#[allow(clippy::unwrap_used, clippy::expect_used, clippy::too_many_lines)]
504mod tests {
505 use std::collections::{HashMap, HashSet};
506
507 use panproto_schema::Vertex;
508 use smallvec::SmallVec;
509
510 use super::*;
511 use crate::metadata::Node;
512 use crate::value::FieldPresence;
513
514 fn edge(src: &str, tgt: &str, name: &str) -> Edge {
517 Edge {
518 src: src.into(),
519 tgt: tgt.into(),
520 kind: "prop".into(),
521 name: Some(name.into()),
522 }
523 }
524
525 fn schema_of(vertices: &[&str], edges: &[Edge]) -> Schema {
528 let mut between: HashMap<(Name, Name), SmallVec<Edge, 2>> = HashMap::new();
529 let mut outgoing: HashMap<Name, SmallVec<Edge, 4>> = HashMap::new();
530 let mut incoming: HashMap<Name, SmallVec<Edge, 4>> = HashMap::new();
531 let mut edge_map = HashMap::new();
532 for e in edges {
533 between
534 .entry((e.src.clone(), e.tgt.clone()))
535 .or_default()
536 .push(e.clone());
537 outgoing.entry(e.src.clone()).or_default().push(e.clone());
538 incoming.entry(e.tgt.clone()).or_default().push(e.clone());
539 edge_map.insert(e.clone(), e.kind.clone());
540 }
541 Schema {
542 protocol: "test".into(),
543 vertices: vertices
544 .iter()
545 .map(|&v| {
546 (
547 Name::from(v),
548 Vertex {
549 id: Name::from(v),
550 kind: "object".into(),
551 nsid: None,
552 },
553 )
554 })
555 .collect(),
556 edges: edge_map,
557 hyper_edges: HashMap::new(),
558 constraints: HashMap::new(),
559 required: HashMap::new(),
560 nsids: HashMap::new(),
561 entries: Vec::new(),
562 variants: HashMap::new(),
563 orderings: HashMap::new(),
564 recursion_points: HashMap::new(),
565 spans: HashMap::new(),
566 usage_modes: HashMap::new(),
567 nominal: HashMap::new(),
568 coercions: HashMap::new(),
569 mergers: HashMap::new(),
570 defaults: HashMap::new(),
571 policies: HashMap::new(),
572 outgoing,
573 incoming,
574 between,
575 }
576 }
577
578 fn row(v: i64) -> HashMap<String, Value> {
579 HashMap::from([("v".to_owned(), Value::Int(v))])
580 }
581
582 fn merge_migration() -> CompiledMigration {
587 let mut vertex_remap = HashMap::new();
588 vertex_remap.insert(Name::from("a"), Name::from("t"));
589 vertex_remap.insert(Name::from("b"), Name::from("t"));
590 vertex_remap.insert(Name::from("c"), Name::from("u"));
591
592 let mut edge_remap = HashMap::new();
593 edge_remap.insert(edge("a", "c", "ea"), edge("t", "u", "e"));
594 edge_remap.insert(edge("b", "c", "eb"), edge("t", "u", "e"));
595
596 CompiledMigration {
597 surviving_verts: HashSet::from([Name::from("t"), Name::from("u")]),
598 surviving_edges: HashSet::new(),
599 vertex_remap,
600 edge_remap,
601 resolver: HashMap::new(),
602 hyper_resolver: HashMap::new(),
603 field_transforms: HashMap::new(),
604 conditional_survival: HashMap::new(),
605 op_term_assignments: HashMap::new(),
606 expansion_path: HashMap::new(),
607 }
608 }
609
610 #[test]
611 fn f_sigma_merges_fibres_into_coproduct() {
612 let m = merge_migration();
613 let x = FInstance::new()
614 .with_table("a", vec![row(1), row(2)])
615 .with_table("b", vec![row(3)])
616 .with_table("c", vec![row(9)]);
617 let sigma = f_sigma(&x, &m);
618 assert_eq!(sigma.tables.get("t").map(Vec::len), Some(3));
620 assert_eq!(sigma.tables.get("u").map(Vec::len), Some(1));
621 }
622
623 #[test]
624 fn f_unit_and_counit_check_over_merge() {
625 let m = merge_migration();
626 let x = FInstance::new()
627 .with_table("a", vec![row(1), row(2)])
628 .with_table("b", vec![row(3)])
629 .with_table("c", vec![row(9)])
630 .with_foreign_key(edge("a", "c", "ea"), vec![(0, 0), (1, 0)])
631 .with_foreign_key(edge("b", "c", "eb"), vec![(0, 0)]);
632
633 let sigma = f_sigma(&x, &m);
634 let delta_sigma = f_delta(&sigma, &m);
635 let unit = f_unit(&x, &m);
636 unit.check(&x, &delta_sigma)
637 .expect("unit is a valid homomorphism into Delta(Sigma X)");
638
639 let phi_unit = f_transpose_right(&unit, &x, &m);
641 assert_eq!(phi_unit, FInstanceHom::identity(&sigma));
642
643 let y = FInstance::new()
644 .with_table("t", vec![row(5), row(6)])
645 .with_table("u", vec![row(7)])
646 .with_foreign_key(edge("t", "u", "e"), vec![(0, 0), (1, 0)]);
647 let delta_y = f_delta(&y, &m);
648 let sigma_delta_y = f_sigma(&delta_y, &m);
649 let counit = f_counit(&y, &m);
650 counit
651 .check(&sigma_delta_y, &y)
652 .expect("counit is a valid homomorphism onto Y");
653
654 let psi_counit = f_transpose_left(&counit, &delta_y, &m);
656 assert_eq!(psi_counit, FInstanceHom::identity(&delta_y));
657 }
658
659 fn rename_migration() -> CompiledMigration {
663 let mut vertex_remap = HashMap::new();
664 vertex_remap.insert(Name::from("root"), Name::from("box"));
665 vertex_remap.insert(Name::from("leaf"), Name::from("item"));
666 let mut edge_remap = HashMap::new();
667 edge_remap.insert(edge("root", "leaf", "child"), edge("box", "item", "child"));
668 CompiledMigration {
669 surviving_verts: HashSet::from([Name::from("box"), Name::from("item")]),
670 surviving_edges: HashSet::new(),
671 vertex_remap,
672 edge_remap,
673 resolver: HashMap::new(),
674 hyper_resolver: HashMap::new(),
675 field_transforms: HashMap::new(),
676 conditional_survival: HashMap::new(),
677 op_term_assignments: HashMap::new(),
678 expansion_path: HashMap::new(),
679 }
680 }
681
682 fn w_pair() -> WInstance {
683 let mut nodes = HashMap::new();
684 nodes.insert(0, Node::new(0, "root"));
685 nodes.insert(
686 1,
687 Node::new(1, "leaf").with_value(FieldPresence::Present(Value::Str("x".into()))),
688 );
689 WInstance::new(
690 nodes,
691 vec![(0, 1, edge("root", "leaf", "child"))],
692 vec![],
693 0,
694 "root".into(),
695 )
696 }
697
698 #[test]
699 fn w_delta_inverts_sigma_on_injective_maps() {
700 let m = rename_migration();
701 let tgt = schema_of(&["box", "item"], &[edge("box", "item", "child")]);
702 let x = w_pair();
703 let sigma = w_sigma(&x, &tgt, &m).expect("sigma");
704 assert_eq!(sigma.nodes[&0].anchor, Name::from("box"));
706 assert_eq!(sigma.nodes[&1].anchor, Name::from("item"));
707 let round = w_delta(&sigma, &m).expect("delta");
709 assert_eq!(round.nodes[&0].anchor, Name::from("root"));
710 assert_eq!(round.nodes[&1].anchor, Name::from("leaf"));
711
712 let unit = w_unit(&x);
713 unit.check(&x, &round).expect("unit checks");
714 }
715
716 #[test]
717 fn w_delta_rejects_merging_maps() {
718 let m = merge_migration();
719 let mut nodes = HashMap::new();
721 nodes.insert(0, Node::new(0, "t"));
722 let y = WInstance::new(nodes, vec![], vec![], 0, "t".into());
723 let err = w_delta(&y, &m).expect_err("merging map has no W-type pullback");
724 assert!(matches!(err, AdjunctionError::NonInjectiveVertexMap { .. }));
725 }
726
727 mod property {
730 use proptest::prelude::*;
731
732 use super::*;
733
734 #[derive(Debug, Clone)]
737 struct FScenario {
738 x: FInstance,
739 y: FInstance,
740 migration: CompiledMigration,
741 }
742
743 fn round_robin(src_len: usize, tgt_len: usize) -> Vec<(usize, usize)> {
746 if tgt_len == 0 {
747 return Vec::new();
748 }
749 (0..src_len).map(|i| (i, i % tgt_len)).collect()
750 }
751
752 fn arb_scenario() -> impl Strategy<Value = FScenario> {
756 (2usize..=4, 1usize..=4).prop_flat_map(|(n, k)| {
757 let k = k.min(n);
758 let assign = prop::collection::vec(0..k, n);
760 let src_rows = prop::collection::vec(0usize..=3, n);
762 let tgt_rows = prop::collection::vec(0usize..=3, k);
763 (Just(n), Just(k), assign, src_rows, tgt_rows).prop_map(
764 |(n, k, assign, src_rows, tgt_rows)| {
765 let s_name = |i: usize| format!("s{i}");
766 let t_name = |j: usize| format!("t{j}");
767
768 let mut vertex_remap = HashMap::new();
770 for (i, &a) in assign.iter().enumerate() {
771 vertex_remap.insert(Name::from(s_name(i)), Name::from(t_name(a)));
772 }
773 let s_edges: Vec<Edge> = (0..n.saturating_sub(1))
775 .map(|i| edge(&s_name(i), &s_name(i + 1), &format!("e{i}")))
776 .collect();
777 let mut edge_remap = HashMap::new();
778 for (i, e) in s_edges.iter().enumerate() {
779 edge_remap.insert(
780 e.clone(),
781 edge(
782 &t_name(assign[i]),
783 &t_name(assign[i + 1]),
784 &format!("te{i}"),
785 ),
786 );
787 }
788 let surviving_verts: HashSet<Name> =
789 (0..k).map(|j| Name::from(t_name(j))).collect();
790
791 let migration = CompiledMigration {
792 surviving_verts,
793 surviving_edges: HashSet::new(),
794 vertex_remap,
795 edge_remap: edge_remap.clone(),
796 resolver: HashMap::new(),
797 hyper_resolver: HashMap::new(),
798 field_transforms: HashMap::new(),
799 conditional_survival: HashMap::new(),
800 op_term_assignments: HashMap::new(),
801 expansion_path: HashMap::new(),
802 };
803
804 let mut counter = 0i64;
806 let mut x = FInstance::new();
807 for (i, &count) in src_rows.iter().enumerate() {
808 let rows: Vec<_> = (0..count)
809 .map(|_| {
810 counter += 1;
811 row(counter)
812 })
813 .collect();
814 x = x.with_table(s_name(i), rows);
815 }
816 for (i, e) in s_edges.iter().enumerate() {
817 let pairs = round_robin(src_rows[i], src_rows[i + 1]);
818 if !pairs.is_empty() {
819 x = x.with_foreign_key(e.clone(), pairs);
820 }
821 }
822
823 let mut y = FInstance::new();
825 for (j, &count) in tgt_rows.iter().enumerate() {
826 let rows: Vec<_> = (0..count)
827 .map(|_| {
828 counter += 1;
829 row(counter)
830 })
831 .collect();
832 y = y.with_table(t_name(j), rows);
833 }
834 for (i, _e) in s_edges.iter().enumerate() {
835 let te = edge(
836 &t_name(assign[i]),
837 &t_name(assign[i + 1]),
838 &format!("te{i}"),
839 );
840 let pairs = round_robin(tgt_rows[assign[i]], tgt_rows[assign[i + 1]]);
841 if !pairs.is_empty() {
842 y = y.with_foreign_key(te, pairs);
843 }
844 }
845
846 FScenario { x, y, migration }
847 },
848 )
849 })
850 }
851
852 proptest! {
853 #![proptest_config(ProptestConfig::with_cases(256))]
854
855 #[test]
858 fn sigma_delta_triangle_identities(scenario in arb_scenario()) {
859 let FScenario { x, y, migration } = scenario;
860
861 let sigma_x = f_sigma(&x, &migration);
863 let delta_sigma_x = f_delta(&sigma_x, &migration);
864 let unit = f_unit(&x, &migration);
865 prop_assert!(unit.check(&x, &delta_sigma_x).is_ok());
866 let phi_unit = f_transpose_right(&unit, &x, &migration);
867 prop_assert_eq!(phi_unit, FInstanceHom::identity(&sigma_x));
868
869 let delta_y = f_delta(&y, &migration);
871 let sigma_delta_y = f_sigma(&delta_y, &migration);
872 let counit = f_counit(&y, &migration);
873 prop_assert!(counit.check(&sigma_delta_y, &y).is_ok());
874 let psi_counit = f_transpose_left(&counit, &delta_y, &migration);
875 prop_assert_eq!(psi_counit, FInstanceHom::identity(&delta_y));
876 }
877
878 #[test]
882 fn sigma_delta_hom_bijection(scenario in arb_scenario()) {
883 let FScenario { x, y, migration } = scenario;
884
885 let sigma_x = f_sigma(&x, &migration);
889 let mut y_g = FInstance::new();
890 for (t, rows) in &sigma_x.tables {
891 let mut doubled = rows.clone();
892 doubled.extend(rows.iter().cloned());
893 y_g = y_g.with_table(t.clone(), doubled);
894 }
895 for (e, pairs) in &sigma_x.foreign_keys {
896 let len_src = sigma_x.tables.get(e.src.as_str()).map_or(0, Vec::len);
897 let len_tgt = sigma_x.tables.get(e.tgt.as_str()).map_or(0, Vec::len);
898 let mut all = pairs.clone();
899 all.extend(pairs.iter().map(|(i, j)| (i + len_src, j + len_tgt)));
900 y_g = y_g.with_foreign_key(e.clone(), all);
901 }
902 let g = FInstanceHom::identity(&sigma_x);
903 prop_assert!(g.check(&sigma_x, &y_g).is_ok());
904
905 let delta_y_g = f_delta(&y_g, &migration);
906 let psi_g = f_transpose_left(&g, &x, &migration);
907 prop_assert!(psi_g.check(&x, &delta_y_g).is_ok());
908 let phi_psi_g = f_transpose_right(&psi_g, &x, &migration);
910 prop_assert_eq!(phi_psi_g, g);
911
912 let delta_y = f_delta(&y, &migration);
915 let f = FInstanceHom::identity(&delta_y);
916 prop_assert!(f.check(&delta_y, &delta_y).is_ok());
917 let phi_f = f_transpose_right(&f, &delta_y, &migration);
918 let sigma_delta_y = f_sigma(&delta_y, &migration);
919 prop_assert!(phi_f.check(&sigma_delta_y, &y).is_ok());
920 let psi_phi_f = f_transpose_left(&phi_f, &delta_y, &migration);
922 prop_assert_eq!(psi_phi_f, f);
923 }
924 }
925
926 #[derive(Debug, Clone)]
932 struct WScenario {
933 x: WInstance,
934 tgt_schema: Schema,
935 migration: CompiledMigration,
936 }
937
938 fn arb_w_scenario() -> impl Strategy<Value = WScenario> {
941 (1usize..=4, any::<bool>()).prop_map(|(n, rename)| {
942 let (s_root, s_leaf, t_root, t_leaf) = if rename {
943 ("root", "leaf", "box", "item")
944 } else {
945 ("root", "leaf", "root", "leaf")
946 };
947
948 let mut nodes = HashMap::new();
949 nodes.insert(0, Node::new(0, s_root));
950 let mut arcs = Vec::new();
951 for i in 0..n {
952 let id = u32::try_from(i + 1).unwrap();
953 nodes.insert(
954 id,
955 Node::new(id, s_leaf)
956 .with_value(FieldPresence::Present(Value::Int(i64::from(id)))),
957 );
958 arcs.push((0, id, edge(s_root, s_leaf, "child")));
959 }
960 let x = WInstance::new(nodes, arcs, vec![], 0, Name::from(s_root));
961
962 let mut vertex_remap = HashMap::new();
963 vertex_remap.insert(Name::from(s_root), Name::from(t_root));
964 vertex_remap.insert(Name::from(s_leaf), Name::from(t_leaf));
965 let mut edge_remap = HashMap::new();
966 edge_remap.insert(edge(s_root, s_leaf, "child"), edge(t_root, t_leaf, "child"));
967
968 let migration = CompiledMigration {
969 surviving_verts: HashSet::from([Name::from(t_root), Name::from(t_leaf)]),
970 surviving_edges: HashSet::new(),
971 vertex_remap,
972 edge_remap,
973 resolver: HashMap::new(),
974 hyper_resolver: HashMap::new(),
975 field_transforms: HashMap::new(),
976 conditional_survival: HashMap::new(),
977 op_term_assignments: HashMap::new(),
978 expansion_path: HashMap::new(),
979 };
980 let tgt_schema = schema_of(&[t_root, t_leaf], &[edge(t_root, t_leaf, "child")]);
981 WScenario {
982 x,
983 tgt_schema,
984 migration,
985 }
986 })
987 }
988
989 fn shift_nodes(y: &WInstance, shift: u32) -> (WInstance, WInstanceHom) {
992 let nodes = y
993 .nodes
994 .iter()
995 .map(|(&id, node)| {
996 let mut moved = node.clone();
997 moved.id = id + shift;
998 (id + shift, moved)
999 })
1000 .collect();
1001 let arcs = y
1002 .arcs
1003 .iter()
1004 .map(|(p, c, e)| (p + shift, c + shift, e.clone()))
1005 .collect();
1006 let shifted = WInstance::new(
1007 nodes,
1008 arcs,
1009 y.fans.clone(),
1010 y.root + shift,
1011 y.schema_root.clone(),
1012 );
1013 let hom = WInstanceHom::new(y.nodes.keys().map(|&id| (id, id + shift)).collect());
1014 (shifted, hom)
1015 }
1016
1017 proptest! {
1018 #![proptest_config(ProptestConfig::with_cases(256))]
1019
1020 #[test]
1023 fn w_sigma_delta_triangle_identities(scenario in arb_w_scenario()) {
1024 let WScenario { x, tgt_schema, migration } = scenario;
1025
1026 let sigma_x = w_sigma(&x, &tgt_schema, &migration).unwrap();
1027 let delta_sigma_x = w_delta(&sigma_x, &migration).unwrap();
1028 let unit = w_unit(&x);
1029 prop_assert!(unit.check(&x, &delta_sigma_x).is_ok());
1030 let phi_unit = w_transpose_right(&unit);
1032 prop_assert!(phi_unit.check(&sigma_x, &sigma_x).is_ok());
1033 prop_assert_eq!(phi_unit, WInstanceHom::identity(&sigma_x));
1034
1035 let y = sigma_x;
1037 let delta_y = w_delta(&y, &migration).unwrap();
1038 let sigma_delta_y = w_sigma(&delta_y, &tgt_schema, &migration).unwrap();
1039 let counit = w_counit(&y);
1040 prop_assert!(counit.check(&sigma_delta_y, &y).is_ok());
1041 let psi_counit = w_transpose_left(&counit);
1043 prop_assert!(psi_counit.check(&delta_y, &delta_y).is_ok());
1044 prop_assert_eq!(psi_counit, WInstanceHom::identity(&delta_y));
1045 }
1046
1047 #[test]
1051 fn w_sigma_delta_hom_bijection(scenario in arb_w_scenario()) {
1052 let WScenario { x, tgt_schema, migration } = scenario;
1053
1054 let sigma_x = w_sigma(&x, &tgt_schema, &migration).unwrap();
1055 let (y, g) = shift_nodes(&sigma_x, 100);
1058 prop_assert!(g.check(&sigma_x, &y).is_ok());
1059
1060 let delta_y = w_delta(&y, &migration).unwrap();
1061 let psi_g = w_transpose_left(&g);
1062 prop_assert!(psi_g.check(&x, &delta_y).is_ok());
1063 let phi_psi_g = w_transpose_right(&psi_g);
1065 prop_assert_eq!(phi_psi_g, g);
1066
1067 let delta_sigma_x = w_delta(&sigma_x, &migration).unwrap();
1069 let f = w_unit(&x);
1070 prop_assert!(f.check(&x, &delta_sigma_x).is_ok());
1071 let phi_f = w_transpose_right(&f);
1072 prop_assert!(phi_f.check(&sigma_x, &sigma_x).is_ok());
1073 let psi_phi_f = w_transpose_left(&phi_f);
1074 prop_assert_eq!(psi_phi_f, f);
1075 }
1076 }
1077 }
1078}