1#![warn(missing_docs)]
18
19mod graph;
20mod normal_form;
21mod reasoner;
22mod route;
23mod taxonomy_extract;
24
25use ontologos_core::{Ontology, Taxonomy};
26use thiserror::Error;
27
28pub use reasoner::{classify_reasoner, try_classify_reasoner};
29pub use route::{classify_with_profile, resolve_profile_flag, ClassifyOutcome, ProfileFlag};
30
31pub type Result<T> = std::result::Result<T, Error>;
33
34#[derive(Debug, Error)]
36pub enum Error {
37 #[error("expected profile {expected:?}, got {actual:?}")]
39 WrongProfile {
40 expected: ontologos_core::Profile,
42 actual: ontologos_core::Profile,
44 },
45 #[error("ontology is not in OWL EL profile (detected {detected:?})")]
47 NonElProfile {
48 detected: ontologos_profile::OwlProfile,
50 },
51 #[error("profile detection failed: {0}")]
53 Profile(String),
54 #[error("classification not supported for profile {0:?}")]
56 UnsupportedProfile(ontologos_profile::OwlProfile),
57 #[error(transparent)]
59 Core(#[from] ontologos_core::Error),
60 #[error(transparent)]
62 Rdfs(#[from] ontologos_rdfs::Error),
63 #[error(transparent)]
65 Rl(#[from] ontologos_rl::Error),
66}
67
68#[derive(Debug, Default)]
70pub struct ElClassifier;
71
72impl ElClassifier {
73 #[must_use]
75 pub fn new() -> Self {
76 Self
77 }
78
79 pub fn classify(&self, ontology: &Ontology) -> Result<Taxonomy> {
84 normal_form::validate_el_profile(ontology)?;
85 let mut graph = graph::CompletionGraph::seed(ontology);
86 graph.saturate();
87 Ok(taxonomy_extract::extract_taxonomy(ontology, &graph))
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use ontologos_core::{Axiom, EntityKind, Ontology};
94
95 use super::*;
96
97 fn class(ontology: &mut Ontology, iri: &str) -> ontologos_core::EntityId {
98 ontology.entity_id(iri, EntityKind::Class).expect("class")
99 }
100
101 #[test]
102 fn transitive_subclass_chain() {
103 let mut ontology = Ontology::new();
104 let a = class(&mut ontology, "http://ex.org/A");
105 let b = class(&mut ontology, "http://ex.org/B");
106 let c = class(&mut ontology, "http://ex.org/C");
107 ontology
108 .add_axiom(Axiom::SubClassOf {
109 subclass: a,
110 superclass: b,
111 })
112 .unwrap();
113 ontology
114 .add_axiom(Axiom::SubClassOf {
115 subclass: b,
116 superclass: c,
117 })
118 .unwrap();
119
120 let taxonomy = ElClassifier::new().classify(&ontology).unwrap();
121 assert!(taxonomy.is_subsumed(a, c));
122 assert!(taxonomy.direct_superclasses(a).contains(&b));
123 assert!(taxonomy.direct_superclasses(a).contains(&c) || taxonomy.is_subsumed(a, c));
124 }
125
126 #[test]
127 fn existential_filler_subsumption_in_taxonomy() {
128 let mut ontology = Ontology::new();
129 let a = class(&mut ontology, "http://ex.org/A");
130 let b = class(&mut ontology, "http://ex.org/B");
131 let c = class(&mut ontology, "http://ex.org/C");
132 ontology
133 .add_axiom(Axiom::SubClassOf {
134 subclass: a,
135 superclass: b,
136 })
137 .unwrap();
138 ontology
139 .add_axiom(Axiom::SubClassOf {
140 subclass: b,
141 superclass: c,
142 })
143 .unwrap();
144
145 let taxonomy = ElClassifier::new().classify(&ontology).unwrap();
146 assert!(taxonomy.is_subsumed(a, c));
147 assert!(taxonomy.is_subsumed(a, b));
148 }
149
150 #[test]
151 fn equivalent_classes_cluster() {
152 let mut ontology = Ontology::new();
153 let a = class(&mut ontology, "http://ex.org/A");
154 let b = class(&mut ontology, "http://ex.org/B");
155 ontology
156 .add_axiom(Axiom::EquivalentClasses(vec![a, b]))
157 .unwrap();
158
159 let taxonomy = ElClassifier::new().classify(&ontology).unwrap();
160 assert!(
161 taxonomy.equivalent_classes(a).is_some()
162 || taxonomy.is_subsumed(a, b) && taxonomy.is_subsumed(b, a)
163 );
164 }
165}