pub mod vocab;
mod abox_rules;
mod cardinality_rules;
mod complex_constructors;
mod property_hierarchy;
#[cfg(test)]
mod tests;
#[cfg(test)]
mod tests_100pct;
use std::collections::{HashMap, HashSet, VecDeque};
use std::time::Instant;
use thiserror::Error;
pub use vocab::RDF_TYPE;
#[derive(Debug, Error)]
pub enum DLError {
#[error("ABox inconsistency: {0}")]
Inconsistency(String),
#[error("Maximum fixpoint iterations ({0}) exceeded")]
MaxIterationsExceeded(usize),
#[error("Invalid TBox axiom: {0}")]
InvalidAxiom(String),
}
pub type Triple = (String, String, String);
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PropertyChain {
pub entailed_property: String,
pub chain: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HasValueRestriction {
pub restriction_class: String,
pub property: String,
pub value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct AllValuesFromRestriction {
pub restriction_class: String,
pub property: String,
pub filler_class: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct SomeValuesFromRestriction {
pub restriction_class: String,
pub property: String,
pub filler_class: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NominalClass {
pub class_iri: String,
pub members: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct IntersectionOfClass {
pub class_iri: String,
pub operands: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ComplementOfClass {
pub class_iri: String,
pub base_class: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DisjointUnionOf {
pub class_iri: String,
pub operands: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HasKeyAxiom {
pub class_iri: String,
pub key_properties: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NegativeObjectPropertyAssertion {
pub source_individual: String,
pub assertion_property: String,
pub target_individual: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct NegativeDataPropertyAssertion {
pub source_individual: String,
pub assertion_property: String,
pub target_value: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct HasSelfRestriction {
pub restriction_class: String,
pub property: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct PropertyCharacteristics {
pub is_transitive: bool,
pub is_symmetric: bool,
pub is_asymmetric: bool,
pub is_reflexive: bool,
pub is_irreflexive: bool,
pub is_functional: bool,
pub is_inverse_functional: bool,
pub inverse_of: Option<String>,
}
#[derive(Debug, Clone, Default)]
pub struct RuleFirings {
pub individual_classification: usize,
pub property_chain: usize,
pub nominal_classification: usize,
pub has_value_forward: usize,
pub has_value_backward: usize,
pub all_values_from: usize,
pub some_values_from: usize,
pub transitivity: usize,
pub symmetry: usize,
pub asymmetry_checks: usize,
pub subclass_propagation: usize,
pub equivalent_class: usize,
pub domain_range: usize,
pub same_as_propagation: usize,
pub inverse_property: usize,
pub complement_of: usize,
pub intersection_of: usize,
pub disjoint_union: usize,
pub has_key: usize,
pub functional_property: usize,
pub inverse_functional_property: usize,
pub sub_object_property: usize,
pub sub_data_property: usize,
pub equivalent_properties: usize,
pub reflexive_self: usize,
pub has_self: usize,
pub negative_property_assertion: usize,
pub max_cardinality: usize,
pub min_cardinality: usize,
pub exact_cardinality: usize,
pub union_of: usize,
pub data_some_values_from: usize,
pub data_all_values_from: usize,
pub all_different: usize,
pub same_as_congruence: usize,
}
#[derive(Debug, Clone)]
pub struct DLInferenceReport {
pub iterations: usize,
pub new_triples: usize,
pub rule_firings: RuleFirings,
pub duration: std::time::Duration,
pub inconsistencies: Vec<String>,
}
pub struct Owl2DLReasoner {
pub(crate) abox: HashSet<Triple>,
pub(crate) asserted: HashSet<Triple>,
pub(crate) subclass_of: HashSet<(String, String)>,
pub(crate) equivalent_classes: HashSet<(String, String)>,
pub(crate) disjoint_classes: HashSet<(String, String)>,
pub(crate) has_value_restrictions: Vec<HasValueRestriction>,
pub(crate) all_values_restrictions: Vec<AllValuesFromRestriction>,
pub(crate) some_values_restrictions: Vec<SomeValuesFromRestriction>,
pub(crate) nominal_classes: Vec<NominalClass>,
pub(crate) property_chains: Vec<PropertyChain>,
pub(crate) complement_of_classes: Vec<ComplementOfClass>,
pub(crate) intersection_of_classes: Vec<IntersectionOfClass>,
pub(crate) disjoint_unions: Vec<DisjointUnionOf>,
pub(crate) has_key_axioms: Vec<HasKeyAxiom>,
pub(crate) sub_object_property_of: HashSet<(String, String)>,
pub(crate) sub_data_property_of: HashSet<(String, String)>,
pub(crate) equivalent_properties: HashSet<(String, String)>,
pub(crate) disjoint_properties: HashSet<(String, String)>,
pub(crate) has_self_restrictions: Vec<HasSelfRestriction>,
pub(crate) negative_object_assertions: Vec<NegativeObjectPropertyAssertion>,
pub(crate) negative_data_assertions: Vec<NegativeDataPropertyAssertion>,
pub(crate) cardinality_restrictions: Vec<cardinality_rules::CardinalityRestriction>,
pub(crate) union_of_classes: Vec<cardinality_rules::UnionOfClass>,
pub(crate) data_some_values_restrictions: Vec<cardinality_rules::DataSomeValuesFromRestriction>,
pub(crate) data_all_values_restrictions: Vec<cardinality_rules::DataAllValuesFromRestriction>,
pub(crate) all_different_axioms: Vec<cardinality_rules::AllDifferentAxiom>,
pub(crate) property_chars: HashMap<String, PropertyCharacteristics>,
pub(crate) max_iterations: usize,
pub(crate) inconsistencies: Vec<String>,
}
impl Owl2DLReasoner {
pub fn new() -> Self {
Self {
abox: HashSet::new(),
asserted: HashSet::new(),
subclass_of: HashSet::new(),
equivalent_classes: HashSet::new(),
disjoint_classes: HashSet::new(),
has_value_restrictions: Vec::new(),
all_values_restrictions: Vec::new(),
some_values_restrictions: Vec::new(),
nominal_classes: Vec::new(),
property_chains: Vec::new(),
complement_of_classes: Vec::new(),
intersection_of_classes: Vec::new(),
disjoint_unions: Vec::new(),
has_key_axioms: Vec::new(),
sub_object_property_of: HashSet::new(),
sub_data_property_of: HashSet::new(),
equivalent_properties: HashSet::new(),
disjoint_properties: HashSet::new(),
has_self_restrictions: Vec::new(),
negative_object_assertions: Vec::new(),
negative_data_assertions: Vec::new(),
cardinality_restrictions: Vec::new(),
union_of_classes: Vec::new(),
data_some_values_restrictions: Vec::new(),
data_all_values_restrictions: Vec::new(),
all_different_axioms: Vec::new(),
property_chars: HashMap::new(),
max_iterations: 500,
inconsistencies: Vec::new(),
}
}
pub fn with_max_iterations(mut self, n: usize) -> Self {
self.max_iterations = n;
self
}
}
impl Default for Owl2DLReasoner {
fn default() -> Self {
Self::new()
}
}
impl Owl2DLReasoner {
pub fn assert_triple(&mut self, subject: &str, predicate: &str, object: &str) {
let t = mk_triple(subject, predicate, object);
self.asserted.insert(t.clone());
self.abox.insert(t);
}
pub fn assert_type(&mut self, individual: &str, class: &str) {
self.assert_triple(individual, vocab::RDF_TYPE, class);
}
pub fn add_property_assertion(&mut self, subject: &str, property: &str, object: &str) {
self.assert_triple(subject, property, object);
}
pub fn assert_same_as(&mut self, ind1: &str, ind2: &str) {
self.assert_triple(ind1, vocab::OWL_SAME_AS, ind2);
self.assert_triple(ind2, vocab::OWL_SAME_AS, ind1);
}
pub fn assert_different_from(&mut self, ind1: &str, ind2: &str) {
self.assert_triple(ind1, vocab::OWL_DIFFERENT_FROM, ind2);
self.assert_triple(ind2, vocab::OWL_DIFFERENT_FROM, ind1);
}
}
impl Owl2DLReasoner {
pub fn add_subclass_of(&mut self, sub: &str, sup: &str) {
self.subclass_of.insert((sub.to_string(), sup.to_string()));
self.abox
.insert(mk_triple(sub, vocab::RDFS_SUBCLASS_OF, sup));
self.asserted
.insert(mk_triple(sub, vocab::RDFS_SUBCLASS_OF, sup));
}
pub fn add_equivalent_classes(&mut self, c1: &str, c2: &str) {
self.equivalent_classes
.insert((c1.to_string(), c2.to_string()));
self.equivalent_classes
.insert((c2.to_string(), c1.to_string()));
self.abox
.insert(mk_triple(c1, vocab::OWL_EQUIVALENT_CLASS, c2));
self.abox
.insert(mk_triple(c2, vocab::OWL_EQUIVALENT_CLASS, c1));
}
pub fn add_disjoint_classes(&mut self, c1: &str, c2: &str) {
self.disjoint_classes
.insert((c1.to_string(), c2.to_string()));
self.disjoint_classes
.insert((c2.to_string(), c1.to_string()));
self.abox
.insert(mk_triple(c1, vocab::OWL_DISJOINT_WITH, c2));
self.abox
.insert(mk_triple(c2, vocab::OWL_DISJOINT_WITH, c1));
}
pub fn add_domain(&mut self, property: &str, class: &str) {
self.abox
.insert(mk_triple(property, vocab::RDFS_DOMAIN, class));
self.asserted
.insert(mk_triple(property, vocab::RDFS_DOMAIN, class));
}
pub fn add_range(&mut self, property: &str, class: &str) {
self.abox
.insert(mk_triple(property, vocab::RDFS_RANGE, class));
self.asserted
.insert(mk_triple(property, vocab::RDFS_RANGE, class));
}
pub fn add_inverse_of(&mut self, p1: &str, p2: &str) {
let chars1 = self.property_chars.entry(p1.to_string()).or_default();
chars1.inverse_of = Some(p2.to_string());
let chars2 = self.property_chars.entry(p2.to_string()).or_default();
chars2.inverse_of = Some(p1.to_string());
self.abox.insert(mk_triple(p1, vocab::OWL_INVERSE_OF, p2));
self.asserted
.insert(mk_triple(p1, vocab::OWL_INVERSE_OF, p2));
}
}
impl Owl2DLReasoner {
fn declare_property_char(&mut self, property: &str, char_type: &str) {
self.property_chars.entry(property.to_string()).or_default();
self.abox
.insert(mk_triple(property, vocab::RDF_TYPE, char_type));
self.asserted
.insert(mk_triple(property, vocab::RDF_TYPE, char_type));
}
pub fn add_transitive_property(&mut self, property: &str) {
self.property_chars
.entry(property.to_string())
.or_default()
.is_transitive = true;
self.declare_property_char(property, vocab::OWL_TRANSITIVE_PROPERTY);
}
pub fn add_symmetric_property(&mut self, property: &str) {
self.property_chars
.entry(property.to_string())
.or_default()
.is_symmetric = true;
self.declare_property_char(property, vocab::OWL_SYMMETRIC_PROPERTY);
}
pub fn add_asymmetric_property(&mut self, property: &str) {
self.property_chars
.entry(property.to_string())
.or_default()
.is_asymmetric = true;
self.declare_property_char(property, vocab::OWL_ASYMMETRIC_PROPERTY);
}
pub fn add_reflexive_property(&mut self, property: &str) {
self.property_chars
.entry(property.to_string())
.or_default()
.is_reflexive = true;
self.declare_property_char(property, vocab::OWL_REFLEXIVE_PROPERTY);
}
pub fn add_irreflexive_property(&mut self, property: &str) {
self.property_chars
.entry(property.to_string())
.or_default()
.is_irreflexive = true;
self.declare_property_char(property, vocab::OWL_IRREFLEXIVE_PROPERTY);
}
pub fn add_functional_property(&mut self, property: &str) {
self.property_chars
.entry(property.to_string())
.or_default()
.is_functional = true;
self.declare_property_char(property, vocab::OWL_FUNCTIONAL_PROPERTY);
}
pub fn add_inverse_functional_property(&mut self, property: &str) {
self.property_chars
.entry(property.to_string())
.or_default()
.is_inverse_functional = true;
self.declare_property_char(property, vocab::OWL_INVERSE_FUNCTIONAL_PROPERTY);
}
}
impl Owl2DLReasoner {
pub fn add_has_value_restriction(
&mut self,
restriction_class: &str,
property: &str,
value: &str,
) {
self.has_value_restrictions.push(HasValueRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
value: value.to_string(),
});
}
pub fn add_all_values_from_restriction(
&mut self,
restriction_class: &str,
property: &str,
filler_class: &str,
) {
self.all_values_restrictions.push(AllValuesFromRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
filler_class: filler_class.to_string(),
});
}
pub fn add_some_values_from_restriction(
&mut self,
restriction_class: &str,
property: &str,
filler_class: &str,
) {
self.some_values_restrictions
.push(SomeValuesFromRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
filler_class: filler_class.to_string(),
});
}
pub fn add_nominal_class(&mut self, class_iri: &str, members: Vec<String>) {
self.nominal_classes.push(NominalClass {
class_iri: class_iri.to_string(),
members,
});
}
pub fn add_property_chain(&mut self, entailed_property: &str, chain: Vec<String>) {
self.property_chains.push(PropertyChain {
entailed_property: entailed_property.to_string(),
chain,
});
}
pub fn add_complement_of(&mut self, class_iri: &str, base_class: &str) {
self.complement_of_classes.push(ComplementOfClass {
class_iri: class_iri.to_string(),
base_class: base_class.to_string(),
});
}
pub fn add_intersection_of(&mut self, class_iri: &str, operands: Vec<String>) {
self.intersection_of_classes.push(IntersectionOfClass {
class_iri: class_iri.to_string(),
operands,
});
}
pub fn add_disjoint_union(&mut self, class_iri: &str, operands: Vec<String>) {
self.disjoint_unions.push(DisjointUnionOf {
class_iri: class_iri.to_string(),
operands,
});
}
pub fn add_has_key(&mut self, class_iri: &str, key_properties: Vec<String>) {
self.has_key_axioms.push(HasKeyAxiom {
class_iri: class_iri.to_string(),
key_properties,
});
}
}
impl Owl2DLReasoner {
pub fn add_sub_object_property_of(&mut self, sub: &str, sup: &str) {
self.sub_object_property_of
.insert((sub.to_string(), sup.to_string()));
self.abox
.insert(mk_triple(sub, vocab::RDFS_SUBPROPERTY_OF, sup));
self.asserted
.insert(mk_triple(sub, vocab::RDFS_SUBPROPERTY_OF, sup));
}
pub fn add_sub_data_property_of(&mut self, sub: &str, sup: &str) {
self.sub_data_property_of
.insert((sub.to_string(), sup.to_string()));
self.abox
.insert(mk_triple(sub, vocab::RDFS_SUBPROPERTY_OF, sup));
self.asserted
.insert(mk_triple(sub, vocab::RDFS_SUBPROPERTY_OF, sup));
}
pub fn add_equivalent_properties(&mut self, p1: &str, p2: &str) {
self.equivalent_properties
.insert((p1.to_string(), p2.to_string()));
self.equivalent_properties
.insert((p2.to_string(), p1.to_string()));
self.abox
.insert(mk_triple(p1, vocab::OWL_EQUIVALENT_PROPERTY, p2));
self.abox
.insert(mk_triple(p2, vocab::OWL_EQUIVALENT_PROPERTY, p1));
}
pub fn add_disjoint_properties(&mut self, p1: &str, p2: &str) {
self.disjoint_properties
.insert((p1.to_string(), p2.to_string()));
self.disjoint_properties
.insert((p2.to_string(), p1.to_string()));
self.abox
.insert(mk_triple(p1, vocab::OWL_PROPERTY_DISJOINT_WITH, p2));
self.abox
.insert(mk_triple(p2, vocab::OWL_PROPERTY_DISJOINT_WITH, p1));
}
pub fn add_has_self_restriction(&mut self, restriction_class: &str, property: &str) {
self.has_self_restrictions.push(HasSelfRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
});
}
pub fn assert_negative_object_property_assertion(
&mut self,
source: &str,
property: &str,
target: &str,
) {
self.negative_object_assertions
.push(NegativeObjectPropertyAssertion {
source_individual: source.to_string(),
assertion_property: property.to_string(),
target_individual: target.to_string(),
});
}
pub fn assert_negative_data_property_assertion(
&mut self,
source: &str,
property: &str,
target_value: &str,
) {
self.negative_data_assertions
.push(NegativeDataPropertyAssertion {
source_individual: source.to_string(),
assertion_property: property.to_string(),
target_value: target_value.to_string(),
});
}
}
impl Owl2DLReasoner {
pub fn add_max_cardinality(&mut self, restriction_class: &str, property: &str, n: usize) {
self.cardinality_restrictions
.push(cardinality_rules::CardinalityRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
cardinality: n,
kind: cardinality_rules::CardinalityKind::Max,
qualifying_class: None,
});
}
pub fn add_max_qualified_cardinality(
&mut self,
restriction_class: &str,
property: &str,
n: usize,
qualifying_class: &str,
) {
self.cardinality_restrictions
.push(cardinality_rules::CardinalityRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
cardinality: n,
kind: cardinality_rules::CardinalityKind::Max,
qualifying_class: Some(qualifying_class.to_string()),
});
}
pub fn add_min_cardinality(&mut self, restriction_class: &str, property: &str, n: usize) {
self.cardinality_restrictions
.push(cardinality_rules::CardinalityRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
cardinality: n,
kind: cardinality_rules::CardinalityKind::Min,
qualifying_class: None,
});
}
pub fn add_min_qualified_cardinality(
&mut self,
restriction_class: &str,
property: &str,
n: usize,
qualifying_class: &str,
) {
self.cardinality_restrictions
.push(cardinality_rules::CardinalityRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
cardinality: n,
kind: cardinality_rules::CardinalityKind::Min,
qualifying_class: Some(qualifying_class.to_string()),
});
}
pub fn add_exact_cardinality(&mut self, restriction_class: &str, property: &str, n: usize) {
self.cardinality_restrictions
.push(cardinality_rules::CardinalityRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
cardinality: n,
kind: cardinality_rules::CardinalityKind::Exact,
qualifying_class: None,
});
}
pub fn add_union_of(&mut self, class_iri: &str, operands: Vec<String>) {
if operands.is_empty() {
return;
}
self.union_of_classes.push(cardinality_rules::UnionOfClass {
class_iri: class_iri.to_string(),
operands,
});
}
pub fn add_data_some_values_from(
&mut self,
restriction_class: &str,
property: &str,
datatype: Option<&str>,
) {
self.data_some_values_restrictions
.push(cardinality_rules::DataSomeValuesFromRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
datatype: datatype.map(|s| s.to_string()),
});
}
pub fn add_data_all_values_from(
&mut self,
restriction_class: &str,
property: &str,
datatype: &str,
) {
self.data_all_values_restrictions
.push(cardinality_rules::DataAllValuesFromRestriction {
restriction_class: restriction_class.to_string(),
property: property.to_string(),
datatype: datatype.to_string(),
});
}
pub fn add_all_different(&mut self, members: Vec<String>) {
if members.len() < 2 {
return;
}
self.all_different_axioms
.push(cardinality_rules::AllDifferentAxiom { members });
}
}
impl Owl2DLReasoner {
pub fn is_type_entailed(&self, individual: &str, class: &str) -> bool {
self.abox
.contains(&mk_triple(individual, vocab::RDF_TYPE, class))
}
pub fn is_triple_entailed(&self, s: &str, p: &str, o: &str) -> bool {
self.abox.contains(&mk_triple(s, p, o))
}
pub fn get_types(&self, individual: &str) -> Vec<String> {
self.abox
.iter()
.filter(|(s, p, _)| s == individual && p == vocab::RDF_TYPE)
.map(|(_, _, o)| o.clone())
.collect()
}
pub fn inferred_triples(&self) -> Vec<Triple> {
self.abox
.iter()
.filter(|t| !self.asserted.contains(*t))
.cloned()
.collect()
}
pub fn is_consistent(&self) -> bool {
self.inconsistencies.is_empty()
}
pub fn inconsistencies(&self) -> &[String] {
&self.inconsistencies
}
pub fn all_individuals(&self) -> HashSet<String> {
let mut individuals = HashSet::new();
for (s, p, _o) in &self.abox {
if p == vocab::RDF_TYPE {
individuals.insert(s.clone());
}
}
individuals
}
}
impl Owl2DLReasoner {
pub fn materialize(&mut self) -> Result<DLInferenceReport, DLError> {
let start = Instant::now();
self.inconsistencies.clear();
let mut firings = RuleFirings::default();
let mut total_new = 0usize;
let mut iterations = 0usize;
self.close_subclass_hierarchy();
self.close_sub_property_hierarchy();
self.expand_disjoint_unions();
loop {
if iterations >= self.max_iterations {
return Err(DLError::MaxIterationsExceeded(self.max_iterations));
}
iterations += 1;
let snapshot: HashSet<Triple> = self.abox.clone();
let mut new_triples: HashSet<Triple> = HashSet::new();
self.apply_subclass_propagation(&snapshot, &mut new_triples, &mut firings);
self.apply_equivalent_class_propagation(&snapshot, &mut new_triples, &mut firings);
self.apply_nominal_classification(&snapshot, &mut new_triples, &mut firings);
self.apply_has_value_forward(&snapshot, &mut new_triples, &mut firings);
self.apply_has_value_backward(&snapshot, &mut new_triples, &mut firings);
self.apply_all_values_from(&snapshot, &mut new_triples, &mut firings);
self.apply_some_values_from(&snapshot, &mut new_triples, &mut firings);
self.apply_property_chains(&snapshot, &mut new_triples, &mut firings);
self.apply_transitivity(&snapshot, &mut new_triples, &mut firings);
self.apply_symmetry(&snapshot, &mut new_triples, &mut firings);
self.apply_inverse_of(&snapshot, &mut new_triples, &mut firings);
self.apply_domain_range(&snapshot, &mut new_triples, &mut firings);
self.apply_same_as_propagation(&snapshot, &mut new_triples, &mut firings);
self.apply_intersection_of(&snapshot, &mut new_triples, &mut firings);
self.apply_functional_property(&snapshot, &mut new_triples, &mut firings);
self.apply_inverse_functional_property(&snapshot, &mut new_triples, &mut firings);
self.apply_has_key(&snapshot, &mut new_triples, &mut firings);
self.apply_sub_property_of(&snapshot, &mut new_triples, &mut firings);
self.apply_equivalent_properties(&snapshot, &mut new_triples, &mut firings);
self.apply_reflexive_property(&snapshot, &mut new_triples, &mut firings);
self.apply_has_self(&snapshot, &mut new_triples, &mut firings);
self.apply_max_cardinality(&snapshot, &mut new_triples, &mut firings);
self.apply_min_cardinality(&snapshot, &mut new_triples, &mut firings);
self.apply_exact_cardinality(&snapshot, &mut new_triples, &mut firings);
self.apply_union_of(&snapshot, &mut new_triples, &mut firings);
self.apply_data_some_values_from(&snapshot, &mut new_triples, &mut firings);
self.apply_data_all_values_from(&snapshot, &mut new_triples, &mut firings);
self.apply_all_different(&snapshot, &mut new_triples, &mut firings);
self.apply_same_as_full_congruence(&snapshot, &mut new_triples, &mut firings);
let genuinely_new: HashSet<Triple> = new_triples
.into_iter()
.filter(|t| !snapshot.contains(t))
.collect();
if genuinely_new.is_empty() {
break;
}
total_new += genuinely_new.len();
self.abox.extend(genuinely_new);
}
self.check_asymmetry_violations(&firings);
self.check_irreflexivity_violations();
self.check_disjoint_violations();
self.check_nothing_violations();
self.check_complement_of_violations(&mut firings);
self.check_disjoint_union_violations();
self.check_disjoint_property_violations();
self.check_negative_property_assertion_violations(&mut firings);
self.check_all_different_violations();
self.check_max_cardinality_violations();
Ok(DLInferenceReport {
iterations,
new_triples: total_new,
rule_firings: firings,
duration: start.elapsed(),
inconsistencies: self.inconsistencies.clone(),
})
}
pub(crate) fn close_sub_property_hierarchy(&mut self) {
let mut obj_pairs: HashSet<(String, String)> = self.sub_object_property_of.clone();
let mut changed = true;
while changed {
changed = false;
let snapshot: Vec<(String, String)> = obj_pairs.iter().cloned().collect();
for (a, b) in &snapshot {
for (c, d) in &snapshot {
if b == c && !obj_pairs.contains(&(a.clone(), d.clone())) {
obj_pairs.insert((a.clone(), d.clone()));
self.abox
.insert(mk_triple(a, vocab::RDFS_SUBPROPERTY_OF, d));
changed = true;
}
}
}
}
self.sub_object_property_of = obj_pairs;
let mut data_pairs: HashSet<(String, String)> = self.sub_data_property_of.clone();
let mut data_changed = true;
while data_changed {
data_changed = false;
let snapshot: Vec<(String, String)> = data_pairs.iter().cloned().collect();
for (a, b) in &snapshot {
for (c, d) in &snapshot {
if b == c && !data_pairs.contains(&(a.clone(), d.clone())) {
data_pairs.insert((a.clone(), d.clone()));
self.abox
.insert(mk_triple(a, vocab::RDFS_SUBPROPERTY_OF, d));
data_changed = true;
}
}
}
}
self.sub_data_property_of = data_pairs;
}
pub(crate) fn close_subclass_hierarchy(&mut self) {
let mut pairs: HashSet<(String, String)> = self.subclass_of.clone();
let mut changed = true;
while changed {
changed = false;
let snapshot: Vec<(String, String)> = pairs.iter().cloned().collect();
for (a, b) in &snapshot {
for (c, d) in &snapshot {
if b == c && !pairs.contains(&(a.clone(), d.clone())) {
pairs.insert((a.clone(), d.clone()));
self.abox.insert(mk_triple(a, vocab::RDFS_SUBCLASS_OF, d));
changed = true;
}
}
}
}
self.subclass_of = pairs;
}
}
#[inline]
pub(crate) fn mk_triple(s: &str, p: &str, o: &str) -> Triple {
(s.to_string(), p.to_string(), o.to_string())
}
pub(crate) fn bfs_reachable<'a>(
start: &'a str,
adj: &'a HashMap<&'a str, Vec<&'a str>>,
) -> Vec<&'a str> {
let mut visited: HashSet<&str> = HashSet::new();
let mut queue: VecDeque<&str> = VecDeque::new();
queue.push_back(start);
while let Some(node) = queue.pop_front() {
if visited.contains(node) {
continue;
}
visited.insert(node);
if let Some(neighbors) = adj.get(node) {
for &n in neighbors {
if !visited.contains(n) {
queue.push_back(n);
}
}
}
}
visited.into_iter().collect()
}
pub(crate) fn evaluate_chain<'a>(
chain: &[String],
pred_index: &HashMap<&'a str, Vec<(&'a str, &'a str)>>,
) -> Vec<(&'a str, &'a str)> {
if chain.is_empty() {
return Vec::new();
}
let first_pred = chain[0].as_str();
let mut current: Vec<(&str, &str)> = pred_index.get(first_pred).cloned().unwrap_or_default();
for step_pred in chain.iter().skip(1) {
let step_pairs = pred_index
.get(step_pred.as_str())
.cloned()
.unwrap_or_default();
let mut step_map: HashMap<&str, Vec<&str>> = HashMap::new();
for (s, o) in &step_pairs {
step_map.entry(s).or_default().push(o);
}
let mut next: Vec<(&str, &str)> = Vec::new();
for (start, mid) in ¤t {
if let Some(ends) = step_map.get(mid) {
for &end in ends {
next.push((start, end));
}
}
}
current = next;
}
current
}