Skip to main content

ontologos_query/
lib.rs

1//! Query interface over classified ontologies.
2//!
3//! # Example
4//!
5//! ```
6//! use ontologos_core::{EntityId, Ontology, Taxonomy};
7//! use ontologos_query::QueryEngine;
8//!
9//! let ontology = Ontology::default();
10//! let taxonomy = Taxonomy::default();
11//! let engine = QueryEngine::new(&ontology, &taxonomy);
12//! assert!(engine.unsatisfiable_classes().is_empty());
13//! ```
14
15#![warn(missing_docs)]
16
17use ontologos_core::{EntityId, Ontology, Taxonomy};
18use thiserror::Error;
19
20/// Result type for query operations.
21pub type Result<T> = std::result::Result<T, Error>;
22
23/// Query errors.
24#[derive(Debug, Error)]
25pub enum Error {
26    /// Entity id is missing or not a class.
27    #[error("unknown entity {0:?}")]
28    UnknownEntity(EntityId),
29    /// Core ontology error.
30    #[error(transparent)]
31    Core(#[from] ontologos_core::Error),
32}
33
34/// Query handle over a classified ontology taxonomy.
35#[derive(Debug)]
36pub struct QueryEngine<'a> {
37    ontology: &'a Ontology,
38    taxonomy: &'a Taxonomy,
39}
40
41impl<'a> QueryEngine<'a> {
42    /// Create a query engine over `ontology` and its `taxonomy`.
43    #[must_use]
44    pub fn new(ontology: &'a Ontology, taxonomy: &'a Taxonomy) -> Self {
45        Self { ontology, taxonomy }
46    }
47
48    /// Return direct subclasses of the given class in the reduced taxonomy.
49    pub fn direct_subclasses(&self, class: EntityId) -> Result<Vec<EntityId>> {
50        self.ensure_class(class)?;
51        Ok(self.taxonomy.direct_subclasses(class))
52    }
53
54    /// Return direct superclasses of the given class in the reduced taxonomy.
55    pub fn direct_superclasses(&self, class: EntityId) -> Result<Vec<EntityId>> {
56        self.ensure_class(class)?;
57        Ok(self.taxonomy.direct_superclasses(class))
58    }
59
60    /// Whether `sub` is subsumed by `sup` in the taxonomy.
61    pub fn is_subsumed(&self, sub: EntityId, sup: EntityId) -> Result<bool> {
62        self.ensure_class(sub)?;
63        self.ensure_class(sup)?;
64        Ok(self.taxonomy.is_subsumed(sub, sup))
65    }
66
67    /// Return the equivalence cluster containing `class`, if any.
68    pub fn equivalent_classes(&self, class: EntityId) -> Result<Option<Vec<EntityId>>> {
69        self.ensure_class(class)?;
70        Ok(self
71            .taxonomy
72            .equivalent_classes(class)
73            .map(<[EntityId]>::to_vec))
74    }
75
76    /// List classes inferred unsatisfiable (⊥).
77    pub fn unsatisfiable_classes(&self) -> Vec<EntityId> {
78        self.taxonomy.unsatisfiable.clone()
79    }
80
81    /// Resolve an entity IRI to its id.
82    pub fn lookup(&self, iri: &str) -> Option<EntityId> {
83        self.ontology.lookup_entity(iri)
84    }
85
86    fn ensure_class(&self, class: EntityId) -> Result<()> {
87        let record = self.ontology.entity(class)?;
88        if record.kind != ontologos_core::EntityKind::Class {
89            return Err(Error::UnknownEntity(class));
90        }
91        Ok(())
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use ontologos_core::{Axiom, EntityKind, Ontology};
98
99    use super::*;
100
101    #[test]
102    fn query_direct_subclasses_from_taxonomy() {
103        let mut ontology = Ontology::new();
104        let a = ontology
105            .entity_id("http://ex.org/A", EntityKind::Class)
106            .unwrap();
107        let b = ontology
108            .entity_id("http://ex.org/B", EntityKind::Class)
109            .unwrap();
110        ontology
111            .add_axiom(Axiom::SubClassOf {
112                subclass: a,
113                superclass: b,
114            })
115            .unwrap();
116
117        let taxonomy = ontologos_el::ElClassifier::new()
118            .classify(&ontology)
119            .expect("classify");
120        let engine = QueryEngine::new(&ontology, &taxonomy);
121        let subs = engine.direct_subclasses(b).expect("subs");
122        assert!(subs.contains(&a));
123    }
124
125    #[test]
126    fn is_subsumed_transitive() {
127        let mut ontology = Ontology::new();
128        let a = ontology
129            .entity_id("http://ex.org/A", EntityKind::Class)
130            .unwrap();
131        let b = ontology
132            .entity_id("http://ex.org/B", EntityKind::Class)
133            .unwrap();
134        let c = ontology
135            .entity_id("http://ex.org/C", EntityKind::Class)
136            .unwrap();
137        ontology
138            .add_axiom(Axiom::SubClassOf {
139                subclass: a,
140                superclass: b,
141            })
142            .unwrap();
143        ontology
144            .add_axiom(Axiom::SubClassOf {
145                subclass: b,
146                superclass: c,
147            })
148            .unwrap();
149
150        let taxonomy = ontologos_el::ElClassifier::new()
151            .classify(&ontology)
152            .expect("classify");
153        let engine = QueryEngine::new(&ontology, &taxonomy);
154        assert!(engine.is_subsumed(a, c).expect("subsumed"));
155    }
156}