use super::*;
use nibli_types::logic::{LogicBuffer, LogicNode, LogicalTerm, ProofRule, ProofTrace};
fn pred(nodes: &mut Vec<LogicNode>, rel: &str, args: Vec<LogicalTerm>) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::Predicate((rel.to_string(), args)));
id
}
fn not(nodes: &mut Vec<LogicNode>, inner: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::NotNode(inner));
id
}
fn or(nodes: &mut Vec<LogicNode>, left: u32, right: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::OrNode((left, right)));
id
}
fn forall(nodes: &mut Vec<LogicNode>, var: &str, body: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::ForAllNode((var.to_string(), body)));
id
}
fn exists(nodes: &mut Vec<LogicNode>, var: &str, body: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::ExistsNode((var.to_string(), body)));
id
}
fn and(nodes: &mut Vec<LogicNode>, left: u32, right: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::AndNode((left, right)));
id
}
fn new_kb() -> KnowledgeBase {
KnowledgeBase {
inner: RefCell::new(KnowledgeBaseInner::new()),
}
}
fn assert_buf(kb: &KnowledgeBase, buf: LogicBuffer) {
kb.assert_fact_inner(buf, String::new()).unwrap();
}
fn assert_id(kb: &KnowledgeBase, buf: LogicBuffer, label: impl Into<String>) -> u64 {
kb.assert_fact_inner(buf, label.into()).unwrap()
}
fn query(kb: &KnowledgeBase, buf: LogicBuffer) -> bool {
kb.query_entailment_inner(buf).unwrap().is_true()
}
fn query_false(kb: &KnowledgeBase, buf: LogicBuffer) -> bool {
kb.query_entailment_inner(buf).unwrap().is_false()
}
fn query_result(kb: &KnowledgeBase, buf: LogicBuffer) -> QueryResult {
kb.query_entailment_inner(buf).unwrap()
}
fn compile_surface(text: &str) -> LogicBuffer {
let ast = nibli_kr::parse_checked(text).unwrap_or_else(|e| panic!("parse '{text}': {e}"));
let mut buf =
nibli_semantics::compile_from_ast(ast).unwrap_or_else(|e| panic!("compile '{text}': {e}"));
transform_compute_nodes(&mut buf, &default_compute_predicates());
buf
}
#[test]
fn compile_surface_smoke() {
let kb = new_kb();
assert_buf(&kb, compile_surface("dog(Adam)."));
assert!(query(&kb, compile_surface("dog(Adam).")));
assert!(query_false(&kb, compile_surface("cat(Adam).")));
let buf = compile_surface("dog(Adam).");
assert!(
buf.nodes.iter().any(|n| matches!(n,
LogicNode::Predicate((rel, _)) if rel == "dog_x1")),
"surface compile must event-decompose (expected a dog_x1 role predicate): {buf:?}"
);
}
fn make_assertion(entity: &str, predicate: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let root = pred(
&mut nodes,
predicate,
vec![
LogicalTerm::Constant(entity.to_string()),
LogicalTerm::Unspecified,
],
);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_universal(restrictor: &str, consequent: &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::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],
}
}
fn make_query(entity: &str, predicate: &str) -> LogicBuffer {
make_assertion(entity, predicate)
}
fn make_universal_naf(pos: &str, neg: &str, consequent: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let p = pred(
&mut nodes,
pos,
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let n = pred(
&mut nodes,
neg,
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let not_n = not(&mut nodes, n);
let ante = and(&mut nodes, p, not_n);
let body = pred(
&mut nodes,
consequent,
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let not_ante = not(&mut nodes, ante);
let disj = or(&mut nodes, not_ante, body);
let root = forall(&mut nodes, "_v0", disj);
LogicBuffer {
nodes,
roots: vec![root],
}
}
#[test]
fn test_native_rule_simple_universal() {
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")));
}
#[test]
fn test_native_rule_entity_after_rule() {
let kb = new_kb();
assert_buf(&kb, make_universal("gerku", "danlu"));
assert_buf(&kb, make_assertion("alis", "gerku"));
assert!(query(&kb, make_query("alis", "danlu")));
}
#[test]
fn test_native_rule_selective_application() {
let kb = new_kb();
assert_buf(&kb, make_assertion("alis", "gerku"));
assert_buf(&kb, make_assertion("bob", "mlatu"));
assert_buf(&kb, make_universal("gerku", "danlu"));
assert!(query(&kb, make_query("alis", "danlu")));
assert!(query_false(&kb, make_query("bob", "danlu")));
}
mod assertions;
mod compute_ingest;
mod conditionals_deontic;
mod descriptions_events;
mod disjunctive;
mod existential;
mod find_aggregates;
mod materialization;
mod memo_regressions;
mod numeric_compute;
mod provenance;
mod retraction_negation;
mod rules_edges;
mod skolem;
mod strict;
mod tense;
mod traces;
mod unify_equality;
mod witnesses;
mod zoo;
mod flat_vs_surface;
fn assert_proof_refs_resolve_to_holds_true(trace: &ProofTrace) {
for (i, step) in trace.steps.iter().enumerate() {
if matches!(step.rule, ProofRule::ProofRef { .. }) {
let target = step.children[0] as usize;
assert!(
trace.steps[target].holds,
"ProofRef step #{i} resolves to a holds:false step #{target} ({:?}) — \
stale not-found memo poisoning",
trace.steps[target].rule
);
}
}
}
fn constraint_fact(rel: &str, entity: &str) -> StoredFact {
StoredFact::Bare(GroundFact::new(
rel,
vec![
GroundTerm::Constant(entity.to_string()),
GroundTerm::Unspecified,
],
))
}
fn make_equals(a: &str, b: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let root = pred(
&mut nodes,
"equals",
vec![
LogicalTerm::Constant(a.to_string()),
LogicalTerm::Constant(b.to_string()),
],
);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_material_cond(concl: &str, cond: &str, neg: bool) -> LogicBuffer {
let mut nodes = Vec::new();
let cond_pred = pred(&mut nodes, cond, vec![LogicalTerm::Constant("x".into())]);
let not_cond = not(&mut nodes, cond_pred);
let antecedent = if neg {
not(&mut nodes, not_cond)
} else {
not_cond
};
let concl_pred = pred(&mut nodes, concl, vec![LogicalTerm::Constant("x".into())]);
let root = or(&mut nodes, antecedent, concl_pred);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn count(nodes: &mut Vec<LogicNode>, var: &str, cnt: u32, body: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::CountNode((var.to_string(), cnt, body)));
id
}
fn make_compute_query(rel: &str, x1: f64, x2: f64, x3: f64) -> LogicBuffer {
let mut nodes = Vec::new();
let root = compute(
&mut nodes,
rel,
vec![
LogicalTerm::Number(x1),
LogicalTerm::Number(x2),
LogicalTerm::Number(x3),
],
);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_numeric_query(relation: &str, a: f64, b: f64) -> LogicBuffer {
let mut nodes = Vec::new();
let root = make_numeric_pred(&mut nodes, relation, a, b);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn compute(nodes: &mut Vec<LogicNode>, rel: &str, args: Vec<LogicalTerm>) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::ComputeNode((rel.to_string(), args)));
id
}
fn make_dependent_skolem_universal(restrictor: &str, consequent: &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::Variable("_v0".to_string()),
LogicalTerm::Variable("_v1".to_string()),
LogicalTerm::Unspecified,
],
);
let ex = exists(&mut nodes, "_v1", body);
let neg = not(&mut nodes, restrict);
let disj = or(&mut nodes, neg, ex);
let root = forall(&mut nodes, "_v0", disj);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_event_assertion(entity: &str, predicate: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let p_type = pred(
&mut nodes,
predicate,
vec![LogicalTerm::Variable("_ev0".to_string())],
);
let p_role = pred(
&mut nodes,
&format!("{}_x1", predicate),
vec![
LogicalTerm::Variable("_ev0".to_string()),
LogicalTerm::Constant(entity.to_string()),
],
);
let p_and = and(&mut nodes, p_type, p_role);
let root = exists(&mut nodes, "_ev0", p_and);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_event_query(entity: &str, predicate: &str) -> LogicBuffer {
make_event_assertion(entity, predicate)
}
fn make_event_universal(restrictor: &str, consequent: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let p_type = pred(
&mut nodes,
restrictor,
vec![LogicalTerm::Variable("_ev0".to_string())],
);
let p_role = pred(
&mut nodes,
&format!("{}_x1", restrictor),
vec![
LogicalTerm::Variable("_ev0".to_string()),
LogicalTerm::Variable("_v0".to_string()),
],
);
let p_and = and(&mut nodes, p_type, p_role);
let p_exists = exists(&mut nodes, "_ev0", p_and);
let q_type = pred(
&mut nodes,
consequent,
vec![LogicalTerm::Variable("_ev1".to_string())],
);
let q_role = pred(
&mut nodes,
&format!("{}_x1", consequent),
vec![
LogicalTerm::Variable("_ev1".to_string()),
LogicalTerm::Variable("_v0".to_string()),
],
);
let q_and = and(&mut nodes, q_type, q_role);
let q_exists = exists(&mut nodes, "_ev1", q_and);
let neg = not(&mut nodes, p_exists);
let disj = or(&mut nodes, neg, q_exists);
let root = forall(&mut nodes, "_v0", disj);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_find_query(predicate: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let body = pred(
&mut nodes,
predicate,
vec![
LogicalTerm::Variable("x".to_string()),
LogicalTerm::Unspecified,
],
);
let root = exists(&mut nodes, "x", body);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_negated_antecedent_rule() -> LogicBuffer {
let mut nodes = Vec::new();
let dog = pred(
&mut nodes,
"gerku",
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let mlatu = pred(
&mut nodes,
"mlatu",
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let neg_mlatu = not(&mut nodes, mlatu);
let antecedent = and(&mut nodes, dog, neg_mlatu);
let danlu = pred(
&mut nodes,
"danlu",
vec![
LogicalTerm::Variable("_v0".to_string()),
LogicalTerm::Unspecified,
],
);
let neg_antecedent = not(&mut nodes, antecedent);
let disj = or(&mut nodes, neg_antecedent, danlu);
let root = forall(&mut nodes, "_v0", disj);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_negated_assertion(entity: &str, predicate: &str) -> LogicBuffer {
let mut nodes = Vec::new();
let inner = pred(
&mut nodes,
predicate,
vec![
LogicalTerm::Constant(entity.to_string()),
LogicalTerm::Unspecified,
],
);
let root = not(&mut nodes, inner);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_temporal_event_assertion(
entity: &str,
predicate: &str,
tense_fn: fn(&mut Vec<LogicNode>, u32) -> u32,
) -> LogicBuffer {
let mut nodes = Vec::new();
let p_type = pred(
&mut nodes,
predicate,
vec![LogicalTerm::Variable("_ev0".to_string())],
);
let p_role = pred(
&mut nodes,
&format!("{}_x1", predicate),
vec![
LogicalTerm::Variable("_ev0".to_string()),
LogicalTerm::Constant(entity.to_string()),
],
);
let p_and = and(&mut nodes, p_type, p_role);
let p_exists = exists(&mut nodes, "_ev0", p_and);
let root = tense_fn(&mut nodes, p_exists);
LogicBuffer {
nodes,
roots: vec![root],
}
}
fn make_temporal_event_query(
entity: &str,
predicate: &str,
tense_fn: fn(&mut Vec<LogicNode>, u32) -> u32,
) -> LogicBuffer {
make_temporal_event_assertion(entity, predicate, tense_fn)
}
fn obligatory(nodes: &mut Vec<LogicNode>, inner: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::ObligatoryNode(inner));
id
}
fn past(nodes: &mut Vec<LogicNode>, inner: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::PastNode(inner));
id
}
fn present(nodes: &mut Vec<LogicNode>, inner: u32) -> u32 {
let id = nodes.len() as u32;
nodes.push(LogicNode::PresentNode(inner));
id
}
fn query_conjunction(
kb: &KnowledgeBase,
pred1: &str,
entity1: &str,
pred2: &str,
entity2: &str,
) -> bool {
let mut nodes = Vec::new();
let p1 = pred(
&mut nodes,
pred1,
vec![
LogicalTerm::Constant(entity1.to_string()),
LogicalTerm::Unspecified,
],
);
let p2 = pred(
&mut nodes,
pred2,
vec![
LogicalTerm::Constant(entity2.to_string()),
LogicalTerm::Unspecified,
],
);
let root = and(&mut nodes, p1, p2);
query(
kb,
LogicBuffer {
nodes,
roots: vec![root],
},
)
}
fn query_with_proof(kb: &KnowledgeBase, buf: LogicBuffer) -> (bool, ProofTrace) {
let (result, trace) = kb.query_entailment_with_proof_inner(buf).unwrap();
(result.is_true(), trace)
}
fn query_find(kb: &KnowledgeBase, buf: LogicBuffer) -> Vec<Vec<WitnessBinding>> {
kb.query_find_inner(buf).unwrap()
}
fn make_numeric_pred(nodes: &mut Vec<LogicNode>, relation: &str, a: f64, b: f64) -> u32 {
let mut args = vec![
LogicalTerm::Number(a),
LogicalTerm::Number(b),
LogicalTerm::Unspecified,
];
if relation == "greater" || relation == "less" {
args.push(LogicalTerm::Unspecified);
}
pred(nodes, relation, args)
}