use super::eval_types::{
ElementaryEmbedding, FoAssignment, FoSignature, FoStructure, FoTheory, FoUltrafilter, Formula,
StructureInterp, Term,
};
pub fn eval_term(term: &Term, struc: &FoStructure, assign: &FoAssignment) -> Option<String> {
match term {
Term::Var(x) => assign.get(x).cloned(),
Term::Const(c) => {
match struc.interp(c) {
Some(StructureInterp::Constant(val)) => Some(val.clone()),
_ => {
if struc.contains(c) {
Some(c.clone())
} else {
None
}
}
}
}
Term::App(f, args) => {
let mut evaluated_args = Vec::with_capacity(args.len());
for arg in args {
let val = eval_term(arg, struc, assign)?;
evaluated_args.push(val);
}
match struc.interp(f) {
Some(StructureInterp::Function(table)) => {
table
.iter()
.find(|(params, _)| params.as_slice() == evaluated_args.as_slice())
.map(|(_, result)| result.clone())
}
_ => None,
}
}
}
}
pub fn eval_formula(formula: &Formula, struc: &FoStructure, assign: &FoAssignment) -> Option<bool> {
match formula {
Formula::Atom(rel, terms) => {
let mut vals = Vec::with_capacity(terms.len());
for t in terms {
let v = eval_term(t, struc, assign)?;
vals.push(v);
}
match struc.interp(rel) {
Some(StructureInterp::Relation(tuples)) => {
Some(tuples.iter().any(|tup| tup.as_slice() == vals.as_slice()))
}
_ => Some(false),
}
}
Formula::Equal(t1, t2) => {
let v1 = eval_term(t1, struc, assign)?;
let v2 = eval_term(t2, struc, assign)?;
Some(v1 == v2)
}
Formula::Neg(phi) => {
let b = eval_formula(phi, struc, assign)?;
Some(!b)
}
Formula::And(phi, psi) => {
let b1 = eval_formula(phi, struc, assign)?;
if !b1 {
return Some(false); }
eval_formula(psi, struc, assign)
}
Formula::Or(phi, psi) => {
let b1 = eval_formula(phi, struc, assign)?;
if b1 {
return Some(true); }
eval_formula(psi, struc, assign)
}
Formula::Implies(phi, psi) => {
let b1 = eval_formula(phi, struc, assign)?;
if !b1 {
return Some(true); }
eval_formula(psi, struc, assign)
}
Formula::Forall(x, phi) => {
for elem in &struc.domain {
let new_assign = assign.extend(x, elem);
let b = eval_formula(phi, struc, &new_assign)?;
if !b {
return Some(false);
}
}
Some(true)
}
Formula::Exists(x, phi) => {
for elem in &struc.domain {
let new_assign = assign.extend(x, elem);
let b = eval_formula(phi, struc, &new_assign)?;
if b {
return Some(true);
}
}
Some(false)
}
}
}
pub fn satisfies(struc: &FoStructure, sentence: &Formula) -> bool {
let assign = FoAssignment::new();
eval_formula(sentence, struc, &assign).unwrap_or(false)
}
pub fn is_fo_model(struc: &FoStructure, theory: &FoTheory) -> bool {
theory.axioms.iter().all(|axiom| satisfies(struc, axiom))
}
pub fn group_theory() -> FoTheory {
let mut sig = FoSignature::new();
sig.add_function("mul", 2);
sig.add_function("inv", 1);
sig.add_constant("e");
let e = Term::Const("e".to_string());
let ax1 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![e.clone(), Term::Var("x".to_string())],
),
Term::Var("x".to_string()),
),
);
let ax2 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![Term::Var("x".to_string()), e.clone()],
),
Term::Var("x".to_string()),
),
);
let ax3 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![
Term::App("inv".to_string(), vec![Term::Var("x".to_string())]),
Term::Var("x".to_string()),
],
),
e.clone(),
),
);
let ax4 = Formula::forall(
"x",
Formula::forall(
"y",
Formula::forall(
"z",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![
Term::App(
"mul".to_string(),
vec![Term::Var("x".to_string()), Term::Var("y".to_string())],
),
Term::Var("z".to_string()),
],
),
Term::App(
"mul".to_string(),
vec![
Term::Var("x".to_string()),
Term::App(
"mul".to_string(),
vec![Term::Var("y".to_string()), Term::Var("z".to_string())],
),
],
),
),
),
),
);
FoTheory::new(vec![ax1, ax2, ax3, ax4], sig)
}
pub fn ring_theory() -> FoTheory {
let mut sig = FoSignature::new();
sig.add_function("add", 2);
sig.add_function("mul", 2);
sig.add_function("neg", 1);
sig.add_constant("0");
sig.add_constant("1");
let zero = Term::Const("0".to_string());
let one = Term::Const("1".to_string());
let ax1 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"add".to_string(),
vec![zero.clone(), Term::Var("x".to_string())],
),
Term::Var("x".to_string()),
),
);
let ax2 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"add".to_string(),
vec![Term::Var("x".to_string()), zero.clone()],
),
Term::Var("x".to_string()),
),
);
let ax3 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"add".to_string(),
vec![
Term::App("neg".to_string(), vec![Term::Var("x".to_string())]),
Term::Var("x".to_string()),
],
),
zero.clone(),
),
);
let ax4 = Formula::forall(
"x",
Formula::forall(
"y",
Formula::Equal(
Term::App(
"add".to_string(),
vec![Term::Var("x".to_string()), Term::Var("y".to_string())],
),
Term::App(
"add".to_string(),
vec![Term::Var("y".to_string()), Term::Var("x".to_string())],
),
),
),
);
let ax5 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![one.clone(), Term::Var("x".to_string())],
),
Term::Var("x".to_string()),
),
);
let ax6 = Formula::forall(
"x",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![Term::Var("x".to_string()), one.clone()],
),
Term::Var("x".to_string()),
),
);
let ax7 = Formula::forall(
"x",
Formula::forall(
"y",
Formula::forall(
"z",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![
Term::App(
"mul".to_string(),
vec![Term::Var("x".to_string()), Term::Var("y".to_string())],
),
Term::Var("z".to_string()),
],
),
Term::App(
"mul".to_string(),
vec![
Term::Var("x".to_string()),
Term::App(
"mul".to_string(),
vec![Term::Var("y".to_string()), Term::Var("z".to_string())],
),
],
),
),
),
),
);
let ax8 = Formula::forall(
"x",
Formula::forall(
"y",
Formula::forall(
"z",
Formula::Equal(
Term::App(
"mul".to_string(),
vec![
Term::Var("x".to_string()),
Term::App(
"add".to_string(),
vec![Term::Var("y".to_string()), Term::Var("z".to_string())],
),
],
),
Term::App(
"add".to_string(),
vec![
Term::App(
"mul".to_string(),
vec![Term::Var("x".to_string()), Term::Var("y".to_string())],
),
Term::App(
"mul".to_string(),
vec![Term::Var("x".to_string()), Term::Var("z".to_string())],
),
],
),
),
),
),
);
FoTheory::new(vec![ax1, ax2, ax3, ax4, ax5, ax6, ax7, ax8], sig)
}
pub fn dense_linear_order() -> FoTheory {
let mut sig = FoSignature::new();
sig.add_relation("lt", 2);
let lt = |a: &str, b: &str| {
Formula::Atom(
"lt".to_string(),
vec![Term::Var(a.to_string()), Term::Var(b.to_string())],
)
};
let ax1 = Formula::forall("x", Formula::neg(lt("x", "x")));
let ax2 = Formula::forall(
"x",
Formula::forall(
"y",
Formula::forall(
"z",
Formula::implies(Formula::and(lt("x", "y"), lt("y", "z")), lt("x", "z")),
),
),
);
let ax3 = Formula::forall(
"x",
Formula::forall(
"y",
Formula::or(
lt("x", "y"),
Formula::or(
lt("y", "x"),
Formula::Equal(Term::Var("x".to_string()), Term::Var("y".to_string())),
),
),
),
);
let ax4 = Formula::forall(
"x",
Formula::forall(
"y",
Formula::implies(
lt("x", "y"),
Formula::exists("z", Formula::and(lt("x", "z"), lt("z", "y"))),
),
),
);
let ax5 = Formula::forall("x", Formula::exists("y", lt("y", "x")));
let ax6 = Formula::forall("x", Formula::exists("y", lt("x", "y")));
FoTheory::new(vec![ax1, ax2, ax3, ax4, ax5, ax6], sig)
}
pub fn is_consistent(theory: &FoTheory) -> bool {
for axiom in &theory.axioms {
let negated = Formula::neg(axiom.clone());
if theory.axioms.contains(&negated) {
return false;
}
}
true
}
pub fn compactness_witness(theory: &FoTheory, extra: &[Formula]) -> Option<FoStructure> {
let all_formulas: Vec<&Formula> = theory.axioms.iter().chain(extra.iter()).collect();
for size in 1..=2usize {
let domain: Vec<String> = (0..size).map(|i| format!("d{i}")).collect();
let struc = build_trivial_structure(&domain, &theory.signature);
let assign = FoAssignment::new();
let ok = all_formulas
.iter()
.all(|phi| eval_formula(phi, &struc, &assign).unwrap_or(false));
if ok {
return Some(struc);
}
}
None
}
fn build_trivial_structure(domain: &[String], sig: &FoSignature) -> FoStructure {
let mut struc = FoStructure::new(domain.to_vec());
let default_elem = domain.first().cloned().unwrap_or_else(|| "d0".to_string());
for c in &sig.constants {
struc.add_interp(c, StructureInterp::Constant(default_elem.clone()));
}
for (fname, arity) in &sig.functions {
let mut table: Vec<(Vec<String>, String)> = Vec::new();
let tuples = cartesian_product_strings(domain, *arity);
for args in tuples {
table.push((args, default_elem.clone()));
}
struc.add_interp(fname, StructureInterp::Function(table));
}
for (rname, _) in &sig.relations {
struc.add_interp(rname, StructureInterp::Relation(vec![]));
}
struc
}
fn cartesian_product_strings(domain: &[String], arity: usize) -> Vec<Vec<String>> {
if arity == 0 {
return vec![vec![]];
}
let mut result: Vec<Vec<String>> = vec![vec![]];
for _ in 0..arity {
let mut new_result = Vec::new();
for existing in &result {
for elem in domain {
let mut extended = existing.clone();
extended.push(elem.clone());
new_result.push(extended);
}
}
result = new_result;
}
result
}
pub fn check_elementary_embedding(
embed: &ElementaryEmbedding,
source: &FoStructure,
target: &FoStructure,
theory: &FoTheory,
) -> bool {
let _ = embed; theory
.axioms
.iter()
.all(|phi| satisfies(source, phi) == satisfies(target, phi))
}
pub fn lowenheim_skolem_downward(struc: &FoStructure, subset: &[String]) -> FoStructure {
let new_domain: Vec<String> = struc
.domain
.iter()
.filter(|e| subset.contains(e))
.cloned()
.collect();
let mut sub = FoStructure::new(new_domain.clone());
for (sym, interp) in &struc.interpretations {
let new_interp = match interp {
StructureInterp::Constant(c) => {
if new_domain.contains(c) {
StructureInterp::Constant(c.clone())
} else {
match new_domain.first() {
Some(first) => StructureInterp::Constant(first.clone()),
None => continue,
}
}
}
StructureInterp::Relation(tuples) => {
let filtered: Vec<Vec<String>> = tuples
.iter()
.filter(|tup| tup.iter().all(|e| new_domain.contains(e)))
.cloned()
.collect();
StructureInterp::Relation(filtered)
}
StructureInterp::Function(table) => {
let filtered: Vec<(Vec<String>, String)> = table
.iter()
.filter(|(args, result)| {
args.iter().all(|e| new_domain.contains(e)) && new_domain.contains(result)
})
.cloned()
.collect();
StructureInterp::Function(filtered)
}
};
sub.add_interp(sym, new_interp);
}
sub
}
pub fn trivial_ultrafilter(index_set: usize) -> FoUltrafilter {
let full: Vec<usize> = (0..index_set).collect();
FoUltrafilter::new(index_set, vec![full])
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model_theory::eval_types::{FoAssignment, FoStructure, StructureInterp, Term};
fn trivial_group() -> FoStructure {
let mut s = FoStructure::new(vec!["e".to_string()]);
s.add_interp("e", StructureInterp::Constant("e".to_string()));
s.add_interp(
"mul",
StructureInterp::Function(vec![(
vec!["e".to_string(), "e".to_string()],
"e".to_string(),
)]),
);
s.add_interp(
"inv",
StructureInterp::Function(vec![(vec!["e".to_string()], "e".to_string())]),
);
s
}
#[test]
fn test_eval_term_var_bound() {
let s = FoStructure::new(vec!["a".to_string()]);
let mut assign = FoAssignment::new();
assign.set("x", "a");
let t = Term::Var("x".to_string());
assert_eq!(eval_term(&t, &s, &assign), Some("a".to_string()));
}
#[test]
fn test_eval_term_var_unbound() {
let s = FoStructure::new(vec!["a".to_string()]);
let assign = FoAssignment::new();
let t = Term::Var("x".to_string());
assert_eq!(eval_term(&t, &s, &assign), None);
}
#[test]
fn test_eval_term_const_in_domain() {
let s = FoStructure::new(vec!["e".to_string()]);
let assign = FoAssignment::new();
let t = Term::Const("e".to_string());
assert_eq!(eval_term(&t, &s, &assign), Some("e".to_string()));
}
#[test]
fn test_eval_term_const_via_interp() {
let mut s = FoStructure::new(vec!["zero".to_string()]);
s.add_interp("0", StructureInterp::Constant("zero".to_string()));
let assign = FoAssignment::new();
let t = Term::Const("0".to_string());
assert_eq!(eval_term(&t, &s, &assign), Some("zero".to_string()));
}
#[test]
fn test_eval_term_app() {
let mut s = FoStructure::new(vec!["e".to_string()]);
s.add_interp(
"f",
StructureInterp::Function(vec![(vec!["e".to_string()], "e".to_string())]),
);
let mut assign = FoAssignment::new();
assign.set("x", "e");
let t = Term::App("f".to_string(), vec![Term::Var("x".to_string())]);
assert_eq!(eval_term(&t, &s, &assign), Some("e".to_string()));
}
#[test]
fn test_eval_term_app_undefined() {
let s = FoStructure::new(vec!["e".to_string()]);
let mut assign = FoAssignment::new();
assign.set("x", "e");
let t = Term::App("g".to_string(), vec![Term::Var("x".to_string())]);
assert_eq!(eval_term(&t, &s, &assign), None);
}
#[test]
fn test_eval_formula_equality_true() {
let s = FoStructure::new(vec!["e".to_string()]);
let mut assign = FoAssignment::new();
assign.set("x", "e");
let phi = Formula::Equal(Term::Var("x".to_string()), Term::Const("e".to_string()));
assert_eq!(eval_formula(&phi, &s, &assign), Some(true));
}
#[test]
fn test_eval_formula_equality_false() {
let s = FoStructure::new(vec!["a".to_string(), "b".to_string()]);
let mut assign = FoAssignment::new();
assign.set("x", "a");
assign.set("y", "b");
let phi = Formula::Equal(Term::Var("x".to_string()), Term::Var("y".to_string()));
assert_eq!(eval_formula(&phi, &s, &assign), Some(false));
}
#[test]
fn test_eval_formula_neg() {
let s = FoStructure::new(vec!["e".to_string()]);
let mut assign = FoAssignment::new();
assign.set("x", "e");
let phi = Formula::neg(Formula::Equal(
Term::Var("x".to_string()),
Term::Var("x".to_string()),
));
assert_eq!(eval_formula(&phi, &s, &assign), Some(false));
}
#[test]
fn test_eval_formula_and() {
let s = FoStructure::new(vec!["e".to_string()]);
let mut assign = FoAssignment::new();
assign.set("x", "e");
let eq = Formula::Equal(Term::Var("x".to_string()), Term::Const("e".to_string()));
let phi = Formula::and(eq.clone(), eq.clone());
assert_eq!(eval_formula(&phi, &s, &assign), Some(true));
}
#[test]
fn test_eval_formula_or_first_true() {
let s = FoStructure::new(vec!["e".to_string()]);
let mut assign = FoAssignment::new();
assign.set("x", "e");
let t = Formula::Equal(Term::Var("x".to_string()), Term::Const("e".to_string()));
let f = Formula::neg(t.clone());
let phi = Formula::or(t, f);
assert_eq!(eval_formula(&phi, &s, &assign), Some(true));
}
#[test]
fn test_eval_formula_forall_trivial_group() {
let s = FoStructure::new(vec!["e".to_string()]);
let assign = FoAssignment::new();
let phi = Formula::forall(
"x",
Formula::Equal(Term::Var("x".to_string()), Term::Var("x".to_string())),
);
assert_eq!(eval_formula(&phi, &s, &assign), Some(true));
}
#[test]
fn test_eval_formula_exists() {
let s = FoStructure::new(vec!["e".to_string()]);
let assign = FoAssignment::new();
let phi = Formula::exists(
"x",
Formula::Equal(Term::Var("x".to_string()), Term::Const("e".to_string())),
);
assert_eq!(eval_formula(&phi, &s, &assign), Some(true));
}
#[test]
fn test_satisfies_reflexivity() {
let s = FoStructure::new(vec!["a".to_string(), "b".to_string()]);
let phi = Formula::forall(
"x",
Formula::Equal(Term::Var("x".to_string()), Term::Var("x".to_string())),
);
assert!(satisfies(&s, &phi));
}
#[test]
fn test_is_fo_model_trivial_group_satisfies_group_theory() {
let s = trivial_group();
let t = group_theory();
assert!(is_fo_model(&s, &t));
}
#[test]
fn test_group_theory_num_axioms() {
let t = group_theory();
assert_eq!(t.num_axioms(), 4);
}
#[test]
fn test_group_theory_signature_has_mul() {
let t = group_theory();
assert!(t.signature.function_arity("mul") == Some(2));
}
#[test]
fn test_group_theory_signature_has_inv() {
let t = group_theory();
assert!(t.signature.function_arity("inv") == Some(1));
}
#[test]
fn test_ring_theory_num_axioms() {
let t = ring_theory();
assert_eq!(t.num_axioms(), 8);
}
#[test]
fn test_ring_theory_signature() {
let t = ring_theory();
assert_eq!(t.signature.function_arity("add"), Some(2));
assert_eq!(t.signature.function_arity("mul"), Some(2));
assert_eq!(t.signature.function_arity("neg"), Some(1));
}
#[test]
fn test_dlo_num_axioms() {
let t = dense_linear_order();
assert_eq!(t.num_axioms(), 6);
}
#[test]
fn test_dlo_signature_has_lt() {
let t = dense_linear_order();
assert_eq!(t.signature.relation_arity("lt"), Some(2));
}
#[test]
fn test_is_consistent_empty_theory() {
let sig = FoSignature::new();
let t = FoTheory::new(vec![], sig);
assert!(is_consistent(&t));
}
#[test]
fn test_is_consistent_group_theory() {
let t = group_theory();
assert!(is_consistent(&t));
}
#[test]
fn test_is_consistent_contradiction() {
let sig = FoSignature::new();
let phi = Formula::forall(
"x",
Formula::Equal(Term::Var("x".to_string()), Term::Var("x".to_string())),
);
let not_phi = Formula::neg(phi.clone());
let t = FoTheory::new(vec![phi, not_phi], sig);
assert!(!is_consistent(&t));
}
#[test]
fn test_compactness_witness_empty_theory() {
let sig = FoSignature::new();
let t = FoTheory::new(vec![], sig);
let result = compactness_witness(&t, &[]);
assert!(result.is_some());
}
#[test]
fn test_elementary_embedding_same_structure() {
let s = trivial_group();
let t = group_theory();
let embed = ElementaryEmbedding::new(0, 0);
assert!(check_elementary_embedding(&embed, &s, &s, &t));
}
#[test]
fn test_lowenheim_skolem_subset() {
let s = FoStructure::new(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
let sub = lowenheim_skolem_downward(&s, &["a".to_string(), "b".to_string()]);
assert_eq!(sub.domain_size(), 2);
assert!(sub.contains("a"));
assert!(sub.contains("b"));
assert!(!sub.contains("c"));
}
#[test]
fn test_lowenheim_skolem_full_set() {
let s = FoStructure::new(vec!["a".to_string(), "b".to_string()]);
let sub = lowenheim_skolem_downward(&s, &["a".to_string(), "b".to_string()]);
assert_eq!(sub.domain_size(), 2);
}
#[test]
fn test_lowenheim_skolem_empty_subset() {
let s = FoStructure::new(vec!["a".to_string(), "b".to_string()]);
let sub = lowenheim_skolem_downward(&s, &[]);
assert_eq!(sub.domain_size(), 0);
}
#[test]
fn test_trivial_ultrafilter() {
let uf = trivial_ultrafilter(3);
assert_eq!(uf.index_set, 3);
assert_eq!(uf.sets.len(), 1);
assert_eq!(uf.sets[0], vec![0, 1, 2]);
}
#[test]
fn test_fo_assignment_extend() {
let a = FoAssignment::new();
let b = a.extend("x", "elem1");
assert_eq!(b.get("x"), Some(&"elem1".to_string()));
assert_eq!(a.get("x"), None);
}
#[test]
fn test_term_free_vars_var() {
let t = Term::Var("x".to_string());
assert_eq!(t.free_vars(), vec!["x".to_string()]);
}
#[test]
fn test_term_free_vars_const() {
let t = Term::Const("c".to_string());
assert!(t.free_vars().is_empty());
}
#[test]
fn test_formula_is_sentence() {
let phi = Formula::forall(
"x",
Formula::Equal(Term::Var("x".to_string()), Term::Var("x".to_string())),
);
assert!(phi.is_sentence());
}
#[test]
fn test_formula_not_sentence() {
let phi = Formula::Equal(Term::Var("x".to_string()), Term::Var("x".to_string()));
assert!(!phi.is_sentence());
}
}