strixonomy_reasoner/
runner.rs1use crate::adapter::{ReasonerAdapter, ReasonerId};
2use crate::auto::AutoAdapter;
3use crate::dl::DlAdapter;
4use crate::el::ElAdapter;
5use crate::error::{ReasonerError, Result};
6use crate::input::ReasonerInput;
7use crate::rdfs::RdfsAdapter;
8use crate::result::{
9 ClassificationResult, ConsistencyResult, ExplanationRequest, ExplanationResult,
10 InferredAssertions, InstanceCheckResult, RealizationResult,
11};
12use crate::rl::RlAdapter;
13use ontologos_profile::scanner::scan_constructs;
14
15pub fn adapter_for(id: ReasonerId) -> Result<Box<dyn ReasonerAdapter>> {
16 let adapter: Box<dyn ReasonerAdapter> = match id {
17 ReasonerId::El => Box::new(ElAdapter),
18 ReasonerId::Rl => Box::new(RlAdapter),
19 ReasonerId::Rdfs => Box::new(RdfsAdapter),
20 ReasonerId::Dl => Box::new(DlAdapter),
21 ReasonerId::Auto => Box::new(AutoAdapter),
22 };
23 Ok(adapter)
24}
25
26pub fn classify(
27 profile: ReasonerId,
28 input: &ReasonerInput,
29 auto_detect: bool,
30) -> Result<ClassificationResult> {
31 let mut warnings = profile_warnings(&input.ontology, profile, auto_detect)?;
32 if auto_detect {
33 if let Some(suggested) = suggest_profile(&input.ontology) {
34 if suggested != profile.as_str() {
35 warnings.push(crate::result::ReasonerWarning {
36 code: "profile_suggestion".to_string(),
37 message: format!(
38 "ontology may be better suited to profile '{suggested}' (selected: {})",
39 profile.as_str()
40 ),
41 suggested_profile: Some(suggested),
42 });
43 }
44 }
45 }
46 let adapter = adapter_for(profile)?;
47 let swrl_present = crate::swrl_run::input_has_swrl_rules(input);
48 let mut used_swrl = false;
49 let mut result = if swrl_present && matches!(profile, ReasonerId::Dl | ReasonerId::Auto) {
50 match crate::swrl_run::classify_with_swrl(input) {
51 Ok(r) => {
52 used_swrl = true;
53 r
54 }
55 Err(e) => {
56 warnings.push(crate::result::ReasonerWarning {
57 code: "swrl_classify_failed".to_string(),
58 message: format!(
59 "SWRL-aware classify failed ({e}); falling back to {profile:?} without SWRL materialization"
60 ),
61 suggested_profile: None,
62 });
63 adapter.classify(input)?
64 }
65 }
66 } else {
67 if swrl_present {
68 warnings.push(crate::result::ReasonerWarning {
70 code: "swrl_skipped_for_profile".to_string(),
71 message: format!(
72 "SWRL rules are present but profile '{}' does not run SWRL materialization; use profile 'dl' or 'auto'",
73 profile.as_str()
74 ),
75 suggested_profile: Some("dl".to_string()),
76 });
77 }
78 adapter.classify(input)?
79 };
80 if !used_swrl {
83 if let Ok(detail) =
84 crate::abox::check_full_consistency(&input.ontology, profile, &result.unsatisfiable)
85 {
86 result.consistent = detail.consistent;
87 if !detail.abox_clashes.is_empty() {
88 for clash in &detail.abox_clashes {
89 warnings.push(crate::result::ReasonerWarning {
90 code: "abox_clash".to_string(),
91 message: clash.clone(),
92 suggested_profile: None,
93 });
94 }
95 }
96 if !detail.complete {
97 warnings.push(crate::result::ReasonerWarning {
98 code: "consistency_incomplete".to_string(),
99 message: "consistency check did not complete (budget or cancel)".into(),
100 suggested_profile: None,
101 });
102 }
103 }
104 }
105 result.warnings.extend(warnings);
106 Ok(result)
107}
108
109pub fn check_consistency(profile: ReasonerId, input: &ReasonerInput) -> Result<ConsistencyResult> {
110 let adapter = adapter_for(profile)?;
111 adapter.check_consistency(input)
112}
113
114pub fn realize(profile: ReasonerId, input: &ReasonerInput) -> Result<RealizationResult> {
115 let adapter = adapter_for(profile)?;
116 adapter.realize(input)
117}
118
119pub fn check_instance(
120 profile: ReasonerId,
121 input: &ReasonerInput,
122 individual_iri: &str,
123 class_iri: &str,
124) -> Result<InstanceCheckResult> {
125 let adapter = adapter_for(profile)?;
126 adapter.check_instance(input, individual_iri, class_iri)
127}
128
129pub fn inferred_assertions(
130 profile: ReasonerId,
131 input: &ReasonerInput,
132) -> Result<InferredAssertions> {
133 let adapter = adapter_for(profile)?;
134 adapter.inferred_assertions(input)
135}
136
137pub fn explain(
138 profile: ReasonerId,
139 input: &ReasonerInput,
140 request: &ExplanationRequest,
141) -> Result<ExplanationResult> {
142 let adapter = adapter_for(profile)?;
143 adapter.explain(input, request)
144}
145
146pub fn explain_alternatives(
147 profile: ReasonerId,
148 input: &ReasonerInput,
149 request: &ExplanationRequest,
150 max_justifications: usize,
151) -> Result<Vec<ExplanationResult>> {
152 crate::explain::explain_unsatisfiable_alternatives(
153 profile,
154 &input.ontology,
155 &request.class_iri,
156 max_justifications,
157 )
158}
159
160fn profile_warnings(
161 ontology: &ontologos_core::Ontology,
162 profile: ReasonerId,
163 auto_detect: bool,
164) -> Result<Vec<crate::result::ReasonerWarning>> {
165 if !auto_detect {
166 return Ok(Vec::new());
167 }
168 let report = ontologos_profile::detect_profile(ontology)
169 .map_err(|e| ReasonerError::Classify(e.to_string()))?;
170 let mut warnings = Vec::new();
171 for diag in report.diagnostics {
172 warnings.push(crate::result::ReasonerWarning {
173 code: "profile_construct".to_string(),
174 message: diag.message,
175 suggested_profile: report.detected.map(|p| format!("{p:?}").to_ascii_lowercase()),
176 });
177 }
178 if profile == ReasonerId::El {
179 let constructs = scan_constructs(ontology);
180 for diag in ontologos_profile::el_diagnostics(&constructs) {
181 warnings.push(crate::result::ReasonerWarning {
182 code: "el_construct".to_string(),
183 message: diag.message,
184 suggested_profile: Some("el".to_string()),
185 });
186 }
187 }
188 Ok(warnings)
189}
190
191fn suggest_profile(ontology: &ontologos_core::Ontology) -> Option<String> {
192 let report = ontologos_profile::detect_profile(ontology).ok()?;
193 report.detected.map(|p| match p {
194 ontologos_profile::OwlProfile::El => "el".to_string(),
195 ontologos_profile::OwlProfile::Rl => "rl".to_string(),
196 ontologos_profile::OwlProfile::Ql => "el".to_string(),
197 ontologos_profile::OwlProfile::Dl => "dl".to_string(),
198 })
199}