use super::*;
use crate::owl::OwlReasoner;
use crate::rdfs::RdfsReasoner;
use crate::swrl::{SwrlArgument, SwrlAtom, SwrlEngine, SwrlRule};
#[test]
fn test_integrated_forward_backward_chaining() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = RuleEngine::new();
engine.add_rule(Rule {
name: "simple_ancestor".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("parent".to_string()),
object: Term::Variable("Y".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("ancestor".to_string()),
object: Term::Variable("Y".to_string()),
}],
});
let facts = vec![RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant("parent".to_string()),
object: Term::Constant("mary".to_string()),
}];
let forward_results = engine.forward_chain(&facts)?;
assert!(forward_results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "john" && p == "ancestor" && o == "mary")
}));
let goal = RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant("ancestor".to_string()),
object: Term::Constant("mary".to_string()),
};
engine.add_facts(facts);
assert!(engine.backward_chain(&goal)?);
Ok(())
}
#[test]
fn test_rdfs_integration() -> Result<(), Box<dyn std::error::Error>> {
let mut rdfs_reasoner = RdfsReasoner::new();
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("Person".to_string()),
predicate: Term::Constant(
"http://www.w3.org/2000/01/rdf-schema#subClassOf".to_string(),
),
object: Term::Constant("LivingThing".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
),
object: Term::Constant("Person".to_string()),
},
];
let inferred = rdfs_reasoner.infer(&facts)?;
assert!(inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "john" && p == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" && o == "LivingThing")
}));
Ok(())
}
#[test]
fn test_owl_integration() -> Result<(), Box<dyn std::error::Error>> {
let mut owl_reasoner = OwlReasoner::new();
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("Human".to_string()),
predicate: Term::Constant("http://www.w3.org/2002/07/owl#equivalentClass".to_string()),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
),
object: Term::Constant("Human".to_string()),
},
];
let inferred = owl_reasoner.infer(&facts)?;
assert!(inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "john" && p == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" && o == "Person")
}));
Ok(())
}
#[test]
fn test_swrl_integration() -> Result<(), Box<dyn std::error::Error>> {
let mut swrl_engine = SwrlEngine::new();
let swrl_rule = SwrlRule {
id: "adult_rule".to_string(),
body: vec![
SwrlAtom::Class {
class_predicate: "Person".to_string(),
argument: SwrlArgument::Variable("x".to_string()),
},
SwrlAtom::DatavalueProperty {
property_predicate: "hasAge".to_string(),
argument1: SwrlArgument::Variable("x".to_string()),
argument2: SwrlArgument::Variable("age".to_string()),
},
SwrlAtom::Builtin {
builtin_predicate: "http://www.w3.org/2003/11/swrlb#greaterThan".to_string(),
arguments: vec![
SwrlArgument::Variable("age".to_string()),
SwrlArgument::Literal("18".to_string()),
],
},
],
head: vec![SwrlAtom::Class {
class_predicate: "Adult".to_string(),
argument: SwrlArgument::Variable("x".to_string()),
}],
metadata: std::collections::HashMap::new(),
};
swrl_engine.add_rule(swrl_rule)?;
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant("hasAge".to_string()),
object: Term::Literal("25".to_string()),
},
];
let results = swrl_engine.execute(&facts)?;
assert!(results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "john" && p == "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" && o == "Adult")
}));
Ok(())
}
#[test]
fn test_rete_integration() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = RuleEngine::new();
engine.add_rule(Rule {
name: "rule1".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Person".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Human".to_string()),
}],
});
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("bob".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Person".to_string()),
},
];
let rete_results = engine.rete_forward_chain(facts)?;
assert!(rete_results.len() >= 4); assert!(rete_results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "alice" && p == "type" && o == "Human")
}));
Ok(())
}
#[test]
fn test_error_handling() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = RuleEngine::new();
let empty_facts = vec![];
let results = engine.forward_chain(&empty_facts)?;
assert!(results.is_empty());
let non_existent_goal = RuleAtom::Triple {
subject: Term::Constant("nonexistent".to_string()),
predicate: Term::Constant("nonexistent".to_string()),
object: Term::Constant("nonexistent".to_string()),
};
assert!(!engine.backward_chain(&non_existent_goal)?);
engine.add_rule(Rule {
name: "circular1".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("b".to_string()),
object: Term::Variable("Y".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("a".to_string()),
object: Term::Variable("Y".to_string()),
}],
});
engine.add_rule(Rule {
name: "circular2".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("a".to_string()),
object: Term::Variable("Y".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("b".to_string()),
object: Term::Variable("Y".to_string()),
}],
});
let circular_facts = vec![RuleAtom::Triple {
subject: Term::Constant("x".to_string()),
predicate: Term::Constant("a".to_string()),
object: Term::Constant("y".to_string()),
}];
let results = engine.forward_chain(&circular_facts)?;
assert!(!results.is_empty());
Ok(())
}
#[test]
fn test_performance_scalability() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = RuleEngine::new();
engine.add_rule(Rule {
name: "simple_rule".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("input".to_string()),
object: Term::Variable("Y".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("output".to_string()),
object: Term::Variable("Y".to_string()),
}],
});
let mut large_facts = Vec::new();
for i in 0..1000 {
large_facts.push(RuleAtom::Triple {
subject: Term::Constant(format!("entity_{i}")),
predicate: Term::Constant("input".to_string()),
object: Term::Constant(format!("value_{i}")),
});
}
let start = std::time::Instant::now();
let results = engine.forward_chain(&large_facts)?;
let duration = start.elapsed();
assert!(duration.as_secs() < 1);
assert!(results.len() >= 2000); Ok(())
}
#[test]
fn test_cross_reasoning_compatibility() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = RuleEngine::new();
let mut rdfs_reasoner = RdfsReasoner::new();
let mut owl_reasoner = OwlReasoner::new();
let rdfs_facts = vec![
RuleAtom::Triple {
subject: Term::Constant("Student".to_string()),
predicate: Term::Constant(
"http://www.w3.org/2000/01/rdf-schema#subClassOf".to_string(),
),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
),
object: Term::Constant("Student".to_string()),
},
];
let rdfs_inferred = rdfs_reasoner.infer(&rdfs_facts)?;
let owl_inferred = owl_reasoner.infer(&rdfs_inferred)?;
engine.add_rule(Rule {
name: "person_rule".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant(
"http://www.w3.org/1999/02/22-rdf-syntax-ns#type".to_string(),
),
object: Term::Constant("Person".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("hasProperty".to_string()),
object: Term::Constant("conscious".to_string()),
}],
});
let final_results = engine.forward_chain(&owl_inferred)?;
assert!(final_results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "john" && p == "hasProperty" && o == "conscious")
}));
Ok(())
}
#[test]
fn test_complex_rule_interactions() -> Result<(), Box<dyn std::error::Error>> {
let mut engine = RuleEngine::new();
engine.add_rules(vec![
Rule {
name: "rule1".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("hasParent".to_string()),
object: Term::Variable("Y".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("Y".to_string()),
predicate: Term::Constant("hasChild".to_string()),
object: Term::Variable("X".to_string()),
}],
},
Rule {
name: "rule2".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("hasChild".to_string()),
object: Term::Variable("Y".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("isParent".to_string()),
object: Term::Constant("true".to_string()),
}],
},
Rule {
name: "rule3".to_string(),
body: vec![
RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("isParent".to_string()),
object: Term::Constant("true".to_string()),
},
RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("age".to_string()),
object: Term::Variable("A".to_string()),
},
],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("category".to_string()),
object: Term::Constant("adult_parent".to_string()),
}],
},
]);
let complex_facts = vec![
RuleAtom::Triple {
subject: Term::Constant("mary".to_string()),
predicate: Term::Constant("hasParent".to_string()),
object: Term::Constant("john".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant("age".to_string()),
object: Term::Constant("45".to_string()),
},
];
let results = engine.forward_chain(&complex_facts)?;
assert!(results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "john" && p == "category" && o == "adult_parent")
}));
Ok(())
}
#[test]
fn test_rdfs_subclass_transitivity() {
let mut rdfs_reasoner = RdfsReasoner::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let rdfs_subclass = "http://www.w3.org/2000/01/rdf-schema#subClassOf";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("Animal".to_string()),
predicate: Term::Constant(rdfs_subclass.to_string()),
object: Term::Constant("LivingThing".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("LivingThing".to_string()),
predicate: Term::Constant(rdfs_subclass.to_string()),
object: Term::Constant("Entity".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("cat".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant("Animal".to_string()),
},
];
let inferred = rdfs_reasoner
.infer(&facts)
.expect("RDFS inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "cat" && p == rdf_type && o == "LivingThing")
}),
"cat should be inferred as LivingThing"
);
assert!(
rdfs_reasoner.context.is_subclass_of("Animal", "Entity"),
"Animal should be transitive subClassOf Entity in RDFS context"
);
assert!(!inferred.is_empty(), "RDFS inference should produce facts");
}
#[test]
fn test_rdfs_domain_inference() {
let mut rdfs_reasoner = RdfsReasoner::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let rdfs_domain = "http://www.w3.org/2000/01/rdf-schema#domain";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("hasMother".to_string()),
predicate: Term::Constant(rdfs_domain.to_string()),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant("hasMother".to_string()),
object: Term::Constant("eve".to_string()),
},
];
let inferred = rdfs_reasoner
.infer(&facts)
.expect("RDFS domain inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "alice" && p == rdf_type && o == "Person")
}),
"alice should be inferred as Person via domain"
);
}
#[test]
fn test_rdfs_range_inference() {
let mut rdfs_reasoner = RdfsReasoner::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let rdfs_range = "http://www.w3.org/2000/01/rdf-schema#range";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("hasChild".to_string()),
predicate: Term::Constant(rdfs_range.to_string()),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("john".to_string()),
predicate: Term::Constant("hasChild".to_string()),
object: Term::Constant("bob".to_string()),
},
];
let inferred = rdfs_reasoner
.infer(&facts)
.expect("RDFS range inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "bob" && p == rdf_type && o == "Person")
}),
"bob should be inferred as Person via range"
);
}
#[test]
fn test_rdfs_subproperty_transitivity() {
let mut rdfs_reasoner = RdfsReasoner::new();
let rdfs_subproperty = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("hasFather".to_string()),
predicate: Term::Constant(rdfs_subproperty.to_string()),
object: Term::Constant("hasParent".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("hasParent".to_string()),
predicate: Term::Constant(rdfs_subproperty.to_string()),
object: Term::Constant("hasAncestor".to_string()),
},
];
let inferred = rdfs_reasoner
.infer(&facts)
.expect("RDFS subproperty inference should succeed");
assert!(
rdfs_reasoner
.context
.is_subproperty_of("hasFather", "hasAncestor"),
"hasFather should be transitive subPropertyOf hasAncestor in RDFS context"
);
assert!(!inferred.is_empty(), "RDFS inference should produce facts");
}
#[test]
fn test_rdfs_subproperty_inference() {
let mut rdfs_reasoner = RdfsReasoner::new();
let rdfs_subproperty = "http://www.w3.org/2000/01/rdf-schema#subPropertyOf";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("hasFather".to_string()),
predicate: Term::Constant(rdfs_subproperty.to_string()),
object: Term::Constant("hasParent".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant("hasFather".to_string()),
object: Term::Constant("bob".to_string()),
},
];
let inferred = rdfs_reasoner
.infer(&facts)
.expect("RDFS subproperty inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "alice" && p == "hasParent" && o == "bob")
}),
"alice hasParent bob should be inferred via subproperty rule"
);
}
#[test]
fn test_owl_transitive_property() {
let mut owl_reasoner = OwlReasoner::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let owl_transitive = "http://www.w3.org/2002/07/owl#TransitiveProperty";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("locatedIn".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant(owl_transitive.to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("Paris".to_string()),
predicate: Term::Constant("locatedIn".to_string()),
object: Term::Constant("France".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("France".to_string()),
predicate: Term::Constant("locatedIn".to_string()),
object: Term::Constant("Europe".to_string()),
},
];
let inferred = owl_reasoner
.infer(&facts)
.expect("OWL transitive inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "Paris" && p == "locatedIn" && o == "Europe")
}),
"Paris locatedIn Europe should be inferred via transitive property"
);
}
#[test]
fn test_owl_symmetric_property() {
let mut owl_reasoner = OwlReasoner::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let owl_symmetric = "http://www.w3.org/2002/07/owl#SymmetricProperty";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("isFriendOf".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant(owl_symmetric.to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant("isFriendOf".to_string()),
object: Term::Constant("bob".to_string()),
},
];
let inferred = owl_reasoner
.infer(&facts)
.expect("OWL symmetric inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "bob" && p == "isFriendOf" && o == "alice")
}),
"bob isFriendOf alice should be inferred via symmetric property"
);
}
#[test]
fn test_owl_inverse_property() {
let mut owl_reasoner = OwlReasoner::new();
let owl_inverse_of = "http://www.w3.org/2002/07/owl#inverseOf";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("isParentOf".to_string()),
predicate: Term::Constant(owl_inverse_of.to_string()),
object: Term::Constant("isChildOf".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant("isParentOf".to_string()),
object: Term::Constant("bob".to_string()),
},
];
let inferred = owl_reasoner
.infer(&facts)
.expect("OWL inverse inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "bob" && p == "isChildOf" && o == "alice")
}),
"bob isChildOf alice should be inferred via inverse property"
);
}
#[test]
fn test_owl_sameas_transitivity() {
let mut owl_reasoner = OwlReasoner::new();
let owl_same_as = "http://www.w3.org/2002/07/owl#sameAs";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("ResourceA".to_string()),
predicate: Term::Constant(owl_same_as.to_string()),
object: Term::Constant("ResourceB".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("ResourceB".to_string()),
predicate: Term::Constant(owl_same_as.to_string()),
object: Term::Constant("ResourceC".to_string()),
},
];
let inferred = owl_reasoner
.infer(&facts)
.expect("OWL sameAs inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "ResourceA" && p == owl_same_as && o == "ResourceC")
}),
"ResourceA sameAs ResourceC should be inferred via transitivity"
);
}
#[test]
fn test_owl_equivalent_class_deep() {
let mut owl_reasoner = OwlReasoner::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let owl_equiv_class = "http://www.w3.org/2002/07/owl#equivalentClass";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("Human".to_string()),
predicate: Term::Constant(owl_equiv_class.to_string()),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("Person".to_string()),
predicate: Term::Constant(owl_equiv_class.to_string()),
object: Term::Constant("HomoSapiens".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant("Human".to_string()),
},
];
let inferred = owl_reasoner
.infer(&facts)
.expect("OWL equivalent class inference should succeed");
assert!(
inferred.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "alice" && p == rdf_type && o == "Person")
}),
"alice should be inferred as Person via equivalent class"
);
}
#[test]
fn test_swrl_less_than_builtin() -> Result<(), Box<dyn std::error::Error>> {
let mut swrl_engine = SwrlEngine::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let swrl_rule = SwrlRule {
id: "minor_rule".to_string(),
body: vec![
SwrlAtom::Class {
class_predicate: "Person".to_string(),
argument: SwrlArgument::Variable("x".to_string()),
},
SwrlAtom::DatavalueProperty {
property_predicate: "hasAge".to_string(),
argument1: SwrlArgument::Variable("x".to_string()),
argument2: SwrlArgument::Variable("age".to_string()),
},
SwrlAtom::Builtin {
builtin_predicate: "http://www.w3.org/2003/11/swrlb#lessThan".to_string(),
arguments: vec![
SwrlArgument::Variable("age".to_string()),
SwrlArgument::Literal("18".to_string()),
],
},
],
head: vec![SwrlAtom::Class {
class_predicate: "Minor".to_string(),
argument: SwrlArgument::Variable("x".to_string()),
}],
metadata: std::collections::HashMap::new(),
};
swrl_engine
.add_rule(swrl_rule)
.expect("SWRL rule should be added");
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("tommy".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("tommy".to_string()),
predicate: Term::Constant("hasAge".to_string()),
object: Term::Literal("12".to_string()),
},
];
let results = swrl_engine
.execute(&facts)
.expect("SWRL execution should succeed");
assert!(
results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "tommy" && p == rdf_type && o == "Minor")
}),
"tommy should be inferred as Minor via SWRL lessThan builtin"
);
Ok(())
}
#[test]
fn test_swrl_individual_property_rule() -> Result<(), Box<dyn std::error::Error>> {
let mut swrl_engine = SwrlEngine::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let swrl_rule = SwrlRule {
id: "medical_professional_rule".to_string(),
body: vec![
SwrlAtom::Class {
class_predicate: "Doctor".to_string(),
argument: SwrlArgument::Variable("x".to_string()),
},
SwrlAtom::IndividualProperty {
property_predicate: "worksAt".to_string(),
argument1: SwrlArgument::Variable("x".to_string()),
argument2: SwrlArgument::Variable("h".to_string()),
},
SwrlAtom::Class {
class_predicate: "Hospital".to_string(),
argument: SwrlArgument::Variable("h".to_string()),
},
],
head: vec![SwrlAtom::Class {
class_predicate: "MedicalProfessional".to_string(),
argument: SwrlArgument::Variable("x".to_string()),
}],
metadata: std::collections::HashMap::new(),
};
swrl_engine
.add_rule(swrl_rule)
.expect("SWRL rule should be added");
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("drsmith".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant("Doctor".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("drsmith".to_string()),
predicate: Term::Constant("worksAt".to_string()),
object: Term::Constant("generalhosp".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("generalhosp".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant("Hospital".to_string()),
},
];
let results = swrl_engine
.execute(&facts)
.expect("SWRL execution should succeed");
assert!(
results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "drsmith" && p == rdf_type && o == "MedicalProfessional")
}),
"drsmith should be inferred as MedicalProfessional"
);
Ok(())
}
#[test]
fn test_swrl_same_individual_constraint() -> Result<(), Box<dyn std::error::Error>> {
let mut swrl_engine = SwrlEngine::new();
let rdf_type = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
let swrl_rule = SwrlRule {
id: "same_class_rule".to_string(),
body: vec![
SwrlAtom::Class {
class_predicate: "Person".to_string(),
argument: SwrlArgument::Variable("x".to_string()),
},
SwrlAtom::SameIndividual {
argument1: SwrlArgument::Variable("x".to_string()),
argument2: SwrlArgument::Variable("y".to_string()),
},
],
head: vec![SwrlAtom::Class {
class_predicate: "Person".to_string(),
argument: SwrlArgument::Variable("y".to_string()),
}],
metadata: std::collections::HashMap::new(),
};
swrl_engine
.add_rule(swrl_rule)
.expect("SWRL rule should be added");
let owl_same_as = "http://www.w3.org/2002/07/owl#sameAs";
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant(rdf_type.to_string()),
object: Term::Constant("Person".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant(owl_same_as.to_string()),
object: Term::Constant("alicia".to_string()),
},
];
let results = swrl_engine
.execute(&facts)
.expect("SWRL execution should succeed");
assert!(
results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "alicia" && p == rdf_type && o == "Person")
}),
"alicia should be inferred as Person via sameAs"
);
Ok(())
}
#[test]
fn test_three_level_rule_chain() {
let mut engine = RuleEngine::new();
engine.add_rules(vec![
Rule {
name: "node_to_processable".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Node".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("status".to_string()),
object: Term::Constant("processable".to_string()),
}],
},
Rule {
name: "processable_to_indexed".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("status".to_string()),
object: Term::Constant("processable".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("status".to_string()),
object: Term::Constant("indexed".to_string()),
}],
},
Rule {
name: "indexed_to_searchable".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("status".to_string()),
object: Term::Constant("indexed".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("status".to_string()),
object: Term::Constant("searchable".to_string()),
}],
},
]);
let facts = vec![RuleAtom::Triple {
subject: Term::Constant("doc1".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Node".to_string()),
}];
let results = engine
.forward_chain(&facts)
.expect("Rule chaining should succeed");
assert!(
results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "doc1" && p == "status" && o == "searchable")
}),
"doc1 should be searchable via three-step rule chain"
);
}
#[test]
fn test_rule_chain_multiple_entities() {
let mut engine = RuleEngine::new();
engine.add_rules(vec![
Rule {
name: "employee_to_worker".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("role".to_string()),
object: Term::Constant("employee".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("role".to_string()),
object: Term::Constant("worker".to_string()),
}],
},
Rule {
name: "worker_to_taxpayer".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("role".to_string()),
object: Term::Constant("worker".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("role".to_string()),
object: Term::Constant("taxpayer".to_string()),
}],
},
]);
let facts = vec![
RuleAtom::Triple {
subject: Term::Constant("alice".to_string()),
predicate: Term::Constant("role".to_string()),
object: Term::Constant("employee".to_string()),
},
RuleAtom::Triple {
subject: Term::Constant("bob".to_string()),
predicate: Term::Constant("role".to_string()),
object: Term::Constant("employee".to_string()),
},
];
let results = engine
.forward_chain(&facts)
.expect("Rule chain with multiple entities should succeed");
let alice_taxpayer = results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "alice" && p == "role" && o == "taxpayer")
});
let bob_taxpayer = results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "bob" && p == "role" && o == "taxpayer")
});
assert!(alice_taxpayer, "alice should be inferred as taxpayer");
assert!(bob_taxpayer, "bob should be inferred as taxpayer");
}
#[test]
fn test_classification_chain() {
let mut engine = RuleEngine::new();
engine.add_rules(vec![
Rule {
name: "scientist_to_researcher".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Scientist".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Researcher".to_string()),
}],
},
Rule {
name: "researcher_to_academic".to_string(),
body: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Researcher".to_string()),
}],
head: vec![RuleAtom::Triple {
subject: Term::Variable("X".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("AcademicProfessional".to_string()),
}],
},
]);
let facts = vec![RuleAtom::Triple {
subject: Term::Constant("einstein".to_string()),
predicate: Term::Constant("type".to_string()),
object: Term::Constant("Scientist".to_string()),
}];
let results = engine
.forward_chain(&facts)
.expect("Classification chain should succeed");
assert!(
results.iter().any(|fact| {
matches!(fact, RuleAtom::Triple {
subject: Term::Constant(s),
predicate: Term::Constant(p),
object: Term::Constant(o)
} if s == "einstein" && p == "type" && o == "AcademicProfessional")
}),
"einstein should be inferred as AcademicProfessional via classification chain"
);
}