strixonomy_owl/
compare.rs1use crate::bridge::{bridge_ontology, OwlBridgeResult};
4use horned_owl::model::{RcAnnotatedComponent, RcStr};
5use horned_owl::ontology::component_mapped::ComponentMappedOntology;
6use std::collections::{BTreeMap, BTreeSet};
7use strixonomy_core::AXIOM_KIND_SUB_CLASS_OF;
8
9#[derive(Debug, Clone, Default, PartialEq, Eq)]
11pub struct SemanticDiff {
12 pub missing_entities: Vec<String>,
13 pub extra_entities: Vec<String>,
14 pub label_mismatches: Vec<String>,
15 pub parent_mismatches: Vec<String>,
16 pub import_mismatches: Vec<String>,
17 pub ontology_iri_mismatch: Option<(Option<String>, Option<String>)>,
18}
19
20impl SemanticDiff {
21 pub fn is_empty(&self) -> bool {
22 self.missing_entities.is_empty()
23 && self.extra_entities.is_empty()
24 && self.label_mismatches.is_empty()
25 && self.parent_mismatches.is_empty()
26 && self.import_mismatches.is_empty()
27 && self.ontology_iri_mismatch.is_none()
28 }
29}
30
31pub fn compare_ontologies(
33 left: ComponentMappedOntology<RcStr, RcAnnotatedComponent>,
34 right: ComponentMappedOntology<RcStr, RcAnnotatedComponent>,
35) -> SemanticDiff {
36 let left_bridge = bridge_ontology(left, "left", "", &BTreeMap::new());
37 let right_bridge = bridge_ontology(right, "right", "", &BTreeMap::new());
38 compare_bridges(&left_bridge, &right_bridge)
39}
40
41pub fn compare_bridges(left: &OwlBridgeResult, right: &OwlBridgeResult) -> SemanticDiff {
42 let mut diff = SemanticDiff::default();
43
44 if left.base_iri != right.base_iri {
45 diff.ontology_iri_mismatch = Some((left.base_iri.clone(), right.base_iri.clone()));
46 }
47
48 let left_iris: BTreeSet<_> = left.entities.iter().map(|e| e.iri.clone()).collect();
49 let right_iris: BTreeSet<_> = right.entities.iter().map(|e| e.iri.clone()).collect();
50 for iri in left_iris.difference(&right_iris) {
51 diff.missing_entities.push(iri.clone());
52 }
53 for iri in right_iris.difference(&left_iris) {
54 diff.extra_entities.push(iri.clone());
55 }
56
57 let right_by_iri: BTreeMap<_, _> = right.entities.iter().map(|e| (e.iri.clone(), e)).collect();
58 for le in &left.entities {
59 if let Some(re) = right_by_iri.get(&le.iri) {
60 let mut ll = le.labels.clone();
61 let mut rl = re.labels.clone();
62 ll.sort();
63 rl.sort();
64 if ll != rl {
65 diff.label_mismatches.push(le.iri.clone());
66 }
67 }
68 }
69
70 let left_parents = parents_map(left);
71 let right_parents = parents_map(right);
72 let subjects: BTreeSet<_> = left_parents.keys().chain(right_parents.keys()).cloned().collect();
73 for subject in subjects {
74 let lp = left_parents.get(&subject).cloned().unwrap_or_default();
75 let rp = right_parents.get(&subject).cloned().unwrap_or_default();
76 if lp != rp {
77 diff.parent_mismatches.push(subject);
78 }
79 }
80
81 let left_imports: BTreeSet<_> = left.imports.iter().map(|i| i.import_iri.clone()).collect();
82 let right_imports: BTreeSet<_> = right.imports.iter().map(|i| i.import_iri.clone()).collect();
83 for iri in left_imports.symmetric_difference(&right_imports) {
84 diff.import_mismatches.push(iri.clone());
85 }
86
87 diff
88}
89
90fn parents_map(bridge: &OwlBridgeResult) -> BTreeMap<String, BTreeSet<String>> {
91 let mut map: BTreeMap<String, BTreeSet<String>> = BTreeMap::new();
92 for ax in &bridge.axioms {
93 if ax.axiom_kind == AXIOM_KIND_SUB_CLASS_OF {
94 map.entry(ax.subject.clone()).or_default().insert(ax.object.clone());
95 }
96 }
97 map
98}