geolog-zeta-fork/tests/unit_chase.rs
2026-02-26 11:50:51 +01:00

427 lines
15 KiB
Rust

//! Unit tests for tensor-backed chase algorithm
use geolog::core::{
Context, DerivedSort, Formula, RelationStorage, Sequent, Signature, Structure, Term, Theory,
};
use geolog::query::chase::chase_fixpoint;
use geolog::universe::Universe;
/// Create a simple test theory with one sort and one unary relation
fn simple_theory_with_relation() -> Theory {
let mut sig = Signature::default();
sig.add_sort("V".to_string());
sig.add_relation("R".to_string(), DerivedSort::Base(0));
Theory {
name: "Simple".to_string(),
signature: sig,
axioms: vec![],
axiom_names: vec![],
}
}
/// Create a preorder-like theory with binary leq relation, reflexivity and transitivity
fn preorder_theory() -> Theory {
let mut sig = Signature::default();
sig.add_sort("X".to_string());
// Binary relation with product domain: leq : [x: X, y: X] -> Prop
let domain = DerivedSort::Product(vec![
("x".to_string(), DerivedSort::Base(0)),
("y".to_string(), DerivedSort::Base(0)),
]);
sig.add_relation("leq".to_string(), domain);
// Reflexivity axiom: forall x : X. |- [x: x, y: x] leq
let refl_axiom = Sequent {
context: Context {
vars: vec![("x".to_string(), DerivedSort::Base(0))],
},
premise: Formula::True,
conclusion: Formula::Rel(
0,
Term::Record(vec![
("x".to_string(), Term::Var("x".to_string(), DerivedSort::Base(0))),
("y".to_string(), Term::Var("x".to_string(), DerivedSort::Base(0))),
]),
),
};
// Transitivity axiom: forall x, y, z : X. [x: x, y: y] leq, [x: y, y: z] leq |- [x: x, y: z] leq
let trans_axiom = Sequent {
context: Context {
vars: vec![
("x".to_string(), DerivedSort::Base(0)),
("y".to_string(), DerivedSort::Base(0)),
("z".to_string(), DerivedSort::Base(0)),
],
},
premise: Formula::Conj(vec![
Formula::Rel(
0,
Term::Record(vec![
("x".to_string(), Term::Var("x".to_string(), DerivedSort::Base(0))),
("y".to_string(), Term::Var("y".to_string(), DerivedSort::Base(0))),
]),
),
Formula::Rel(
0,
Term::Record(vec![
("x".to_string(), Term::Var("y".to_string(), DerivedSort::Base(0))),
("y".to_string(), Term::Var("z".to_string(), DerivedSort::Base(0))),
]),
),
]),
conclusion: Formula::Rel(
0,
Term::Record(vec![
("x".to_string(), Term::Var("x".to_string(), DerivedSort::Base(0))),
("y".to_string(), Term::Var("z".to_string(), DerivedSort::Base(0))),
]),
),
};
Theory {
name: "Preorder".to_string(),
signature: sig,
axioms: vec![refl_axiom, trans_axiom],
axiom_names: vec!["ax/refl".to_string(), "ax/trans".to_string()],
}
}
#[test]
fn test_chase_adds_relation_from_true_premise() {
// Axiom: forall x : V. |- R(x)
// This should add all elements to R
let mut sig = Signature::default();
sig.add_sort("V".to_string());
sig.add_relation("R".to_string(), DerivedSort::Base(0));
let axiom = Sequent {
context: Context {
vars: vec![("x".to_string(), DerivedSort::Base(0))],
},
premise: Formula::True,
conclusion: Formula::Rel(0, Term::Var("x".to_string(), DerivedSort::Base(0))),
};
let mut universe = Universe::new();
let mut structure = Structure::new(1);
// Add some elements
let (a, _) = structure.add_element(&mut universe, 0);
let (b, _) = structure.add_element(&mut universe, 0);
let (c, _) = structure.add_element(&mut universe, 0);
// Initialize relation
structure.init_relations(&[1]);
// Run chase
let iterations = chase_fixpoint(&[axiom], &mut structure, &mut universe, &sig, 100).unwrap();
// Should add all 3 elements to R
assert_eq!(structure.get_relation(0).len(), 3);
assert!(structure.query_relation(0, &[a]));
assert!(structure.query_relation(0, &[b]));
assert!(structure.query_relation(0, &[c]));
// Should converge in 2 iterations
assert_eq!(iterations, 2);
}
#[test]
fn test_chase_fixpoint_empty_structure() {
let theory = simple_theory_with_relation();
// Axiom: forall x : V. |- R(x)
let axiom = Sequent {
context: Context {
vars: vec![("x".to_string(), DerivedSort::Base(0))],
},
premise: Formula::True,
conclusion: Formula::Rel(0, Term::Var("x".to_string(), DerivedSort::Base(0))),
};
let mut universe = Universe::new();
let mut structure = Structure::new(1);
structure.init_relations(&[1]);
let iterations = chase_fixpoint(&[axiom], &mut structure, &mut universe, &theory.signature, 100).unwrap();
// Empty structure: no elements, so nothing to add
assert_eq!(iterations, 1);
assert_eq!(structure.get_relation(0).len(), 0);
}
#[test]
fn test_chase_preorder_reflexivity() {
// Test that chase correctly computes reflexive closure
let theory = preorder_theory();
let mut universe = Universe::new();
let mut structure = Structure::new(1);
// Add 3 elements
let (a, _) = structure.add_element(&mut universe, 0);
let (b, _) = structure.add_element(&mut universe, 0);
let (c, _) = structure.add_element(&mut universe, 0);
// Initialize relation with arity 2
structure.init_relations(&[2]);
// Run chase
let iterations = chase_fixpoint(
&theory.axioms,
&mut structure,
&mut universe,
&theory.signature,
100,
).unwrap();
// Should have exactly 3 reflexive tuples
let relation = structure.get_relation(0);
assert_eq!(relation.len(), 3, "Should have exactly 3 reflexive tuples");
// Check reflexive pairs exist
assert!(structure.query_relation(0, &[a, a]), "Should have (a,a)");
assert!(structure.query_relation(0, &[b, b]), "Should have (b,b)");
assert!(structure.query_relation(0, &[c, c]), "Should have (c,c)");
// Check non-reflexive pairs do NOT exist
assert!(!structure.query_relation(0, &[a, b]), "Should NOT have (a,b)");
assert!(!structure.query_relation(0, &[a, c]), "Should NOT have (a,c)");
assert!(!structure.query_relation(0, &[b, a]), "Should NOT have (b,a)");
// Should complete in 2 iterations
assert_eq!(iterations, 2);
}
#[test]
fn test_chase_transitive_closure() {
// Test that chase correctly computes transitive closure
let theory = preorder_theory();
let mut universe = Universe::new();
let mut structure = Structure::new(1);
// Add 3 elements: a < b < c (chain order)
let (a, _) = structure.add_element(&mut universe, 0);
let (b, _) = structure.add_element(&mut universe, 0);
let (c, _) = structure.add_element(&mut universe, 0);
// Initialize relation with arity 2
structure.init_relations(&[2]);
// Manually add initial ordering: a ≤ b and b ≤ c
structure.get_relation_mut(0).insert(vec![a, b]);
structure.get_relation_mut(0).insert(vec![b, c]);
// Run chase
let _iterations = chase_fixpoint(
&theory.axioms,
&mut structure,
&mut universe,
&theory.signature,
100,
).unwrap();
// Expected: 3 reflexive + 2 initial + 1 transitive (a,c) = 6
let relation = structure.get_relation(0);
assert_eq!(relation.len(), 6, "Should have 6 tuples");
// Check reflexive pairs
assert!(structure.query_relation(0, &[a, a]));
assert!(structure.query_relation(0, &[b, b]));
assert!(structure.query_relation(0, &[c, c]));
// Check initial ordering
assert!(structure.query_relation(0, &[a, b]));
assert!(structure.query_relation(0, &[b, c]));
// Check transitive closure!
assert!(structure.query_relation(0, &[a, c]), "Should have (a,c) from transitivity");
// Should NOT have backwards edges
assert!(!structure.query_relation(0, &[b, a]));
assert!(!structure.query_relation(0, &[c, b]));
assert!(!structure.query_relation(0, &[c, a]));
}
#[test]
fn test_chase_conjunction_in_conclusion() {
// Axiom: forall x : V. |- R(x) ∧ S(x)
let mut sig = Signature::default();
sig.add_sort("V".to_string());
sig.add_relation("R".to_string(), DerivedSort::Base(0));
sig.add_relation("S".to_string(), DerivedSort::Base(0));
let axiom = Sequent {
context: Context {
vars: vec![("x".to_string(), DerivedSort::Base(0))],
},
premise: Formula::True,
conclusion: Formula::Conj(vec![
Formula::Rel(0, Term::Var("x".to_string(), DerivedSort::Base(0))),
Formula::Rel(1, Term::Var("x".to_string(), DerivedSort::Base(0))),
]),
};
let mut universe = Universe::new();
let mut structure = Structure::new(1);
let (a, _) = structure.add_element(&mut universe, 0);
let (b, _) = structure.add_element(&mut universe, 0);
structure.init_relations(&[1, 1]);
let _iterations = chase_fixpoint(&[axiom], &mut structure, &mut universe, &sig, 100).unwrap();
// Both relations should have both elements
assert_eq!(structure.get_relation(0).len(), 2);
assert_eq!(structure.get_relation(1).len(), 2);
assert!(structure.query_relation(0, &[a]));
assert!(structure.query_relation(0, &[b]));
assert!(structure.query_relation(1, &[a]));
assert!(structure.query_relation(1, &[b]));
}
#[test]
fn test_chase_relation_premise() {
// Axiom: forall x, y : V. R(x, y) |- S(x, y)
// Copy tuples from R to S
let mut sig = Signature::default();
sig.add_sort("V".to_string());
let domain = DerivedSort::Product(vec![
("a".to_string(), DerivedSort::Base(0)),
("b".to_string(), DerivedSort::Base(0)),
]);
sig.add_relation("R".to_string(), domain.clone());
sig.add_relation("S".to_string(), domain);
let axiom = Sequent {
context: Context {
vars: vec![
("x".to_string(), DerivedSort::Base(0)),
("y".to_string(), DerivedSort::Base(0)),
],
},
premise: Formula::Rel(
0,
Term::Record(vec![
("a".to_string(), Term::Var("x".to_string(), DerivedSort::Base(0))),
("b".to_string(), Term::Var("y".to_string(), DerivedSort::Base(0))),
]),
),
conclusion: Formula::Rel(
1,
Term::Record(vec![
("a".to_string(), Term::Var("x".to_string(), DerivedSort::Base(0))),
("b".to_string(), Term::Var("y".to_string(), DerivedSort::Base(0))),
]),
),
};
let mut universe = Universe::new();
let mut structure = Structure::new(1);
let (a, _) = structure.add_element(&mut universe, 0);
let (b, _) = structure.add_element(&mut universe, 0);
structure.init_relations(&[2, 2]);
// Add some tuples to R
structure.get_relation_mut(0).insert(vec![a, b]);
structure.get_relation_mut(0).insert(vec![b, a]);
let _iterations = chase_fixpoint(&[axiom], &mut structure, &mut universe, &sig, 100).unwrap();
// S should have the same tuples as R
assert_eq!(structure.get_relation(1).len(), 2);
assert!(structure.query_relation(1, &[a, b]));
assert!(structure.query_relation(1, &[b, a]));
}
/// Test chase with existential premise (the feature that motivated tensor-backed chase!)
#[test]
fn test_chase_existential_premise() {
// Theory: Graph with reachability
// Axiom: forall v0, v1 : V. (exists e : E. src(e) = v0 ∧ tgt(e) = v1) |- reachable(v0, v1)
let mut sig = Signature::default();
let v_sort = sig.add_sort("V".to_string());
let e_sort = sig.add_sort("E".to_string());
// src, tgt : E -> V
sig.add_function("src".to_string(), DerivedSort::Base(e_sort), DerivedSort::Base(v_sort));
sig.add_function("tgt".to_string(), DerivedSort::Base(e_sort), DerivedSort::Base(v_sort));
// reachable : [from: V, to: V] -> Prop
let reach_domain = DerivedSort::Product(vec![
("from".to_string(), DerivedSort::Base(v_sort)),
("to".to_string(), DerivedSort::Base(v_sort)),
]);
sig.add_relation("reachable".to_string(), reach_domain);
// Axiom: (exists e : E. src(e) = v0 ∧ tgt(e) = v1) |- reachable(v0, v1)
let axiom = Sequent {
context: Context {
vars: vec![
("v0".to_string(), DerivedSort::Base(v_sort)),
("v1".to_string(), DerivedSort::Base(v_sort)),
],
},
premise: Formula::Exists(
"e".to_string(),
DerivedSort::Base(e_sort),
Box::new(Formula::Conj(vec![
Formula::Eq(
Term::App(0, Box::new(Term::Var("e".to_string(), DerivedSort::Base(e_sort)))),
Term::Var("v0".to_string(), DerivedSort::Base(v_sort)),
),
Formula::Eq(
Term::App(1, Box::new(Term::Var("e".to_string(), DerivedSort::Base(e_sort)))),
Term::Var("v1".to_string(), DerivedSort::Base(v_sort)),
),
])),
),
conclusion: Formula::Rel(
0,
Term::Record(vec![
("from".to_string(), Term::Var("v0".to_string(), DerivedSort::Base(v_sort))),
("to".to_string(), Term::Var("v1".to_string(), DerivedSort::Base(v_sort))),
]),
),
};
let mut universe = Universe::new();
let mut structure = Structure::new(2); // 2 sorts: V and E
// Add vertices: a, b, c
let (a, _) = structure.add_element(&mut universe, v_sort);
let (b, _) = structure.add_element(&mut universe, v_sort);
let (c, _) = structure.add_element(&mut universe, v_sort);
// Add edges: e1 (a->b), e2 (b->c)
let (e1, _) = structure.add_element(&mut universe, e_sort);
let (e2, _) = structure.add_element(&mut universe, e_sort);
// Initialize functions and relations
structure.init_functions(&[Some(e_sort), Some(e_sort)]); // src, tgt both have domain E
structure.init_relations(&[2]); // reachable is binary
// Define src and tgt
structure.define_function(0, e1, a).unwrap(); // src(e1) = a
structure.define_function(1, e1, b).unwrap(); // tgt(e1) = b
structure.define_function(0, e2, b).unwrap(); // src(e2) = b
structure.define_function(1, e2, c).unwrap(); // tgt(e2) = c
// Run chase
let iterations = chase_fixpoint(&[axiom], &mut structure, &mut universe, &sig, 100).unwrap();
// Should derive reachable(a,b) and reachable(b,c)
assert_eq!(structure.get_relation(0).len(), 2, "Should have 2 reachable pairs");
assert!(structure.query_relation(0, &[a, b]), "Should have reachable(a,b)");
assert!(structure.query_relation(0, &[b, c]), "Should have reachable(b,c)");
// Should NOT have other pairs
assert!(!structure.query_relation(0, &[a, c]), "Should NOT have reachable(a,c) without transitive closure axiom");
println!("Chase with existential premise completed in {} iterations", iterations);
}