use super::*;
fn make_universal_2arg(restrictor: &str, consequent: &str, fixed_entity: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let restrict = pred(
&mut nodes,
restrictor,
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let body = pred(
&mut nodes,
consequent,
vec![
LogicalTerm::Constant(fixed_entity.to_string()),
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let neg = not(&mut nodes, restrict);
let disj = or(&mut nodes, neg, body);
let root = forall(&mut nodes, "_v0", disj);
LogicBuffer {
nodes,
roots: vec![root],
}
}
#[test]
fn test_x2_conversion_universal_rule() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal_2arg("gerku", "nelci", "bob"));
let mut nodes = Vec::new();
let root = pred(
&mut nodes,
"nelci",
vec![
LogicalTerm::Constant("bob".to_string()),
LogicalTerm::Constant("alis".to_string()),
LogicalTerm::Unspecified,
],
);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_x2_conversion_universal_multiple_entities() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("rex", "gerku"));
assert_buf(&kb, make_universal_2arg("gerku", "nelci", "bob"));
let mut n1 = Vec::new();
let r1 = pred(
&mut n1,
"nelci",
vec![
LogicalTerm::Constant("bob".to_string()),
LogicalTerm::Constant("alis".to_string()),
LogicalTerm::Unspecified,
],
);
assert!(query(
&kb,
LogicBuffer {
nodes: n1,
roots: vec![r1]
}
));
let mut n2 = Vec::new();
let r2 = pred(
&mut n2,
"nelci",
vec![
LogicalTerm::Constant("bob".to_string()),
LogicalTerm::Constant("rex".to_string()),
LogicalTerm::Unspecified,
],
);
assert!(query(
&kb,
LogicBuffer {
nodes: n2,
roots: vec![r2]
}
));
let mut n3 = Vec::new();
let r3 = pred(
&mut n3,
"nelci",
vec![
LogicalTerm::Constant("bob".to_string()),
LogicalTerm::Constant("carol".to_string()),
LogicalTerm::Unspecified,
],
);
assert!(query_false(
&kb,
LogicBuffer {
nodes: n3,
roots: vec![r3]
}
));
}
#[test]
fn test_targeted_witness_search_with_fixed_entity() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal_2arg("gerku", "nelci", "bob"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"nelci",
vec![
LogicalTerm::Constant("bob".to_string()),
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
let results = query_find(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert!(results.len() >= 1);
let found: Vec<String> = results
.iter()
.filter_map(|bs| match &bs[0].term {
LogicalTerm::Constant(c) => Some(c.clone()),
_ => None,
})
.collect();
assert!(
found.contains(&"alis".to_string()),
"alis should be a witness"
);
}
#[test]
fn test_targeted_witness_search_multiple_matches() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("rex", "gerku"));
assert_buf(&kb, make_universal_2arg("gerku", "nelci", "bob"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"nelci",
vec![
LogicalTerm::Constant("bob".to_string()),
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
let results = query_find(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert!(results.len() >= 2);
let found: Vec<String> = results
.iter()
.filter_map(|bs| match &bs[0].term {
LogicalTerm::Constant(c) => Some(c.clone()),
_ => None,
})
.collect();
assert!(
found.contains(&"alis".to_string()),
"alis should be a witness"
);
assert!(
found.contains(&"rex".to_string()),
"rex should be a witness"
);
}
#[test]
fn test_conjunction_introduction_multiple_entities() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("alis", "barda"));
assert_buf(&kb, make_assertion("bob", "mlatu"));
assert_buf(&kb, make_assertion("bob", "cmalu"));
assert!(query_conjunction(&kb, "gerku", "alis", "barda", "alis"));
assert!(query_conjunction(&kb, "mlatu", "bob", "cmalu", "bob"));
assert!(query_conjunction(&kb, "gerku", "alis", "mlatu", "bob"));
}
#[test]
fn test_kb_reset_clears_facts() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(query(&kb, make_query("alis", "gerku")));
kb.inner.borrow_mut().reset();
assert!(query_false(&kb, make_query("alis", "gerku")));
}
#[test]
fn test_kb_reset_clears_rules() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal("gerku", "danlu"));
assert!(query(&kb, make_query("alis", "danlu")));
kb.inner.borrow_mut().reset();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(query_false(&kb, make_query("alis", "danlu")));
}
#[test]
fn test_kb_reset_resets_skolem_counter() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
let counter_before = kb.inner.borrow().skolem_counter;
assert!(counter_before > 0);
kb.inner.borrow_mut().reset();
assert_eq!(kb.inner.borrow().skolem_counter, 0);
}
#[test]
fn test_query_with_no_facts() {
let kb = new_kb();
assert!(query_false(&kb, make_query("alis", "gerku")));
}
#[test]
fn test_assert_and_query_same_fact_twice() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(query(&kb, make_query("alis", "gerku")));
}
#[test]
fn test_disjunction_left_true() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
let mut nodes = Vec::new();
let left = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Constant("alis".into()),
LogicalTerm::Unspecified,
],
);
let right = pred(
&mut nodes,
"mlatu",
vec![
LogicalTerm::Constant("alis".into()),
LogicalTerm::Unspecified,
],
);
let root = or(&mut nodes, left, right);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_disjunction_right_true() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "mlatu"));
let mut nodes = Vec::new();
let left = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Constant("alis".into()),
LogicalTerm::Unspecified,
],
);
let right = pred(
&mut nodes,
"mlatu",
vec![
LogicalTerm::Constant("alis".into()),
LogicalTerm::Unspecified,
],
);
let root = or(&mut nodes, left, right);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_disjunction_both_false() {
let kb = new_kb();
let mut nodes = Vec::new();
let left = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Constant("alis".into()),
LogicalTerm::Unspecified,
],
);
let right = pred(
&mut nodes,
"mlatu",
vec![
LogicalTerm::Constant("alis".into()),
LogicalTerm::Unspecified,
],
);
let root = or(&mut nodes, left, right);
assert!(query_false(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn test_double_negation_elimination() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
let mut nodes = Vec::new();
let inner = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Constant("alis".into()),
LogicalTerm::Unspecified,
],
);
let neg1 = not(&mut nodes, inner);
let root = not(&mut nodes, neg1);
assert!(query(
&kb,
LogicBuffer {
nodes,
roots: vec![root]
}
));
}
#[test]
fn stratification_rollback_pops_flat_plus_group_edges() {
let kb = new_kb();
let pv = || GroundTerm::PatternVar("x__v0".to_string());
{
let mut inner = kb.inner.borrow_mut();
let result = crate::rules::register_rule(
&mut inner,
"dog <- ~dog + ~exists-person-group".to_string(),
vec!["x__v0".to_string()],
vec![StoredFact::Bare(GroundFact::new("dog", vec![pv()]))],
vec![StoredFact::Bare(GroundFact::new("dog", vec![pv()]))],
vec![0],
vec![crate::kb::NegatedExistsGroup {
conditions: vec![StoredFact::Bare(GroundFact::new(
"person",
vec![GroundTerm::PatternVar("ev__g0".to_string())],
))],
event_var: "ev__g0".to_string(),
}],
false,
);
assert!(
result.is_err(),
"the negative self-loop must be rejected as unstratifiable"
);
}
assert_buf(&kb, make_universal("dog", "animal"));
assert_buf(&kb, make_assertion("rex", "dog"));
assert!(
query(&kb, make_query("rex", "animal")),
"the post-rollback rule must register and derive animal(rex)"
);
}
#[test]
fn bare_universal_second_witness_base_survives_registry_dedup() {
let kb = new_kb();
assert_buf(&kb, make_dependent_skolem_universal("dog", "loves"));
let mut nodes = Vec::new();
let g = pred(
&mut nodes,
"gives",
vec![
LogicalTerm::Variable("_u0".to_string()),
LogicalTerm::Variable("_y0".to_string()),
LogicalTerm::Variable("_w0".to_string()),
],
);
let e = exists(&mut nodes, "_y0", g);
let fw = forall(&mut nodes, "_w0", e);
let root = forall(&mut nodes, "_u0", fw);
assert_buf(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert_buf(&kb, make_assertion("adam", "dog"));
assert_buf(&kb, make_assertion("rex", "cat"));
let mut nodes = Vec::new();
let q = pred(
&mut nodes,
"gives",
vec![
LogicalTerm::Constant("adam".to_string()),
LogicalTerm::Variable("y".to_string()),
LogicalTerm::Constant("rex".to_string()),
],
);
let root = exists(&mut nodes, "y", q);
assert!(
query(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
),
"the bare universal's (u, w)-dependent witness must derive gives(adam, y, rex)"
);
}
#[test]
fn naf_groups_skipped_when_positive_conditions_definitively_fail() {
let kb = new_kb();
assert_buf(&kb, compile_surface("beautiful(every dog where ~cat(it))."));
assert_buf(&kb, compile_surface("dog(every wolf)."));
assert_buf(&kb, compile_surface("cat(every animal)."));
assert_buf(&kb, compile_surface("animal(every cat)."));
assert_buf(&kb, compile_surface("person(Bel)."));
assert_buf(&kb, compile_surface("cat(Kim)."));
assert!(
query_false(&kb, compile_surface("beautiful(Bel).")),
"a definitively failed restrictor must yield definitive FALSE, not Unknown"
);
}
#[test]
fn flat_naf_group_skipped_when_positive_condition_definitively_fails() {
let kb = new_kb();
kb.set_existential_import(false);
assert_buf(&kb, make_universal("animal", "cat"));
assert_buf(&kb, make_universal("cat", "animal"));
assert_buf(&kb, make_assertion("bel", "person"));
{
let mut inner = kb.inner.borrow_mut();
let pv = |n: &str| GroundTerm::PatternVar(n.to_string());
crate::rules::register_rule(
&mut inner,
"beautiful <- dog + ~exists-cat".to_string(),
vec!["x__v0".to_string()],
vec![StoredFact::Bare(GroundFact::new(
"dog",
vec![pv("x__v0"), GroundTerm::Unspecified],
))],
vec![StoredFact::Bare(GroundFact::new(
"beautiful",
vec![pv("x__v0"), GroundTerm::Unspecified],
))],
vec![],
vec![crate::kb::NegatedExistsGroup {
conditions: vec![StoredFact::Bare(GroundFact::new(
"cat",
vec![pv("ev__g0"), GroundTerm::Unspecified],
))],
event_var: "ev__g0".to_string(),
}],
false,
)
.expect("the flat NAF-group rule must register (stratifiable)");
}
assert!(
query_false(&kb, make_query("bel", "beautiful")),
"a definitively failed flat condition must yield definitive FALSE, not Unknown"
);
}