1use crate::adapter::ReasonerId;
4use crate::error::{ReasonerError, Result};
5use crate::result::{
6 entity_iri, ConsistencyDetail, InferredAssertions, InstanceCheckResult, RealizationEntry,
7 RealizationResult, SameAsCluster,
8};
9use ontologos_core::{EntityId, EntityKind, Ontology, Profile, Reasoner, ReasonerConfig};
10use ontologos_facade::{
11 check_consistency as facade_check_consistency, is_entailed_axiom, EntailmentCheck,
12};
13use std::collections::{BTreeMap, BTreeSet};
14use std::sync::atomic::{AtomicBool, Ordering};
15use std::sync::Arc;
16use std::time::Instant;
17
18const MAX_REALIZE_INDIVIDUALS: usize = 500;
20const MAX_REALIZE_ENTAILMENT_CHECKS: usize = 50_000;
22
23static ENGINE_CANCEL: std::sync::Mutex<Option<Arc<AtomicBool>>> = std::sync::Mutex::new(None);
25
26pub fn install_cancel_flag(flag: Arc<AtomicBool>) {
28 ontologos_core::set_current_cancel(Some(Arc::clone(&flag)));
29 if let Ok(mut guard) = ENGINE_CANCEL.lock() {
30 *guard = Some(flag);
31 }
32}
33
34pub fn clear_cancel_flag() {
36 ontologos_core::set_current_cancel(None);
37 if let Ok(mut guard) = ENGINE_CANCEL.lock() {
38 *guard = None;
39 }
40}
41
42pub fn cancel_requested() -> bool {
44 if ontologos_core::cancel_requested() {
45 return true;
46 }
47 ENGINE_CANCEL
48 .lock()
49 .ok()
50 .and_then(|g| g.as_ref().map(|f| f.load(Ordering::Acquire)))
51 .unwrap_or(false)
52}
53
54fn profile_for(id: ReasonerId) -> Profile {
55 match id {
56 ReasonerId::El => Profile::El,
57 ReasonerId::Rl => Profile::Rl,
58 ReasonerId::Rdfs => Profile::Rdfs,
59 ReasonerId::Dl => Profile::Dl,
60 ReasonerId::Auto => Profile::Auto,
61 }
62}
63
64fn build_reasoner(ontology: &Ontology, id: ReasonerId) -> Result<Reasoner> {
65 let config = ReasonerConfig { explanations: true, ..ReasonerConfig::default() };
66 Reasoner::builder()
67 .profile(profile_for(id))
68 .config(config)
69 .build(ontology.clone())
70 .map_err(|e| ReasonerError::Classify(e.to_string()))
71}
72
73pub fn check_full_consistency(
75 ontology: &Ontology,
76 id: ReasonerId,
77 unsatisfiable: &[String],
78) -> Result<ConsistencyDetail> {
79 if cancel_requested() {
80 return Err(ReasonerError::Cancelled);
81 }
82 let mut reasoner = build_reasoner(ontology, id)?;
83 let facade = facade_check_consistency(&mut reasoner)
84 .map_err(|e| ReasonerError::Classify(e.to_string()))?;
85
86 let mut abox_clashes = Vec::new();
87 if matches!(id, ReasonerId::Rl | ReasonerId::Rdfs | ReasonerId::Auto | ReasonerId::El) {
88 let (clashes, _) = ontologos_rl::abox::collect_same_as_clashes(ontology);
89 abox_clashes.extend(clashes);
90 if let Ok(ok) = ontologos_rl::abox::is_abox_consistent(ontology) {
91 if !ok && abox_clashes.is_empty() {
92 abox_clashes.push("ABox is inconsistent (sameAs/differentFrom clash)".into());
93 }
94 }
95 }
96
97 let ontology_consistent = facade.complete && facade.consistent && abox_clashes.is_empty();
98 let consistent = ontology_consistent && unsatisfiable.is_empty();
99
100 Ok(ConsistencyDetail {
101 consistent,
102 complete: facade.complete,
103 ontology_consistent,
104 abox_clashes,
105 unsatisfiable: unsatisfiable.to_vec(),
106 })
107}
108
109pub fn check_instance(
111 ontology: &Ontology,
112 id: ReasonerId,
113 individual_iri: &str,
114 class_iri: &str,
115) -> Result<InstanceCheckResult> {
116 if cancel_requested() {
117 return Err(ReasonerError::Cancelled);
118 }
119 let started = Instant::now();
120 let mut reasoner = build_reasoner(ontology, id)?;
121 let entailed = is_entailed_axiom(
122 &mut reasoner,
123 EntailmentCheck::ClassAssertion {
124 individual: individual_iri.to_string(),
125 class: class_iri.to_string(),
126 },
127 )
128 .map_err(map_entailment_error)?;
129
130 Ok(InstanceCheckResult {
131 individual_iri: individual_iri.to_string(),
132 class_iri: class_iri.to_string(),
133 entailed,
134 profile_used: id.as_str().to_string(),
135 duration_ms: started.elapsed().as_millis() as u64,
136 })
137}
138
139fn map_entailment_error(e: impl std::fmt::Display) -> ReasonerError {
140 let msg = e.to_string();
141 if cancel_requested() || msg.to_ascii_lowercase().contains("cancel") {
142 ReasonerError::Cancelled
143 } else {
144 ReasonerError::Classify(msg)
145 }
146}
147
148fn named_individuals(ontology: &Ontology) -> Result<Vec<(EntityId, String)>> {
149 let mut out = Vec::new();
150 for (id, record) in ontology.entities().iter() {
151 if record.kind != EntityKind::Individual {
152 continue;
153 }
154 let iri = entity_iri(ontology, id).map_err(ReasonerError::Ontology)?;
155 if iri.starts_with("http://www.w3.org/2002/07/owl#")
156 || iri.starts_with("http://www.w3.org/1999/02/22-rdf-syntax-ns#")
157 {
158 continue;
159 }
160 out.push((id, iri));
161 }
162 out.sort_by(|a, b| a.1.cmp(&b.1));
163 Ok(out)
164}
165
166fn named_classes(ontology: &Ontology) -> Result<Vec<(EntityId, String)>> {
167 let mut out = Vec::new();
168 for (id, record) in ontology.entities().iter() {
169 if record.kind != EntityKind::Class && record.kind != EntityKind::ClassIndividual {
170 continue;
171 }
172 let iri = entity_iri(ontology, id).map_err(ReasonerError::Ontology)?;
173 if iri == "http://www.w3.org/2002/07/owl#Nothing"
174 || iri == "http://www.w3.org/2002/07/owl#Thing"
175 {
176 continue;
177 }
178 out.push((id, iri));
179 }
180 out.sort_by(|a, b| a.1.cmp(&b.1));
181 Ok(out)
182}
183
184fn most_specific_types(
185 ontology: &Ontology,
186 hierarchy_parents: &BTreeMap<String, Vec<String>>,
187 types: &[String],
188) -> Vec<String> {
189 let type_set: BTreeSet<&str> = types.iter().map(|s| s.as_str()).collect();
190 types
191 .iter()
192 .filter(|t| {
193 !types.iter().any(|other| {
195 if other == *t {
196 return false;
197 }
198 is_subclass_of(hierarchy_parents, other, t) || {
199 let Some(other_id) = ontology.lookup_entity(other) else {
200 return false;
201 };
202 let Some(t_id) = ontology.lookup_entity(t) else {
203 return false;
204 };
205 ontology.direct_superclasses(other_id).contains(&t_id)
206 && type_set.contains(other.as_str())
207 }
208 })
209 })
210 .cloned()
211 .collect()
212}
213
214fn is_subclass_of(parents: &BTreeMap<String, Vec<String>>, child: &str, ancestor: &str) -> bool {
215 if child == ancestor {
216 return false;
217 }
218 let mut stack = vec![child.to_string()];
219 let mut seen = BTreeSet::new();
220 while let Some(cur) = stack.pop() {
221 if !seen.insert(cur.clone()) {
222 continue;
223 }
224 let Some(supers) = parents.get(&cur) else {
225 continue;
226 };
227 for p in supers {
228 if p == ancestor {
229 return true;
230 }
231 stack.push(p.clone());
232 }
233 }
234 false
235}
236
237fn check_class_assertion(
238 reasoner: &mut Reasoner,
239 individual_iri: &str,
240 class_iri: &str,
241) -> Result<bool> {
242 if cancel_requested() {
243 return Err(ReasonerError::Cancelled);
244 }
245 match is_entailed_axiom(
246 reasoner,
247 EntailmentCheck::ClassAssertion {
248 individual: individual_iri.to_string(),
249 class: class_iri.to_string(),
250 },
251 ) {
252 Ok(v) => Ok(v),
253 Err(e) => {
254 if cancel_requested() || e.to_string().to_ascii_lowercase().contains("cancel") {
255 Err(ReasonerError::Cancelled)
256 } else {
257 Err(ReasonerError::Classify(e.to_string()))
259 }
260 }
261 }
262}
263
264pub fn realize(ontology: &Ontology, id: ReasonerId) -> Result<RealizationResult> {
266 if cancel_requested() {
267 return Err(ReasonerError::Cancelled);
268 }
269 let started = Instant::now();
270 let mut reasoner = build_reasoner(ontology, id)?;
271 let mut individuals = named_individuals(ontology)?;
272 let classes = named_classes(ontology)?;
273
274 let mut truncated = false;
275 if individuals.len() > MAX_REALIZE_INDIVIDUALS {
276 individuals.truncate(MAX_REALIZE_INDIVIDUALS);
277 truncated = true;
278 }
279
280 let mut parents: BTreeMap<String, Vec<String>> = BTreeMap::new();
282 for (class_id, class_iri) in &classes {
283 for &sup in ontology.direct_superclasses(*class_id) {
284 if let Ok(sup_iri) = entity_iri(ontology, sup) {
285 parents.entry(class_iri.clone()).or_default().push(sup_iri);
286 }
287 }
288 }
289
290 let mut entries = Vec::new();
291 let mut entailment_checks = 0usize;
292 let mut entailment_errors = 0usize;
293 'individuals: for (ind_id, ind_iri) in &individuals {
294 if cancel_requested() {
295 return Err(ReasonerError::Cancelled);
296 }
297 let asserted: Vec<String> = ontology
298 .classes_of(*ind_id)
299 .iter()
300 .filter_map(|cid| entity_iri(ontology, *cid).ok())
301 .filter(|iri| {
302 iri != "http://www.w3.org/2002/07/owl#Thing"
303 && iri != "http://www.w3.org/2002/07/owl#NamedIndividual"
304 })
305 .collect();
306
307 let mut types = BTreeSet::new();
308 for a in &asserted {
309 types.insert(a.clone());
310 }
311
312 let mut candidates: BTreeSet<String> = asserted.iter().cloned().collect();
313 for (_, class_iri) in &classes {
314 candidates.insert(class_iri.clone());
315 }
316
317 for class_iri in &candidates {
318 if types.contains(class_iri) {
319 continue;
320 }
321 if entailment_checks >= MAX_REALIZE_ENTAILMENT_CHECKS {
322 truncated = true;
323 break 'individuals;
324 }
325 entailment_checks += 1;
326 match check_class_assertion(&mut reasoner, ind_iri, class_iri) {
327 Ok(true) => {
328 types.insert(class_iri.clone());
329 }
330 Ok(false) => {}
331 Err(ReasonerError::Cancelled) => return Err(ReasonerError::Cancelled),
332 Err(_) => {
334 entailment_errors += 1;
335 truncated = true;
336 }
337 }
338 }
339
340 let type_list: Vec<String> = types.into_iter().collect();
341 let most_specific = most_specific_types(ontology, &parents, &type_list);
342 let inferred: Vec<String> =
343 type_list.iter().filter(|t| !asserted.contains(t)).cloned().collect();
344
345 entries.push(RealizationEntry {
346 individual_iri: ind_iri.clone(),
347 types: type_list,
348 most_specific,
349 asserted,
350 inferred,
351 });
352
353 if entailment_checks >= MAX_REALIZE_ENTAILMENT_CHECKS {
354 truncated = true;
355 break;
356 }
357 }
358
359 Ok(RealizationResult {
360 profile_used: id.as_str().to_string(),
361 individuals: entries,
362 duration_ms: started.elapsed().as_millis() as u64,
363 truncated,
364 entailment_errors,
365 })
366}
367
368pub fn inferred_assertions_from_realization(
370 ontology: &Ontology,
371 id: ReasonerId,
372 realization: &RealizationResult,
373) -> Result<InferredAssertions> {
374 if cancel_requested() {
375 return Err(ReasonerError::Cancelled);
376 }
377
378 let mut class_assertions = Vec::new();
379 for entry in &realization.individuals {
380 for class_iri in &entry.inferred {
381 class_assertions.push(crate::result::InferredClassAssertion {
382 individual_iri: entry.individual_iri.clone(),
383 class_iri: class_iri.clone(),
384 });
385 }
386 }
387
388 let asserted_ops: BTreeSet<(String, String, String)> = {
389 let mut set = BTreeSet::new();
390 let inds = named_individuals(ontology)?;
391 for (subj_id, subj_iri) in &inds {
392 for &(prop_id, obj_id) in ontology.object_assertions_of(*subj_id) {
393 let Ok(prop_iri) = entity_iri(ontology, prop_id) else {
394 continue;
395 };
396 let Ok(obj_iri) = entity_iri(ontology, obj_id) else {
397 continue;
398 };
399 set.insert((subj_iri.clone(), prop_iri, obj_iri));
400 }
401 }
402 set
403 };
404
405 let mut object_property_assertions = Vec::new();
406 let mut working = ontology.clone();
407 if matches!(
409 id,
410 ReasonerId::Rl | ReasonerId::Rdfs | ReasonerId::Auto | ReasonerId::El | ReasonerId::Dl
411 ) {
412 let _ = ontologos_rl::abox::materialize_abox(&mut working);
413 }
414 if cancel_requested() {
415 return Err(ReasonerError::Cancelled);
416 }
417
418 let individuals = named_individuals(&working)?;
419 let mut props = Vec::new();
420 for (pid, record) in working.entities().iter() {
421 if record.kind == EntityKind::ObjectProperty {
422 if let Ok(iri) = entity_iri(&working, pid) {
423 props.push((pid, iri));
424 }
425 }
426 }
427 for (subj_id, subj_iri) in &individuals {
428 if cancel_requested() {
429 return Err(ReasonerError::Cancelled);
430 }
431 for (prop_id, prop_iri) in &props {
432 let Ok(objs) =
433 ontologos_rl::abox::object_property_values(&mut working, *subj_id, *prop_id)
434 else {
435 continue;
436 };
437 for obj_id in objs {
438 let Ok(obj_iri) = entity_iri(&working, obj_id) else {
439 continue;
440 };
441 if !asserted_ops.contains(&(subj_iri.clone(), prop_iri.clone(), obj_iri.clone())) {
442 object_property_assertions.push(
443 crate::result::InferredObjectPropertyAssertion {
444 subject_iri: subj_iri.clone(),
445 property_iri: prop_iri.clone(),
446 object_iri: obj_iri,
447 },
448 );
449 }
450 }
451 }
452 }
453
454 let closure = ontologos_rl::abox::same_as_closure(ontology);
455 let mut same_as_clusters = Vec::new();
456 for cluster in &closure.clusters {
457 if cluster.len() < 2 {
458 continue;
459 }
460 let mut iris: Vec<String> =
461 cluster.iter().filter_map(|id| entity_iri(ontology, *id).ok()).collect();
462 iris.sort();
463 iris.dedup();
464 if iris.len() >= 2 {
465 same_as_clusters.push(SameAsCluster { individuals: iris });
466 }
467 }
468
469 Ok(InferredAssertions { class_assertions, object_property_assertions, same_as_clusters })
470}
471
472pub fn inferred_assertions(ontology: &Ontology, id: ReasonerId) -> Result<InferredAssertions> {
474 let realization = realize(ontology, id)?;
475 inferred_assertions_from_realization(ontology, id, &realization)
476}
477
478#[cfg(test)]
479mod tests {
480 use super::*;
481
482 #[test]
483 fn cancel_requested_aborts_realize() {
484 let flag = Arc::new(AtomicBool::new(true));
485 install_cancel_flag(Arc::clone(&flag));
486 let ontology = Ontology::new();
487 let err = realize(&ontology, ReasonerId::El).expect_err("cancelled");
488 clear_cancel_flag();
489 assert!(matches!(err, ReasonerError::Cancelled));
490 }
491}