1use crate::error::{ReasonerError, Result};
4use crate::input::ReasonerInput;
5use crate::result::{
6 build_inferred_hierarchy, new_inferences, taxonomy_equivalence_clusters, taxonomy_to_iri_edges,
7 unsatisfiable_iris, ClassificationResult, ReasonerWarning,
8};
9use ontologos_core::{EntityKind, Ontology, SwrlAtom, SwrlDArg, SwrlIArg, SwrlRule};
10use std::time::Instant;
11
12const SWRL_RULE_PRED: &str = "http://ontocode.dev/ns#swrlRule";
13
14pub fn classify_with_swrl(input: &ReasonerInput) -> Result<ClassificationResult> {
16 let started = Instant::now();
17 let (ontology, mut warnings) = prepare_swrl_ontology(input)?;
18 let (taxonomy, _) = ontologos_swrl::classify_with_swrl(&ontology)
19 .map_err(|e| ReasonerError::Classify(format!("SWRL classify: {e}")))?;
20
21 let iri_edges = taxonomy_to_iri_edges(&ontology, &taxonomy).map_err(ReasonerError::Classify)?;
22 let equivalences =
23 taxonomy_equivalence_clusters(&ontology, &taxonomy).map_err(ReasonerError::Classify)?;
24 let reported = unsatisfiable_iris(&ontology, &taxonomy).map_err(ReasonerError::Classify)?;
25 let inferred = build_inferred_hierarchy(&iri_edges, &reported, &input.asserted_hierarchy);
26 let unsatisfiable = inferred.unsatisfiable.clone();
27 let new_inferences = new_inferences(&input.asserted_hierarchy, &inferred.edges);
28
29 let mut consistent = unsatisfiable.is_empty();
31 if let Ok(detail) = crate::abox::check_full_consistency(
32 &ontology,
33 crate::adapter::ReasonerId::Dl,
34 &unsatisfiable,
35 ) {
36 consistent = detail.consistent;
37 for clash in &detail.abox_clashes {
38 warnings.push(ReasonerWarning {
39 code: "abox_clash".into(),
40 message: clash.clone(),
41 suggested_profile: None,
42 });
43 }
44 if !detail.complete {
45 warnings.push(ReasonerWarning {
46 code: "consistency_incomplete".into(),
47 message: "consistency check did not complete (budget or cancel)".into(),
48 suggested_profile: None,
49 });
50 }
51 }
52
53 Ok(ClassificationResult {
54 profile_used: "swrl".to_string(),
55 consistent,
56 unsatisfiable,
57 inferred,
58 new_inferences,
59 warnings,
60 duration_ms: started.elapsed().as_millis() as u64,
61 subsumption_count: taxonomy.subsumption_count(),
62 inferred_axiom_count: taxonomy.subsumption_count(),
63 equivalences,
64 })
65}
66
67pub fn prepare_swrl_ontology(input: &ReasonerInput) -> Result<(Ontology, Vec<ReasonerWarning>)> {
71 let mut ontology = input.ontology.clone();
72 let mut warnings = Vec::new();
73 if ontology.swrl_rules().is_empty() {
76 let (authored, inject_warnings) = inject_authored_swrl_from_input(&mut ontology, input);
77 warnings.extend(inject_warnings);
78 if authored > 0 {
79 warnings.push(ReasonerWarning {
80 code: "swrl_authored_injected".into(),
81 message: format!(
82 "injected {authored} Strixonomy-authored SWRL rule(s) from strixonomy:swrlRule annotations"
83 ),
84 suggested_profile: None,
85 });
86 }
87 }
88 let report = ontologos_swrl::materialize_swrl_rules(&mut ontology)
89 .map_err(|e| ReasonerError::Classify(format!("SWRL materialization: {e}")))?;
90 if report.inferences_added > 0 || report.rules_found > 0 {
91 warnings.push(ReasonerWarning {
92 code: "swrl_materialized".into(),
93 message: format!(
94 "SWRL: {} rule(s), {} inferred axiom(s) materialized",
95 report.rules_found, report.inferences_added
96 ),
97 suggested_profile: None,
98 });
99 }
100 Ok((ontology, warnings))
101}
102
103pub fn ontology_has_swrl_rules(ontology: &Ontology) -> bool {
105 !ontology.swrl_rules().is_empty()
106}
107
108pub fn input_has_swrl_rules(input: &ReasonerInput) -> bool {
110 ontology_has_swrl_rules(&input.ontology) || input_mentions_authored_swrl(input)
111}
112
113fn input_mentions_authored_swrl(input: &ReasonerInput) -> bool {
114 input.document_overrides.values().any(|t| t.contains(SWRL_RULE_PRED))
115}
116
117pub fn inject_authored_swrl_from_input(
119 ontology: &mut Ontology,
120 input: &ReasonerInput,
121) -> (usize, Vec<ReasonerWarning>) {
122 let mut injected = 0usize;
123 let mut warnings = Vec::new();
124 for text in input.document_overrides.values() {
125 let (n, w) = inject_swrl_from_turtle(ontology, text);
126 injected += n;
127 warnings.extend(w);
128 }
129 (injected, warnings)
130}
131
132pub fn inject_swrl_from_turtle(
137 ontology: &mut Ontology,
138 text: &str,
139) -> (usize, Vec<ReasonerWarning>) {
140 let mut injected = 0usize;
141 let mut warnings = Vec::new();
142 for rule in strixonomy_swrl::rules_from_turtle_document(text) {
143 if !rule.enabled {
144 continue;
145 }
146 let rule_id = rule.id.as_deref().unwrap_or("<anonymous>");
147 match convert_authored_rule(ontology, &rule) {
148 Ok(converted) => {
149 if ontology.push_swrl_rule(converted).is_ok() {
150 injected += 1;
151 }
152 }
153 Err(e) => {
154 warnings.push(ReasonerWarning {
155 code: "swrl_rule_skipped".into(),
156 message: format!("enabled SWRL rule {rule_id} was not injected: {e}"),
157 suggested_profile: None,
158 });
159 }
160 }
161 }
162 (injected, warnings)
163}
164
165fn convert_authored_rule(
166 ontology: &mut Ontology,
167 rule: &strixonomy_swrl::SwrlRule,
168) -> Result<SwrlRule> {
169 let mut body = Vec::new();
170 for atom in &rule.body {
171 body.push(convert_atom(ontology, atom)?);
172 }
173 let mut head = Vec::new();
174 for atom in &rule.head {
175 head.push(convert_atom(ontology, atom)?);
176 }
177 Ok(SwrlRule { body, head })
178}
179
180fn convert_atom(ontology: &mut Ontology, atom: &strixonomy_swrl::SwrlAtom) -> Result<SwrlAtom> {
181 use strixonomy_swrl::SwrlAtom as A;
182 match atom {
183 A::Class { class, arg } => Ok(SwrlAtom::Class {
184 class: ontology
185 .entity_id(class, EntityKind::Class)
186 .map_err(|e| ReasonerError::Classify(e.to_string()))?,
187 arg: convert_iarg(ontology, arg)?,
188 }),
189 A::ObjectProperty { property, subject, object } => Ok(SwrlAtom::ObjectProperty {
190 property: ontology
191 .entity_id(property, EntityKind::ObjectProperty)
192 .map_err(|e| ReasonerError::Classify(e.to_string()))?,
193 subject: convert_iarg(ontology, subject)?,
194 object: convert_iarg(ontology, object)?,
195 }),
196 A::DataProperty { property, subject, value } => Ok(SwrlAtom::DataProperty {
197 property: ontology
198 .entity_id(property, EntityKind::DataProperty)
199 .map_err(|e| ReasonerError::Classify(e.to_string()))?,
200 subject: convert_iarg(ontology, subject)?,
201 value: convert_darg(ontology, value)?,
202 }),
203 A::SameIndividual { left, right } => Ok(SwrlAtom::SameIndividual(
204 convert_iarg(ontology, left)?,
205 convert_iarg(ontology, right)?,
206 )),
207 A::DifferentIndividuals { left, right } => Ok(SwrlAtom::DifferentIndividuals(
208 convert_iarg(ontology, left)?,
209 convert_iarg(ontology, right)?,
210 )),
211 A::BuiltIn { .. } => Err(ReasonerError::Classify(
212 "BuiltIn SWRL atoms are not executable via Ontologos materialization".into(),
213 )),
214 A::DataRange { .. } => Err(ReasonerError::Classify(
215 "DataRange SWRL atoms are not mapped for Ontologos injection".into(),
216 )),
217 }
218}
219
220fn convert_iarg(ontology: &mut Ontology, arg: &strixonomy_swrl::SwrlIArg) -> Result<SwrlIArg> {
221 match arg {
222 strixonomy_swrl::SwrlIArg::Variable(v) => Ok(SwrlIArg::Variable(v.clone())),
223 strixonomy_swrl::SwrlIArg::Individual(iri) => Ok(SwrlIArg::Individual(
224 ontology
225 .entity_id(iri, EntityKind::Individual)
226 .map_err(|e| ReasonerError::Classify(e.to_string()))?,
227 )),
228 }
229}
230
231fn convert_darg(ontology: &mut Ontology, arg: &strixonomy_swrl::SwrlDArg) -> Result<SwrlDArg> {
232 match arg {
233 strixonomy_swrl::SwrlDArg::Variable(v) => Ok(SwrlDArg::Variable(v.clone())),
234 strixonomy_swrl::SwrlDArg::Literal { lexical, datatype } => {
235 let datatype = match datatype {
236 Some(iri) => Some(
237 ontology
238 .entity_id(iri, EntityKind::Datatype)
239 .map_err(|e| ReasonerError::Classify(e.to_string()))?,
240 ),
241 None => None,
242 };
243 Ok(SwrlDArg::Literal { lexical: lexical.clone(), datatype })
244 }
245 }
246}