Skip to main content

ontologos_core/
reasoner.rs

1use crate::error::{Error, Result};
2use crate::ontology::Ontology;
3
4const MAX_PARALLELISM: usize = 64;
5
6/// OWL profile selected for reasoning.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8pub enum Profile {
9    /// Detect the most specific supported profile automatically.
10    #[default]
11    Auto,
12    /// RDFS reasoning only.
13    Rdfs,
14    /// OWL RL rule-based reasoning.
15    Rl,
16    /// OWL EL completion-based classification.
17    El,
18}
19
20/// Configuration options for the reasoner builder.
21#[derive(Debug, Clone)]
22pub struct ReasonerConfig {
23    /// Enable incremental re-classification when axioms change.
24    pub incremental: bool,
25    /// Record explanations for inferences.
26    pub explanations: bool,
27    /// Number of threads for parallel rule execution.
28    pub parallelism: usize,
29}
30
31impl Default for ReasonerConfig {
32    fn default() -> Self {
33        Self {
34            incremental: false,
35            explanations: false,
36            parallelism: 1,
37        }
38    }
39}
40
41/// Builder for constructing a configured reasoner instance.
42#[derive(Debug, Default)]
43pub struct ReasonerBuilder {
44    profile: Profile,
45    config: ReasonerConfig,
46}
47
48impl ReasonerBuilder {
49    /// Create a builder with default configuration.
50    #[must_use]
51    pub fn new() -> Self {
52        Self::default()
53    }
54
55    /// Set the OWL profile for reasoning.
56    #[must_use]
57    pub fn profile(mut self, profile: Profile) -> Self {
58        self.profile = profile;
59        self
60    }
61
62    /// Set reasoner configuration options.
63    #[must_use]
64    pub fn config(mut self, config: ReasonerConfig) -> Self {
65        self.config = config;
66        self
67    }
68
69    /// Build a reasoner over the given ontology.
70    pub fn build(self, ontology: Ontology) -> Result<Reasoner> {
71        if self.config.parallelism == 0 || self.config.parallelism > MAX_PARALLELISM {
72            return Err(Error::Message(format!(
73                "parallelism must be in 1..={MAX_PARALLELISM}, got {}",
74                self.config.parallelism
75            )));
76        }
77        Ok(Reasoner {
78            ontology,
79            profile: self.profile,
80            config: self.config,
81            session: None,
82        })
83    }
84}
85
86/// Main reasoner facade over profile-specific engines.
87#[derive(Debug)]
88pub struct Reasoner {
89    ontology: Ontology,
90    profile: Profile,
91    config: ReasonerConfig,
92    session: Option<Box<dyn crate::session::ReasonerSession>>,
93}
94
95impl Reasoner {
96    /// Create a new reasoner builder.
97    #[must_use]
98    pub fn builder() -> ReasonerBuilder {
99        ReasonerBuilder::new()
100    }
101
102    /// The configured OWL profile.
103    #[must_use]
104    pub fn profile(&self) -> Profile {
105        self.profile
106    }
107
108    /// The reasoner configuration.
109    #[must_use]
110    pub fn config(&self) -> &ReasonerConfig {
111        &self.config
112    }
113
114    /// Borrow the loaded ontology.
115    #[must_use]
116    pub fn ontology(&self) -> &Ontology {
117        &self.ontology
118    }
119
120    /// Mutably borrow the loaded ontology (e.g. for RDFS materialization).
121    pub fn ontology_mut(&mut self) -> &mut Ontology {
122        &mut self.ontology
123    }
124
125    /// Borrow incremental session state, if any.
126    #[must_use]
127    pub fn session(&self) -> Option<&dyn crate::session::ReasonerSession> {
128        self.session.as_deref()
129    }
130
131    /// Mutably borrow incremental session state.
132    pub fn session_mut(&mut self) -> Option<&mut dyn crate::session::ReasonerSession> {
133        self.session.as_deref_mut()
134    }
135
136    /// Take session state out of the reasoner (for facade downcasting).
137    pub fn take_session(&mut self) -> Option<Box<dyn crate::session::ReasonerSession>> {
138        self.session.take()
139    }
140
141    /// Install or replace incremental session state.
142    pub fn set_session(&mut self, session: Box<dyn crate::session::ReasonerSession>) {
143        self.session = Some(session);
144    }
145
146    /// Clear incremental session state.
147    pub fn clear_session(&mut self) {
148        if let Some(session) = self.session.as_mut() {
149            session.clear();
150        }
151        self.session = None;
152    }
153
154    /// Run classification over the loaded ontology.
155    ///
156    /// Profile engines live in separate crates. Use `ontologos_rdfs::classify_reasoner`
157    /// for [`Profile::Rdfs`] and `ontologos_rl::classify_reasoner` for [`Profile::Rl`].
158    /// EL/Auto return [`Error::NotImplemented`]. Calling this with [`Profile::Rdfs`]
159    /// or [`Profile::Rl`] returns [`Error::Message`] with a delegate hint.
160    pub fn classify(&mut self) -> Result<()> {
161        if self.profile == Profile::Rdfs {
162            return Err(Error::Message(
163                "Profile::Rdfs: use ontologos_rdfs::classify_reasoner or \
164                 ontologos_rdfs::materialize_reasoner; Reasoner::classify does not \
165                 dispatch to profile engines"
166                    .into(),
167            ));
168        }
169        if self.profile == Profile::Rl {
170            return Err(Error::Message(
171                "Profile::Rl: use ontologos_rl::classify_reasoner or \
172                 ontologos_rl::materialize_reasoner; Reasoner::classify does not \
173                 dispatch to profile engines"
174                    .into(),
175            ));
176        }
177        Err(Error::NotImplemented)
178    }
179}