use super::*;
#[test]
fn test_proof_trace_simple_predicate() {
let kb = new_kb();
assert_buf(&kb, make_assertion("mi", "klama"));
let (result, trace) = query_with_proof(&kb, make_query("mi", "klama"));
assert!(result);
assert!(!trace.steps.is_empty());
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Asserted { .. }));
}
#[test]
fn test_proof_trace_false_predicate() {
let kb = new_kb();
let (result, trace) = kb
.query_entailment_with_proof_inner(make_query("mi", "klama"))
.unwrap();
assert!(result.is_false());
let root_step = &trace.steps[trace.root as usize];
assert!(!root_step.holds);
assert!(
matches!(&root_step.rule, ProofRule::PredicateNotFound { .. }),
"expected PredicateNotFound, got {:?}",
root_step.rule
);
}
#[test]
fn test_proof_trace_conjunction() {
let kb = new_kb();
assert_buf(&kb, make_assertion("mi", "klama"));
assert_buf(&kb, make_assertion("mi", "prami"));
let mut nodes = Vec::new();
let p1 = pred(
&mut nodes,
"klama",
vec![LogicalTerm::Constant("mi".into()), LogicalTerm::Unspecified],
);
let p2 = pred(
&mut nodes,
"prami",
vec![LogicalTerm::Constant("mi".into()), LogicalTerm::Unspecified],
);
let root = and(&mut nodes, p1, p2);
let (result, trace) = query_with_proof(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Conjunction));
assert_eq!(root_step.children.len(), 2);
for &child in &root_step.children {
let child_step = &trace.steps[child as usize];
assert!(child_step.holds);
assert!(matches!(&child_step.rule, ProofRule::Asserted { .. }));
}
}
#[test]
fn test_proof_trace_negation() {
let kb = new_kb();
let mut nodes = Vec::new();
let inner = pred(
&mut nodes,
"klama",
vec![LogicalTerm::Constant("mi".into()), LogicalTerm::Unspecified],
);
let root = not(&mut nodes, inner);
let (result, trace) = query_with_proof(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Negation));
assert_eq!(root_step.children.len(), 1);
let inner_step = &trace.steps[root_step.children[0] as usize];
assert!(!inner_step.holds);
}
#[test]
fn test_proof_trace_exists_witness() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "klama"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"klama",
vec![LogicalTerm::Variable("x".into()), LogicalTerm::Unspecified],
);
let root = exists(&mut nodes, "x", body);
let (result, trace) = query_with_proof(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::ExistsWitness { .. }));
if let ProofRule::ExistsWitness { var, term } = &root_step.rule {
assert_eq!(var, "x");
assert!(matches!(term, LogicalTerm::Constant(c) if c == "alis"));
}
}
#[test]
fn test_proof_trace_exists_failed() {
let kb = new_kb();
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"klama",
vec![LogicalTerm::Variable("x".into()), LogicalTerm::Unspecified],
);
let root = exists(&mut nodes, "x", body);
let (result, trace) = kb
.query_entailment_with_proof_inner(LogicBuffer {
nodes,
roots: vec![root],
})
.unwrap();
assert!(result.is_false());
let root_step = &trace.steps[trace.root as usize];
assert!(!root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::ExistsFailed));
}
#[test]
fn test_proof_trace_forall() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("bob", "gerku"));
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
"gerku",
vec![LogicalTerm::Variable("x".into()), LogicalTerm::Unspecified],
);
let root = forall(&mut nodes, "x", body);
let (result, trace) = query_with_proof(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::ForallVerified { .. }));
if let ProofRule::ForallVerified { entities } = &root_step.rule {
assert_eq!(entities.len(), 2);
}
for &child in &root_step.children {
let child_step = &trace.steps[child as usize];
assert!(child_step.holds);
}
}
#[test]
fn cwa_false_flag_set_for_absence_false() {
let kb = new_kb();
let (verdict, trace) = kb
.query_entailment_with_proof_inner(make_query("bob", "danlu"))
.unwrap();
assert!(
verdict.is_false(),
"a missing fact must be FALSE: got {verdict:?}"
);
assert!(
trace.cwa_false,
"an absence-driven FALSE must set cwa_false (the closed-world caveat)"
);
}
#[test]
fn cwa_cda_relativity_of_false_and_forall_verified() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("bob", "gerku"));
assert_buf(&kb, make_assertion("alis", "danlu"));
assert_buf(&kb, make_assertion("bob", "danlu"));
assert_eq!(
query_result(&kb, make_query("carl", "danlu")),
QueryResult::False,
"a missing fact must be FALSE under the closed-world assumption, not Unknown"
);
let (verdict, trace) = kb
.query_entailment_with_proof_inner(make_universal("gerku", "danlu"))
.unwrap();
assert_eq!(
verdict,
QueryResult::True,
"a ∀ satisfied across the whole closed domain must be TRUE"
);
let root_rule = &trace.steps[trace.root as usize].rule;
assert!(
matches!(root_rule, ProofRule::ForallVerified { entities } if entities.len() == 2),
"the True verdict must be a ForallVerified over the 2 known individuals, got {root_rule:?}"
);
assert_buf(&kb, make_assertion("carl", "gerku")); let (verdict2, trace2) = kb
.query_entailment_with_proof_inner(make_universal("gerku", "danlu"))
.unwrap();
assert_eq!(
verdict2,
QueryResult::False,
"adding a counterexample to the closed domain must flip the ∀ to FALSE"
);
assert!(
matches!(
&trace2.steps[trace2.root as usize].rule,
ProofRule::ForallCounterexample { .. }
),
"the flipped verdict must be a ForallCounterexample over the new member"
);
}
#[test]
fn test_proof_trace_asserted_fact() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
let (result, trace) = query_with_proof(&kb, make_query("alis", "gerku"));
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Asserted { .. }));
if let ProofRule::Asserted { fact } = &root_step.rule {
assert!(fact.contains("gerku"));
assert!(fact.contains("alis"));
}
}
#[test]
fn test_proof_trace_single_hop_derived() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal("gerku", "danlu"));
let (result, trace) = query_with_proof(&kb, make_query("alis", "danlu"));
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Derived { .. }));
if let ProofRule::Derived { label, fact } = &root_step.rule {
assert!(fact.contains("danlu"));
assert!(label.contains("gerku"));
assert!(label.contains("danlu"));
}
assert_eq!(root_step.children.len(), 1);
let child_step = &trace.steps[root_step.children[0] as usize];
assert!(child_step.holds);
assert!(matches!(&child_step.rule, ProofRule::Asserted { .. }));
}
#[test]
fn test_proof_trace_multi_hop_derived() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_universal("danlu", "xanlu"));
let (result, trace) = query_with_proof(&kb, make_query("alis", "xanlu"));
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Derived { .. }));
if let ProofRule::Derived { label, .. } = &root_step.rule {
assert!(label.contains("xanlu"));
}
assert_eq!(root_step.children.len(), 1);
let mid_step = &trace.steps[root_step.children[0] as usize];
assert!(mid_step.holds);
assert!(matches!(&mid_step.rule, ProofRule::Derived { .. }));
assert_eq!(mid_step.children.len(), 1);
let leaf_step = &trace.steps[mid_step.children[0] as usize];
assert!(leaf_step.holds);
assert!(matches!(&leaf_step.rule, ProofRule::Asserted { .. }));
}
#[test]
fn test_proof_trace_derived_depth_limit() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_universal("gerku", "gerku"));
let (result, trace) = query_with_proof(&kb, make_query("alis", "gerku"));
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Asserted { .. }));
}
#[test]
fn test_proof_trace_existential_import_presup_is_asserted() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
let (result, trace) = query_with_proof(&kb, make_query("sk_0", "gerku"));
assert!(result);
let root_step = &trace.steps[trace.root as usize];
assert!(root_step.holds);
assert!(matches!(&root_step.rule, ProofRule::Asserted { .. }));
}
#[test]
fn test_conjunction_introduction_basic() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("alis", "barda"));
assert!(
query_conjunction(&kb, "gerku", "alis", "barda", "alis"),
"And(gerku(alis), barda(alis)) should hold"
);
assert!(
query_conjunction(&kb, "barda", "alis", "gerku", "alis"),
"And(barda(alis), gerku(alis)) should hold (commutativity)"
);
}
#[test]
fn test_conjunction_both_individually_true() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("bob", "mlatu"));
assert!(
query_conjunction(&kb, "gerku", "alis", "mlatu", "bob"),
"And(gerku(alis), mlatu(bob)) should hold when both are individually true"
);
}
#[test]
fn test_conjunction_introduction_with_derived() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu")); assert_buf(&kb, make_assertion("alis", "gerku")); assert_buf(&kb, make_assertion("alis", "barda"));
assert!(
query_conjunction(&kb, "danlu", "alis", "barda", "alis"),
"And(danlu(alis), barda(alis)) should hold via rule + conjunction"
);
assert!(
query_conjunction(&kb, "gerku", "alis", "danlu", "alis"),
"And(gerku(alis), danlu(alis)) should hold"
);
}
#[test]
fn test_conjunction_introduction_cross_position() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
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_buf(
&kb,
LogicBuffer {
nodes,
roots: vec![root],
},
);
let mut nodes2 = Vec::new();
let p1 = pred(
&mut nodes2,
"gerku",
vec![
LogicalTerm::Constant("alis".to_string()),
LogicalTerm::Unspecified,
],
);
let p2 = pred(
&mut nodes2,
"nelci",
vec![
LogicalTerm::Constant("bob".to_string()),
LogicalTerm::Constant("alis".to_string()),
LogicalTerm::Unspecified,
],
);
let root2 = and(&mut nodes2, p1, p2);
assert!(
query(
&kb,
LogicBuffer {
nodes: nodes2,
roots: vec![root2]
}
),
"Cross-position entity sharing should allow conjunction query"
);
}