1use crate::ConceptPool;
22use crate::ir::{ClassId, ConceptExpr, ConceptId};
23use crate::ontology::{Axiom, InternalOntology};
24
25#[derive(Debug, Default, Clone)]
27pub struct Definitions {
28 defs: Vec<Option<ConceptId>>,
29}
30
31impl Definitions {
32 #[must_use]
34 pub fn body_of(&self, c: ClassId) -> Option<ConceptId> {
35 self.defs.get(c.index() as usize).copied().flatten()
36 }
37
38 #[must_use]
40 pub fn is_defined(&self, c: ClassId) -> bool {
41 self.body_of(c).is_some()
42 }
43
44 #[must_use]
47 pub fn num_classes(&self) -> usize {
48 self.defs.len()
49 }
50
51 #[must_use]
53 pub fn num_defined(&self) -> usize {
54 self.defs.iter().filter(|o| o.is_some()).count()
55 }
56
57 pub fn iter(&self) -> impl Iterator<Item = (ClassId, ConceptId)> + '_ {
59 (0u32..)
60 .zip(self.defs.iter())
61 .filter_map(|(i, body)| body.map(|b| (ClassId::new(i), b)))
62 }
63}
64
65#[must_use]
67pub fn extract_definitions(ontology: &InternalOntology) -> Definitions {
68 let n = ontology.vocabulary.num_classes();
69 let mut defs: Vec<Option<ConceptId>> = vec![None; n];
70
71 for axiom in &ontology.axioms {
72 if let Axiom::EquivalentClasses(ids) = axiom
73 && ids.len() == 2
74 && let Some((name, body)) = pick_def_pair(ids[0], ids[1], &ontology.concepts)
75 {
76 let idx = name.index() as usize;
77 if defs[idx].is_none() {
78 defs[idx] = Some(body);
79 }
80 }
82 }
83
84 Definitions { defs }
85}
86
87fn pick_def_pair(a: ConceptId, b: ConceptId, pool: &ConceptPool) -> Option<(ClassId, ConceptId)> {
89 let a_atom = match pool.get(a) {
90 ConceptExpr::Atomic(c) => Some(*c),
91 _ => None,
92 };
93 let b_atom = match pool.get(b) {
94 ConceptExpr::Atomic(c) => Some(*c),
95 _ => None,
96 };
97 match (a_atom, b_atom) {
98 (Some(name), None) => Some((name, b)),
99 (None, Some(name)) => Some((name, a)),
100 _ => None,
101 }
102}
103
104#[cfg(test)]
105mod tests {
106 #![allow(clippy::many_single_char_names)]
107
108 use super::*;
109 use crate::ir::{Role, RoleId};
110
111 fn ontology_with(classes: &[&str]) -> InternalOntology {
112 let mut o = InternalOntology::new();
113 for c in classes {
114 o.vocabulary.intern_class(c);
115 }
116 o
117 }
118
119 fn cid(o: &InternalOntology, name: &str) -> ClassId {
120 o.vocabulary.class_id(name).expect("class missing")
121 }
122
123 fn atom(o: &mut InternalOntology, name: &str) -> ConceptId {
124 let c = cid(o, name);
125 o.concepts.atomic(c)
126 }
127
128 #[test]
129 fn empty_ontology_has_no_definitions() {
130 let o = ontology_with(&["A", "B"]);
131 let d = extract_definitions(&o);
132 assert_eq!(d.num_classes(), 2);
133 assert_eq!(d.num_defined(), 0);
134 assert!(!d.is_defined(cid(&o, "A")));
135 }
136
137 #[test]
138 fn equiv_atomic_to_complex_creates_definition() {
139 let mut o = ontology_with(&["A", "B"]);
141 let a = atom(&mut o, "A");
142 let b = atom(&mut o, "B");
143 let r = Role::named(RoleId::new(0));
144 let some_b = o.concepts.some(r, b);
145 o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
146 let d = extract_definitions(&o);
147 let a_id = cid(&o, "A");
148 assert!(d.is_defined(a_id));
149 assert_eq!(d.body_of(a_id), Some(some_b));
150 assert!(!d.is_defined(cid(&o, "B")));
152 }
153
154 #[test]
155 fn equiv_complex_to_atomic_creates_definition_with_swap() {
156 let mut o = ontology_with(&["A", "B"]);
158 let a = atom(&mut o, "A");
159 let b = atom(&mut o, "B");
160 let r = Role::named(RoleId::new(0));
161 let some_b = o.concepts.some(r, b);
162 o.axioms.push(Axiom::EquivalentClasses(vec![some_b, a]));
163 let d = extract_definitions(&o);
164 assert_eq!(d.body_of(cid(&o, "A")), Some(some_b));
165 }
166
167 #[test]
168 fn two_atomic_members_is_not_a_definition() {
169 let mut o = ontology_with(&["A", "B"]);
171 let a = atom(&mut o, "A");
172 let b = atom(&mut o, "B");
173 o.axioms.push(Axiom::EquivalentClasses(vec![a, b]));
174 let d = extract_definitions(&o);
175 assert_eq!(d.num_defined(), 0);
176 }
177
178 #[test]
179 fn three_or_more_members_skipped() {
180 let mut o = ontology_with(&["A", "B"]);
181 let a = atom(&mut o, "A");
182 let b = atom(&mut o, "B");
183 let r = Role::named(RoleId::new(0));
184 let some_b = o.concepts.some(r, b);
185 o.axioms.push(Axiom::EquivalentClasses(vec![a, b, some_b]));
186 let d = extract_definitions(&o);
187 assert_eq!(d.num_defined(), 0);
188 }
189
190 #[test]
191 fn two_complex_members_skipped() {
192 let mut o = ontology_with(&["A", "B"]);
193 let a = atom(&mut o, "A");
194 let b = atom(&mut o, "B");
195 let r = Role::named(RoleId::new(0));
196 let some_b = o.concepts.some(r, b);
197 let not_a = o.concepts.not(a);
198 o.axioms.push(Axiom::EquivalentClasses(vec![some_b, not_a]));
199 let d = extract_definitions(&o);
200 assert_eq!(d.num_defined(), 0);
201 }
202
203 #[test]
204 fn first_definition_wins_when_conflicts() {
205 let mut o = ontology_with(&["A", "B", "C"]);
207 let a = atom(&mut o, "A");
208 let b = atom(&mut o, "B");
209 let cc = atom(&mut o, "C");
210 let r = Role::named(RoleId::new(0));
211 let some_b = o.concepts.some(r, b);
212 let some_c = o.concepts.some(r, cc);
213 o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
214 o.axioms.push(Axiom::EquivalentClasses(vec![a, some_c]));
215 let d = extract_definitions(&o);
216 assert_eq!(d.body_of(cid(&o, "A")), Some(some_b));
217 }
218
219 #[test]
220 fn idempotent_when_same_def_appears_twice() {
221 let mut o = ontology_with(&["A", "B"]);
222 let a = atom(&mut o, "A");
223 let b = atom(&mut o, "B");
224 let r = Role::named(RoleId::new(0));
225 let some_b = o.concepts.some(r, b);
226 o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
227 o.axioms.push(Axiom::EquivalentClasses(vec![a, some_b]));
228 let d = extract_definitions(&o);
229 assert_eq!(d.num_defined(), 1);
230 }
231
232 #[test]
233 fn multiple_distinct_class_definitions() {
234 let mut o = ontology_with(&["A", "B", "X", "Y"]);
236 let a = atom(&mut o, "A");
237 let b = atom(&mut o, "B");
238 let x = atom(&mut o, "X");
239 let y = atom(&mut o, "Y");
240 let r = Role::named(RoleId::new(0));
241 let s = Role::named(RoleId::new(1));
242 let some_x = o.concepts.some(r, x);
243 let some_y = o.concepts.some(s, y);
244 o.axioms.push(Axiom::EquivalentClasses(vec![a, some_x]));
245 o.axioms.push(Axiom::EquivalentClasses(vec![b, some_y]));
246 let d = extract_definitions(&o);
247 assert_eq!(d.num_defined(), 2);
248 let pairs: Vec<(ClassId, ConceptId)> = d.iter().collect();
249 assert_eq!(pairs, vec![(cid(&o, "A"), some_x), (cid(&o, "B"), some_y)]);
250 }
251
252 #[test]
253 fn sub_class_only_is_not_a_definition() {
254 let mut o = ontology_with(&["A", "B"]);
256 let a = atom(&mut o, "A");
257 let b = atom(&mut o, "B");
258 let r = Role::named(RoleId::new(0));
259 let some_b = o.concepts.some(r, b);
260 o.axioms.push(Axiom::SubClassOf {
261 sub: a,
262 sup: some_b,
263 });
264 let d = extract_definitions(&o);
265 assert_eq!(d.num_defined(), 0);
266 }
267}