use super::{StratificationError, stratify};
use crate::ir::{
Atom, ConstantSymbol, Literal, PredicateSymbol, Program, Rule, Term, VariableSymbol,
};
fn var(name: &str) -> Term {
Term::Variable(VariableSymbol::new(name))
}
fn atom(predicate: &str, terms: Vec<Term>) -> Atom {
Atom {
predicate: PredicateSymbol::new(predicate),
terms,
}
}
fn pred(name: &str) -> PredicateSymbol {
PredicateSymbol::new(name)
}
#[test]
fn positive_program_is_single_stratum() {
let program = Program {
rules: vec![
Rule::positive(
atom("ancestor", vec![var("x"), var("y")]),
vec![atom("parent", vec![var("x"), var("y")])],
),
Rule::positive(
atom("ancestor", vec![var("x"), var("z")]),
vec![
atom("parent", vec![var("x"), var("y")]),
atom("ancestor", vec![var("y"), var("z")]),
],
),
],
};
let strat = stratify(&program).expect("positive program is stratifiable");
assert_eq!(strat.strata.len(), 1);
assert_eq!(strat.stratum_of(&pred("parent")), Some(0));
assert_eq!(strat.stratum_of(&pred("ancestor")), Some(0));
}
#[test]
fn simple_stratified_negation() {
let program = Program {
rules: vec![Rule::new(
atom("bachelor", vec![var("x")]),
vec![
Literal::Positive(atom("person", vec![var("x")])),
Literal::Negative(atom("married", vec![var("x")])),
],
)],
};
let strat = stratify(&program).expect("program is stratified");
let married_s = strat.stratum_of(&pred("married")).unwrap();
let bachelor_s = strat.stratum_of(&pred("bachelor")).unwrap();
assert!(bachelor_s > married_s);
}
#[test]
fn recursion_through_negation_is_rejected() {
let program = Program {
rules: vec![Rule::new(
atom("p", vec![var("x")]),
vec![
Literal::Positive(atom("q", vec![var("x")])),
Literal::Negative(atom("p", vec![var("x")])),
],
)],
};
let err = stratify(&program).expect_err("unstratifiable");
assert!(matches!(
err,
StratificationError::UnstratifiableCycle { .. }
));
}
#[test]
fn mutual_recursion_through_negation_is_rejected() {
let program = Program {
rules: vec![
Rule::new(
atom("a", vec![var("x")]),
vec![
Literal::Positive(atom("b", vec![var("x")])),
Literal::Negative(atom("b", vec![var("x")])),
],
),
Rule::positive(atom("b", vec![var("x")]), vec![atom("a", vec![var("x")])]),
],
};
let err = stratify(&program).expect_err("unstratifiable");
assert!(matches!(
err,
StratificationError::UnstratifiableCycle { .. }
));
}
#[test]
fn chained_negation_strata() {
let program = Program {
rules: vec![
Rule::new(
atom("level1", vec![var("x")]),
vec![
Literal::Positive(atom("base", vec![var("x")])),
Literal::Negative(atom("excluded", vec![var("x")])),
],
),
Rule::new(
atom("level2", vec![var("x")]),
vec![
Literal::Positive(atom("level1", vec![var("x")])),
Literal::Negative(atom("more_excluded", vec![var("x")])),
],
),
],
};
let strat = stratify(&program).expect("stratifiable");
let base_s = strat.stratum_of(&pred("base")).unwrap();
let excluded_s = strat.stratum_of(&pred("excluded")).unwrap();
let more_excluded_s = strat.stratum_of(&pred("more_excluded")).unwrap();
let level1_s = strat.stratum_of(&pred("level1")).unwrap();
let level2_s = strat.stratum_of(&pred("level2")).unwrap();
assert_eq!(base_s, 0);
assert_eq!(excluded_s, 0);
assert_eq!(more_excluded_s, 0);
assert!(level1_s > excluded_s);
assert!(level2_s >= level1_s);
assert!(level2_s > more_excluded_s);
}
fn constant(value: &str) -> Term {
Term::Constant(ConstantSymbol::new(value))
}
fn atom_with_graph(predicate: &str, terms: Vec<Term>, graph: Term) -> Atom {
let mut all_terms = terms;
all_terms.push(graph);
Atom {
predicate: PredicateSymbol::new(predicate),
terms: all_terms,
}
}
#[test]
fn quad_shape_rules_stratify_like_predicate_only_when_head_class_is_uniform() {
let program = Program {
rules: vec![
Rule::positive(
atom_with_graph("p", vec![var("s")], var("g")),
vec![atom_with_graph("q", vec![var("s")], var("g"))],
),
Rule::positive(
atom_with_graph("q", vec![var("s")], var("g")),
vec![atom_with_graph("r", vec![var("s")], var("g"))],
),
],
};
let strat = stratify(&program).expect("stratifiable");
assert_eq!(strat.strata.len(), 1);
assert_eq!(strat.stratum_of(&pred("p")), Some(0));
assert_eq!(strat.stratum_of(&pred("q")), Some(0));
assert_eq!(strat.stratum_of(&pred("r")), Some(0));
}
#[test]
fn fixed_graph_producer_and_consumer_share_one_node_per_predicate() {
let g_inferred = "urn:example:G_inferred";
let program = Program {
rules: vec![
Rule::positive(
atom_with_graph("inferred", vec![var("s")], constant(g_inferred)),
vec![atom_with_graph("input", vec![var("s")], var("g"))],
),
Rule::positive(
atom_with_graph("downstream", vec![var("s")], var("g")),
vec![atom_with_graph(
"inferred",
vec![var("s")],
constant(g_inferred),
)],
),
],
};
let strat = stratify(&program).expect("stratifiable");
let input_s = strat.stratum_of(&pred("input")).unwrap();
let inferred_s = strat.stratum_of(&pred("inferred")).unwrap();
let downstream_s = strat.stratum_of(&pred("downstream")).unwrap();
assert!(inferred_s >= input_s);
assert!(downstream_s >= inferred_s);
assert_eq!(strat.strata.len(), 1);
}
#[test]
fn body_atom_with_specific_graph_does_not_depend_on_other_literal_producers() {
let g_a = "urn:example:G_a";
let g_b = "urn:example:G_b";
let program = Program {
rules: vec![
Rule::positive(
atom_with_graph("producer_a", vec![var("s")], constant(g_a)),
vec![atom_with_graph("input", vec![var("s")], var("g"))],
),
Rule::new(
atom_with_graph("producer_b", vec![var("s")], constant(g_b)),
vec![
Literal::Positive(atom_with_graph("input", vec![var("s")], var("g"))),
Literal::Negative(atom_with_graph("exclusion", vec![var("s")], var("g"))),
],
),
Rule::positive(
atom_with_graph("consumer", vec![var("s")], var("g")),
vec![atom_with_graph("producer_a", vec![var("s")], constant(g_a))],
),
],
};
let strat = stratify(&program).expect("stratifiable");
let producer_a_s = strat.stratum_of(&pred("producer_a")).unwrap();
let producer_b_s = strat.stratum_of(&pred("producer_b")).unwrap();
let consumer_s = strat.stratum_of(&pred("consumer")).unwrap();
let exclusion_s = strat.stratum_of(&pred("exclusion")).unwrap();
assert!(producer_b_s > exclusion_s);
assert_eq!(consumer_s, producer_a_s);
}