#[allow(unused_imports)] use crate::formats::n3_types::{N3Formula, N3Implication, N3Statement, N3Term, N3Variable};
use std::collections::HashMap;
#[derive(Debug, Clone, Default)]
pub struct VariableBindings {
bindings: HashMap<String, N3Term>,
}
impl VariableBindings {
pub fn new() -> Self {
Self {
bindings: HashMap::new(),
}
}
pub fn bind(&mut self, var_name: String, term: N3Term) {
self.bindings.insert(var_name, term);
}
pub fn get(&self, var_name: &str) -> Option<&N3Term> {
self.bindings.get(var_name)
}
pub fn is_bound(&self, var_name: &str) -> bool {
self.bindings.contains_key(var_name)
}
pub fn all_bindings(&self) -> &HashMap<String, N3Term> {
&self.bindings
}
pub fn is_compatible(&self, other: &VariableBindings) -> bool {
for (var, term) in &self.bindings {
if let Some(other_term) = other.get(var) {
if term != other_term {
return false;
}
}
}
true
}
pub fn merge(&mut self, other: &VariableBindings) {
for (var, term) in &other.bindings {
self.bindings.insert(var.clone(), term.clone());
}
}
}
pub trait Substitution {
fn substitute(&self, term: &N3Term) -> N3Term;
fn substitute_statement(&self, stmt: &N3Statement) -> N3Statement {
N3Statement::new(
self.substitute(&stmt.subject),
self.substitute(&stmt.predicate),
self.substitute(&stmt.object),
)
}
fn substitute_formula(&self, formula: &N3Formula) -> N3Formula {
let mut new_formula = N3Formula::new();
for stmt in &formula.triples {
new_formula.add_statement(self.substitute_statement(stmt));
}
new_formula
}
}
impl Substitution for VariableBindings {
fn substitute(&self, term: &N3Term) -> N3Term {
match term {
N3Term::Variable(var) => {
if let Some(bound_term) = self.get(&var.name) {
bound_term.clone()
} else {
term.clone()
}
}
N3Term::Formula(formula) => N3Term::Formula(Box::new(self.substitute_formula(formula))),
_ => term.clone(),
}
}
}
#[derive(Debug, Clone)]
pub struct FormulaPattern {
pub statements: Vec<N3Statement>,
}
impl FormulaPattern {
pub fn new() -> Self {
Self {
statements: Vec::new(),
}
}
pub fn new_with_statement(subject: N3Term, predicate: N3Term, object: N3Term) -> Self {
let mut pattern = Self::new();
pattern
.statements
.push(N3Statement::new(subject, predicate, object));
pattern
}
pub fn add_statement(&mut self, stmt: N3Statement) {
self.statements.push(stmt);
}
pub fn from_formula(formula: &N3Formula) -> Self {
Self {
statements: formula.triples.clone(),
}
}
}
impl Default for FormulaPattern {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct Matcher;
impl Matcher {
pub fn new() -> Self {
Self
}
pub fn match_pattern(
&self,
pattern: &FormulaPattern,
formula: &N3Formula,
) -> Option<VariableBindings> {
let mut bindings = VariableBindings::new();
for pattern_stmt in &pattern.statements {
let mut found_match = false;
for formula_stmt in &formula.triples {
if let Some(stmt_bindings) = self.match_statement(pattern_stmt, formula_stmt) {
if bindings.is_compatible(&stmt_bindings) {
bindings.merge(&stmt_bindings);
found_match = true;
break;
}
}
}
if !found_match {
return None;
}
}
Some(bindings)
}
fn match_statement(
&self,
pattern: &N3Statement,
concrete: &N3Statement,
) -> Option<VariableBindings> {
let mut bindings = VariableBindings::new();
if !self.match_term(&pattern.subject, &concrete.subject, &mut bindings) {
return None;
}
if !self.match_term(&pattern.predicate, &concrete.predicate, &mut bindings) {
return None;
}
if !self.match_term(&pattern.object, &concrete.object, &mut bindings) {
return None;
}
Some(bindings)
}
fn match_term(
&self,
pattern: &N3Term,
concrete: &N3Term,
bindings: &mut VariableBindings,
) -> bool {
match pattern {
N3Term::Variable(var) => {
if let Some(bound_term) = bindings.get(&var.name) {
bound_term == concrete
} else {
bindings.bind(var.name.clone(), concrete.clone());
true
}
}
_ => {
pattern == concrete
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct KnowledgeBase {
facts: Vec<N3Statement>,
rules: Vec<N3Implication>,
}
impl KnowledgeBase {
pub fn new() -> Self {
Self {
facts: Vec::new(),
rules: Vec::new(),
}
}
pub fn add_fact(&mut self, fact: N3Statement) {
if !fact.has_variables() {
self.facts.push(fact);
}
}
pub fn add_rule(&mut self, rule: N3Implication) {
self.rules.push(rule);
}
pub fn facts(&self) -> &[N3Statement] {
&self.facts
}
pub fn rules(&self) -> &[N3Implication] {
&self.rules
}
pub fn facts_as_formula(&self) -> N3Formula {
N3Formula::with_statements(self.facts.clone())
}
}
#[derive(Debug, Clone, Default)]
pub struct ReasoningEngine {
matcher: Matcher,
}
impl ReasoningEngine {
pub fn new() -> Self {
Self {
matcher: Matcher::new(),
}
}
pub fn forward_chain(&self, kb: &KnowledgeBase, max_iterations: usize) -> Vec<N3Statement> {
let mut new_facts = Vec::new();
let mut current_kb = kb.clone();
for _ in 0..max_iterations {
let mut derived_facts = Vec::new();
for rule in kb.rules() {
let pattern = FormulaPattern::from_formula(&rule.antecedent);
let formula = current_kb.facts_as_formula();
if let Some(bindings) = self.matcher.match_pattern(&pattern, &formula) {
let instantiated = bindings.substitute_formula(&rule.consequent);
for stmt in instantiated.triples {
if !stmt.has_variables() && !current_kb.facts.contains(&stmt) {
derived_facts.push(stmt.clone());
new_facts.push(stmt);
}
}
}
}
if derived_facts.is_empty() {
break;
}
for fact in derived_facts {
current_kb.add_fact(fact);
}
}
new_facts
}
}
#[cfg(test)]
mod tests {
use super::*;
use oxirs_core::model::NamedNode;
#[test]
fn test_variable_bindings() {
let mut bindings = VariableBindings::new();
let term =
N3Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI"));
bindings.bind("x".to_string(), term.clone());
assert!(bindings.is_bound("x"));
assert_eq!(bindings.get("x"), Some(&term));
}
#[test]
fn test_substitution() {
let mut bindings = VariableBindings::new();
bindings.bind(
"x".to_string(),
N3Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI")),
);
let var_term = N3Term::Variable(N3Variable::universal("x"));
let substituted = bindings.substitute(&var_term);
assert!(!substituted.is_variable());
}
#[test]
fn test_pattern_matching() {
let pattern = FormulaPattern::new_with_statement(
N3Term::Variable(N3Variable::universal("x")),
N3Term::NamedNode(NamedNode::new("http://example.org/knows").expect("valid IRI")),
N3Term::Variable(N3Variable::universal("y")),
);
let mut formula = N3Formula::new();
formula.add_statement(N3Statement::new(
N3Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI")),
N3Term::NamedNode(NamedNode::new("http://example.org/knows").expect("valid IRI")),
N3Term::NamedNode(NamedNode::new("http://example.org/bob").expect("valid IRI")),
));
let matcher = Matcher::new();
let bindings = matcher.match_pattern(&pattern, &formula);
assert!(bindings.is_some());
}
#[test]
fn test_forward_chaining() {
let mut kb = KnowledgeBase::new();
kb.add_fact(N3Statement::new(
N3Term::NamedNode(NamedNode::new("http://example.org/alice").expect("valid IRI")),
N3Term::NamedNode(NamedNode::new("http://example.org/knows").expect("valid IRI")),
N3Term::NamedNode(NamedNode::new("http://example.org/bob").expect("valid IRI")),
));
let mut antecedent = N3Formula::new();
antecedent.add_statement(N3Statement::new(
N3Term::Variable(N3Variable::universal("x")),
N3Term::NamedNode(NamedNode::new("http://example.org/knows").expect("valid IRI")),
N3Term::Variable(N3Variable::universal("y")),
));
let mut consequent = N3Formula::new();
consequent.add_statement(N3Statement::new(
N3Term::Variable(N3Variable::universal("y")),
N3Term::NamedNode(NamedNode::new("http://example.org/knows").expect("valid IRI")),
N3Term::Variable(N3Variable::universal("x")),
));
kb.add_rule(N3Implication::new(antecedent, consequent));
let engine = ReasoningEngine::new();
let new_facts = engine.forward_chain(&kb, 1);
assert!(!new_facts.is_empty());
}
}