use anyhow::{anyhow, Result};
use scirs2_core::metrics::{Counter, Gauge};
use std::collections::{HashMap, HashSet, VecDeque};
lazy_static::lazy_static! {
static ref TABLEAUX_EXPANSIONS: Counter = Counter::new("tableaux_expansions".to_string());
static ref TABLEAUX_CLASHES: Counter = Counter::new("tableaux_clashes".to_string());
static ref TABLEAUX_NODE_COUNT: Gauge = Gauge::new("tableaux_node_count".to_string());
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Concept {
Top,
Bottom,
Atomic(String),
Not(Box<Concept>),
And(Box<Concept>, Box<Concept>),
Or(Box<Concept>, Box<Concept>),
Exists(Role, Box<Concept>),
ForAll(Role, Box<Concept>),
AtLeast(usize, Role, Box<Concept>),
AtMost(usize, Role, Box<Concept>),
Exactly(usize, Role, Box<Concept>),
Nominal(Nominal),
SelfRestriction(Role),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Role {
pub name: String,
}
impl Role {
pub fn new(name: String) -> Self {
Self { name }
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum RoleAxiom {
Transitive(Role),
Symmetric(Role),
Functional(Role),
InverseFunctional(Role),
SubRoleOf(Role, Role),
InverseOf(Role, Role),
RoleChain(Vec<Role>, Role),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Nominal {
pub individual: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Individual {
pub id: usize,
pub name: Option<String>,
}
impl Individual {
pub fn new(id: usize) -> Self {
Self { id, name: None }
}
pub fn named(id: usize, name: String) -> Self {
Self {
id,
name: Some(name),
}
}
}
#[derive(Debug, Clone)]
pub struct NodeLabel {
pub individual: Individual,
pub concepts: HashSet<Concept>,
}
impl NodeLabel {
pub fn new(individual: Individual) -> Self {
Self {
individual,
concepts: HashSet::new(),
}
}
pub fn add_concept(&mut self, concept: Concept) -> bool {
self.concepts.insert(concept)
}
pub fn contains(&self, concept: &Concept) -> bool {
self.concepts.contains(concept)
}
pub fn has_clash(&self) -> bool {
for concept in &self.concepts {
let negation = Concept::Not(Box::new(concept.clone()));
if self.concepts.contains(&negation) {
return true;
}
}
false
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Edge {
pub from: Individual,
pub role: Role,
pub to: Individual,
}
impl Edge {
pub fn new(from: Individual, role: Role, to: Individual) -> Self {
Self { from, role, to }
}
}
#[derive(Debug, Clone)]
pub struct CompletionTree {
pub labels: HashMap<Individual, NodeLabel>,
pub edges: Vec<Edge>,
next_id: usize,
}
impl CompletionTree {
pub fn new() -> Self {
Self {
labels: HashMap::new(),
edges: Vec::new(),
next_id: 0,
}
}
pub fn create_individual(&mut self) -> Individual {
let individual = Individual::new(self.next_id);
self.next_id += 1;
self.labels
.insert(individual.clone(), NodeLabel::new(individual.clone()));
individual
}
pub fn add_concept(&mut self, individual: &Individual, concept: Concept) -> bool {
if let Some(label) = self.labels.get_mut(individual) {
label.add_concept(concept)
} else {
false
}
}
pub fn add_edge(&mut self, from: Individual, role: Role, to: Individual) {
self.edges.push(Edge::new(from, role, to));
}
pub fn get_successors(&self, individual: &Individual, role: &Role) -> Vec<Individual> {
self.edges
.iter()
.filter(|e| &e.from == individual && &e.role == role)
.map(|e| e.to.clone())
.collect()
}
pub fn has_clash(&self) -> bool {
self.labels.values().any(|label| label.has_clash())
}
pub fn get_label(&self, individual: &Individual) -> Option<&NodeLabel> {
self.labels.get(individual)
}
}
impl Default for CompletionTree {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
pub enum ExpansionResult {
NoChange,
Modified,
Branching(Vec<CompletionTree>),
}
pub struct TableauxReasoner {
max_depth: usize,
max_branches: usize,
pub stats: TableauxStats,
role_axioms: Vec<RoleAxiom>,
blocking_enabled: bool,
}
impl Default for TableauxReasoner {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct TableauxStats {
pub expansions: usize,
pub clashes: usize,
pub branches: usize,
pub max_depth_reached: usize,
}
impl TableauxReasoner {
pub fn new() -> Self {
Self {
max_depth: 100,
max_branches: 1000,
stats: TableauxStats::default(),
role_axioms: Vec::new(),
blocking_enabled: true,
}
}
pub fn with_max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
pub fn with_max_branches(mut self, branches: usize) -> Self {
self.max_branches = branches;
self
}
pub fn add_role_axiom(&mut self, axiom: RoleAxiom) {
self.role_axioms.push(axiom);
}
pub fn with_blocking(mut self, enabled: bool) -> Self {
self.blocking_enabled = enabled;
self
}
pub fn is_satisfiable(&mut self, concept: &Concept) -> Result<bool> {
let mut tree = CompletionTree::new();
let root = tree.create_individual();
tree.add_concept(&root, concept.clone());
self.expand_tree(tree, 0)
}
pub fn is_subsumed_by(&mut self, c: &Concept, d: &Concept) -> Result<bool> {
let negated_d = Concept::Not(Box::new(d.clone()));
let conjunction = Concept::And(Box::new(c.clone()), Box::new(negated_d));
let satisfiable = self.is_satisfiable(&conjunction)?;
Ok(!satisfiable)
}
pub fn is_equivalent(&mut self, c: &Concept, d: &Concept) -> Result<bool> {
let c_subsumes_d = self.is_subsumed_by(c, d)?;
let d_subsumes_c = self.is_subsumed_by(d, c)?;
Ok(c_subsumes_d && d_subsumes_c)
}
fn expand_tree(&mut self, mut tree: CompletionTree, depth: usize) -> Result<bool> {
if depth > self.max_depth {
return Err(anyhow!("Maximum expansion depth exceeded"));
}
if tree.has_clash() {
self.stats.clashes += 1;
TABLEAUX_CLASHES.inc();
return Ok(false);
}
let mut queue: VecDeque<Individual> = tree.labels.keys().cloned().collect();
let mut changed = true;
while changed {
changed = false;
while let Some(individual) = queue.pop_front() {
let label = match tree.get_label(&individual) {
Some(l) => l.clone(),
None => continue,
};
for concept in &label.concepts {
if let Concept::And(c1, c2) = concept {
if tree.add_concept(&individual, (**c1).clone()) {
changed = true;
queue.push_back(individual.clone());
}
if tree.add_concept(&individual, (**c2).clone()) {
changed = true;
queue.push_back(individual.clone());
}
self.stats.expansions += 1;
TABLEAUX_EXPANSIONS.inc();
}
}
for concept in &label.concepts {
if let Concept::Exists(role, c) = concept {
let successors = tree.get_successors(&individual, role);
let has_successor = successors.iter().any(|succ| {
tree.get_label(succ).map(|l| l.contains(c)).unwrap_or(false)
});
if !has_successor {
let new_individual = tree.create_individual();
tree.add_edge(individual.clone(), role.clone(), new_individual.clone());
tree.add_concept(&new_individual, (**c).clone());
changed = true;
queue.push_back(new_individual);
self.stats.expansions += 1;
TABLEAUX_EXPANSIONS.inc();
}
}
}
for concept in &label.concepts {
if let Concept::ForAll(role, c) = concept {
let successors = tree.get_successors(&individual, role);
for successor in successors {
if tree.add_concept(&successor, (**c).clone()) {
changed = true;
queue.push_back(successor);
self.stats.expansions += 1;
TABLEAUX_EXPANSIONS.inc();
}
}
}
}
for concept in &label.concepts {
if let Concept::AtLeast(n, role, c) = concept {
let successors = tree.get_successors(&individual, role);
let matching_successors: Vec<_> = successors
.iter()
.filter(|succ| {
tree.get_label(succ).map(|l| l.contains(c)).unwrap_or(false)
})
.collect();
if matching_successors.len() < *n {
for _ in matching_successors.len()..*n {
let new_individual = tree.create_individual();
tree.add_edge(
individual.clone(),
role.clone(),
new_individual.clone(),
);
tree.add_concept(&new_individual, (**c).clone());
changed = true;
queue.push_back(new_individual);
self.stats.expansions += 1;
TABLEAUX_EXPANSIONS.inc();
}
}
}
}
for concept in &label.concepts {
if let Concept::AtMost(n, role, c) = concept {
let successors = tree.get_successors(&individual, role);
let matching_successors: Vec<_> = successors
.iter()
.filter(|succ| {
tree.get_label(succ).map(|l| l.contains(c)).unwrap_or(false)
})
.cloned()
.collect();
if matching_successors.len() > *n {
self.stats.clashes += 1;
TABLEAUX_CLASHES.inc();
return Ok(false);
}
}
}
for concept in &label.concepts {
if let Concept::Exactly(n, role, c) = concept {
let at_least = Concept::AtLeast(*n, role.clone(), c.clone());
let at_most = Concept::AtMost(*n, role.clone(), c.clone());
if tree.add_concept(&individual, at_least) {
changed = true;
queue.push_back(individual.clone());
}
if tree.add_concept(&individual, at_most) {
changed = true;
queue.push_back(individual.clone());
}
self.stats.expansions += 1;
TABLEAUX_EXPANSIONS.inc();
}
}
for concept in &label.concepts {
if let Concept::SelfRestriction(role) = concept {
tree.add_edge(individual.clone(), role.clone(), individual.clone());
changed = true;
self.stats.expansions += 1;
TABLEAUX_EXPANSIONS.inc();
}
}
self.apply_role_axioms(&mut tree, &individual, &mut changed, &mut queue);
if tree.has_clash() {
self.stats.clashes += 1;
TABLEAUX_CLASHES.inc();
return Ok(false);
}
}
}
for individual in tree.labels.keys().cloned().collect::<Vec<_>>() {
let label = match tree.get_label(&individual) {
Some(l) => l.clone(),
None => continue,
};
for concept in &label.concepts {
if let Concept::Or(c1, c2) = concept {
if label.contains(c1) || label.contains(c2) {
continue;
}
let mut branch1 = tree.clone();
branch1.add_concept(&individual, (**c1).clone());
let mut branch2 = tree.clone();
branch2.add_concept(&individual, (**c2).clone());
self.stats.branches += 2;
let satisfiable1 = self.expand_tree(branch1, depth + 1)?;
if satisfiable1 {
return Ok(true);
}
let satisfiable2 = self.expand_tree(branch2, depth + 1)?;
return Ok(satisfiable2);
}
}
}
Ok(true)
}
fn apply_role_axioms(
&self,
tree: &mut CompletionTree,
individual: &Individual,
changed: &mut bool,
queue: &mut VecDeque<Individual>,
) {
for axiom in &self.role_axioms {
match axiom {
RoleAxiom::Transitive(role) => {
let successors: Vec<_> = tree.get_successors(individual, role);
for successor in &successors {
let next_successors: Vec<_> = tree.get_successors(successor, role);
for next_succ in next_successors {
let edge_exists = tree.edges.iter().any(|e| {
&e.from == individual && &e.role == role && e.to == next_succ
});
if !edge_exists {
tree.add_edge(individual.clone(), role.clone(), next_succ.clone());
*changed = true;
queue.push_back(next_succ);
}
}
}
}
RoleAxiom::Symmetric(role) => {
let successors: Vec<_> = tree.get_successors(individual, role);
for successor in successors {
let edge_exists = tree
.edges
.iter()
.any(|e| e.from == successor && &e.role == role && &e.to == individual);
if !edge_exists {
tree.add_edge(successor.clone(), role.clone(), individual.clone());
*changed = true;
queue.push_back(successor);
}
}
}
RoleAxiom::SubRoleOf(sub_role, super_role) => {
let successors: Vec<_> = tree.get_successors(individual, sub_role);
for successor in successors {
let edge_exists = tree.edges.iter().any(|e| {
&e.from == individual && &e.role == super_role && e.to == successor
});
if !edge_exists {
tree.add_edge(
individual.clone(),
super_role.clone(),
successor.clone(),
);
*changed = true;
queue.push_back(successor);
}
}
}
RoleAxiom::InverseOf(role1, role2) => {
let successors: Vec<_> = tree.get_successors(individual, role1);
for successor in successors {
let edge_exists = tree.edges.iter().any(|e| {
e.from == successor && &e.role == role2 && &e.to == individual
});
if !edge_exists {
tree.add_edge(successor.clone(), role2.clone(), individual.clone());
*changed = true;
queue.push_back(successor);
}
}
}
RoleAxiom::RoleChain(chain, result_role) => {
if chain.len() == 2 {
let successors: Vec<_> = tree.get_successors(individual, &chain[0]);
for successor in successors {
let next_successors: Vec<_> =
tree.get_successors(&successor, &chain[1]);
for next_succ in next_successors {
let edge_exists = tree.edges.iter().any(|e| {
&e.from == individual
&& &e.role == result_role
&& e.to == next_succ
});
if !edge_exists {
tree.add_edge(
individual.clone(),
result_role.clone(),
next_succ.clone(),
);
*changed = true;
queue.push_back(next_succ);
}
}
}
}
}
RoleAxiom::Functional(_) | RoleAxiom::InverseFunctional(_) => {}
}
}
}
pub fn reset_stats(&mut self) {
self.stats = TableauxStats::default();
}
}
pub fn to_nnf(concept: &Concept) -> Concept {
match concept {
Concept::Top | Concept::Bottom | Concept::Atomic(_) => concept.clone(),
Concept::Not(c) => match c.as_ref() {
Concept::Top => Concept::Bottom,
Concept::Bottom => Concept::Top,
Concept::Atomic(_) => concept.clone(),
Concept::Not(c2) => to_nnf(c2),
Concept::And(c1, c2) => {
let nnf1 = to_nnf(&Concept::Not(c1.clone()));
let nnf2 = to_nnf(&Concept::Not(c2.clone()));
Concept::Or(Box::new(nnf1), Box::new(nnf2))
}
Concept::Or(c1, c2) => {
let nnf1 = to_nnf(&Concept::Not(c1.clone()));
let nnf2 = to_nnf(&Concept::Not(c2.clone()));
Concept::And(Box::new(nnf1), Box::new(nnf2))
}
Concept::Exists(r, c2) => {
let nnf = to_nnf(&Concept::Not(c2.clone()));
Concept::ForAll(r.clone(), Box::new(nnf))
}
Concept::ForAll(r, c2) => {
let nnf = to_nnf(&Concept::Not(c2.clone()));
Concept::Exists(r.clone(), Box::new(nnf))
}
_ => concept.clone(),
},
Concept::And(c1, c2) => Concept::And(Box::new(to_nnf(c1)), Box::new(to_nnf(c2))),
Concept::Or(c1, c2) => Concept::Or(Box::new(to_nnf(c1)), Box::new(to_nnf(c2))),
Concept::Exists(r, c) => Concept::Exists(r.clone(), Box::new(to_nnf(c))),
Concept::ForAll(r, c) => Concept::ForAll(r.clone(), Box::new(to_nnf(c))),
Concept::AtLeast(n, r, c) => Concept::AtLeast(*n, r.clone(), Box::new(to_nnf(c))),
Concept::AtMost(n, r, c) => Concept::AtMost(*n, r.clone(), Box::new(to_nnf(c))),
Concept::Exactly(n, r, c) => Concept::Exactly(*n, r.clone(), Box::new(to_nnf(c))),
Concept::Nominal(_) | Concept::SelfRestriction(_) => concept.clone(),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_concept_creation() {
let person = Concept::Atomic("Person".to_string());
let animal = Concept::Atomic("Animal".to_string());
let conjunction = Concept::And(Box::new(person), Box::new(animal));
match conjunction {
Concept::And(c1, c2) => {
assert!(matches!(*c1, Concept::Atomic(_)));
assert!(matches!(*c2, Concept::Atomic(_)));
}
_ => panic!("Expected And concept"),
}
}
#[test]
fn test_completion_tree_creation() {
let mut tree = CompletionTree::new();
let ind1 = tree.create_individual();
let ind2 = tree.create_individual();
assert_eq!(ind1.id, 0);
assert_eq!(ind2.id, 1);
assert_eq!(tree.labels.len(), 2);
}
#[test]
fn test_clash_detection() {
let mut tree = CompletionTree::new();
let ind = tree.create_individual();
let person = Concept::Atomic("Person".to_string());
let not_person = Concept::Not(Box::new(person.clone()));
tree.add_concept(&ind, person);
tree.add_concept(&ind, not_person);
assert!(tree.has_clash());
}
#[test]
fn test_satisfiability_simple() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
assert!(reasoner.is_satisfiable(&person)?);
Ok(())
}
#[test]
fn test_satisfiability_conjunction() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let animal = Concept::Atomic("Animal".to_string());
let both = Concept::And(Box::new(person), Box::new(animal));
assert!(reasoner.is_satisfiable(&both)?);
Ok(())
}
#[test]
fn test_unsatisfiability_contradiction() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let not_person = Concept::Not(Box::new(person.clone()));
let contradiction = Concept::And(Box::new(person), Box::new(not_person));
assert!(!reasoner.is_satisfiable(&contradiction)?);
Ok(())
}
#[test]
fn test_existential_restriction() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let has_child = Role::new("hasChild".to_string());
let parent = Concept::Exists(has_child, Box::new(person));
assert!(reasoner.is_satisfiable(&parent)?);
Ok(())
}
#[test]
fn test_subsumption() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let animal = Concept::Atomic("Animal".to_string());
let both = Concept::And(Box::new(person.clone()), Box::new(animal));
assert!(reasoner.is_subsumed_by(&both, &person)?);
Ok(())
}
#[test]
fn test_nnf_conversion() {
let person = Concept::Atomic("Person".to_string());
let animal = Concept::Atomic("Animal".to_string());
let and = Concept::And(Box::new(person.clone()), Box::new(animal.clone()));
let negated = Concept::Not(Box::new(and));
let nnf = to_nnf(&negated);
match nnf {
Concept::Or(c1, c2) => {
assert!(matches!(*c1, Concept::Not(_)));
assert!(matches!(*c2, Concept::Not(_)));
}
_ => panic!("Expected Or concept"),
}
}
#[test]
fn test_disjunction_branching() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let animal = Concept::Atomic("Animal".to_string());
let either = Concept::Or(Box::new(person), Box::new(animal));
assert!(reasoner.is_satisfiable(&either)?);
assert!(reasoner.stats.branches > 0);
Ok(())
}
#[test]
fn test_universal_restriction() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let has_child = Role::new("hasChild".to_string());
let forall = Concept::ForAll(has_child.clone(), Box::new(person));
let exists = Concept::Exists(has_child, Box::new(Concept::Top));
let combined = Concept::And(Box::new(forall), Box::new(exists));
assert!(reasoner.is_satisfiable(&combined)?);
Ok(())
}
#[test]
fn test_at_least_cardinality() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let has_child = Role::new("hasChild".to_string());
let at_least_two = Concept::AtLeast(2, has_child, Box::new(person));
assert!(reasoner.is_satisfiable(&at_least_two)?);
Ok(())
}
#[test]
fn test_at_most_cardinality() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let has_child = Role::new("hasChild".to_string());
let at_most_one = Concept::AtMost(1, has_child, Box::new(person));
assert!(reasoner.is_satisfiable(&at_most_one)?);
Ok(())
}
#[test]
fn test_exactly_cardinality() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let has_child = Role::new("hasChild".to_string());
let exactly_three = Concept::Exactly(3, has_child, Box::new(person));
assert!(reasoner.is_satisfiable(&exactly_three)?);
Ok(())
}
#[test]
fn test_cardinality_contradiction() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let has_child = Role::new("hasChild".to_string());
let at_least_three = Concept::AtLeast(3, has_child.clone(), Box::new(person.clone()));
let at_most_two = Concept::AtMost(2, has_child, Box::new(person));
let contradiction = Concept::And(Box::new(at_least_three), Box::new(at_most_two));
assert!(!reasoner.is_satisfiable(&contradiction)?);
Ok(())
}
#[test]
fn test_transitive_role() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let ancestor = Role::new("ancestor".to_string());
reasoner.add_role_axiom(RoleAxiom::Transitive(ancestor.clone()));
let person = Concept::Atomic("Person".to_string());
let inner_exists = Concept::Exists(ancestor.clone(), Box::new(person.clone()));
let outer_exists = Concept::Exists(ancestor, Box::new(inner_exists));
assert!(reasoner.is_satisfiable(&outer_exists)?);
Ok(())
}
#[test]
fn test_symmetric_role() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let sibling = Role::new("sibling".to_string());
reasoner.add_role_axiom(RoleAxiom::Symmetric(sibling.clone()));
let person = Concept::Atomic("Person".to_string());
let has_sibling = Concept::Exists(sibling, Box::new(person));
assert!(reasoner.is_satisfiable(&has_sibling)?);
Ok(())
}
#[test]
fn test_sub_role_of() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let parent = Role::new("parent".to_string());
let ancestor = Role::new("ancestor".to_string());
reasoner.add_role_axiom(RoleAxiom::SubRoleOf(parent.clone(), ancestor.clone()));
let person = Concept::Atomic("Person".to_string());
let has_parent = Concept::Exists(parent, Box::new(person));
assert!(reasoner.is_satisfiable(&has_parent)?);
Ok(())
}
#[test]
fn test_inverse_role() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let has_parent = Role::new("hasParent".to_string());
let has_child = Role::new("hasChild".to_string());
reasoner.add_role_axiom(RoleAxiom::InverseOf(has_parent.clone(), has_child));
let person = Concept::Atomic("Person".to_string());
let has_parent_concept = Concept::Exists(has_parent, Box::new(person));
assert!(reasoner.is_satisfiable(&has_parent_concept)?);
Ok(())
}
#[test]
fn test_role_chain() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let has_parent = Role::new("hasParent".to_string());
let has_grandparent = Role::new("hasGrandparent".to_string());
reasoner.add_role_axiom(RoleAxiom::RoleChain(
vec![has_parent.clone(), has_parent.clone()],
has_grandparent,
));
let person = Concept::Atomic("Person".to_string());
let inner = Concept::Exists(has_parent.clone(), Box::new(person.clone()));
let outer = Concept::Exists(has_parent, Box::new(inner));
assert!(reasoner.is_satisfiable(&outer)?);
Ok(())
}
#[test]
fn test_self_restriction() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let likes = Role::new("likes".to_string());
let self_love = Concept::SelfRestriction(likes);
assert!(reasoner.is_satisfiable(&self_love)?);
Ok(())
}
#[test]
fn test_nominal() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let john_nominal = Nominal {
individual: "john".to_string(),
};
let john_concept = Concept::Nominal(john_nominal);
assert!(reasoner.is_satisfiable(&john_concept)?);
Ok(())
}
#[test]
fn test_complex_owl_concept() -> Result<()> {
let mut reasoner = TableauxReasoner::new();
let person = Concept::Atomic("Person".to_string());
let student = Concept::Atomic("Student".to_string());
let has_child = Role::new("hasChild".to_string());
let at_least_two_children =
Concept::AtLeast(2, has_child.clone(), Box::new(person.clone()));
let person_and_student = Concept::And(Box::new(person.clone()), Box::new(student));
let all_children_students = Concept::ForAll(has_child, Box::new(person_and_student));
let complex = Concept::And(
Box::new(person),
Box::new(Concept::And(
Box::new(at_least_two_children),
Box::new(all_children_students),
)),
);
assert!(reasoner.is_satisfiable(&complex)?);
Ok(())
}
#[test]
fn test_nnf_cardinality() {
let person = Concept::Atomic("Person".to_string());
let has_child = Role::new("hasChild".to_string());
let at_least = Concept::AtLeast(2, has_child.clone(), Box::new(person.clone()));
let nnf = to_nnf(&at_least);
match nnf {
Concept::AtLeast(n, _, _) => assert_eq!(n, 2),
_ => panic!("Expected AtLeast concept"),
}
let exactly = Concept::Exactly(3, has_child, Box::new(person));
let nnf_exactly = to_nnf(&exactly);
match nnf_exactly {
Concept::Exactly(n, _, _) => assert_eq!(n, 3),
_ => panic!("Expected Exactly concept"),
}
}
}