/* * richard-atp-engine: A saturation-based automated theorem prover for first-order logic. * Copyright (C) 2026 Richard Qin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include "atp/core/clause.h" #include "atp/core/clause_store.h" #include "atp/core/literal.h" #include "atp/core/symbol_table.h" #include "atp/core/term_bank.h" #include "atp/core/types.h" #include "atp/search/prover.h" #include #include #include namespace atp { namespace { class ProverTest : public ::testing::Test { protected: SymbolTable symbols_; TermBank bank_{symbols_}; ClauseStore store_; SymbolId sym_a_{}, sym_b_{}; SymbolId sym_f_{}; SymbolId sym_p_{}, sym_q_{}, sym_r_{}; SymbolId sym_x_{}, sym_y_{}; TermId a_{}, b_{}, x_{}, y_{}; void SetUp() override { sym_a_ = symbols_.intern("a", SymbolKind::kConstant); sym_b_ = symbols_.intern("b", SymbolKind::kConstant); sym_f_ = symbols_.intern("f", SymbolKind::kFunction, 1); sym_p_ = symbols_.intern("P", SymbolKind::kPredicate, 1); sym_q_ = symbols_.intern("Q", SymbolKind::kPredicate, 1); sym_r_ = symbols_.intern("R", SymbolKind::kPredicate, 2); sym_x_ = symbols_.intern("X", SymbolKind::kVariable); sym_y_ = symbols_.intern("Y", SymbolKind::kVariable); a_ = bank_.makeTerm(sym_a_, {}); b_ = bank_.makeTerm(sym_b_, {}); x_ = bank_.makeVar(sym_x_); y_ = bank_.makeVar(sym_y_); } TermId p(TermId arg) { return bank_.makeTerm(sym_p_, {{arg}}); } TermId q(TermId arg) { return bank_.makeTerm(sym_q_, {{arg}}); } TermId r(TermId a1, TermId a2) { return bank_.makeTerm(sym_r_, {{a1, a2}}); } TermId f(TermId arg) { return bank_.makeTerm(sym_f_, {{arg}}); } static Literal pos(TermId atom) { return {.atom_ = atom, .is_positive_ = true}; } static Literal neg(TermId atom) { return {.atom_ = atom, .is_positive_ = false}; } static Clause makeClause(std::vector lits) { Clause cl; cl.literals_ = std::move(lits); return cl; } }; // ═══════════════════════════════════════════════════════════════════════════ // Trivial refutation: {P(a)} and {¬P(a)} → empty clause // ═══════════════════════════════════════════════════════════════════════════ TEST_F(ProverTest, TrivialRefutation) { Prover prover(bank_, store_); prover.addClauses({makeClause({pos(p(a_))}), makeClause({neg(p(a_))})}); const ProverResult kResult = prover.prove(); EXPECT_EQ(kResult, ProverResult::kTheorem); EXPECT_TRUE(prover.getEmptyClauseId().has_value()); } // ═══════════════════════════════════════════════════════════════════════════ // Unification required: {P(X)} and {¬P(a)} → empty clause // ═══════════════════════════════════════════════════════════════════════════ TEST_F(ProverTest, RefutationWithUnification) { Prover prover(bank_, store_); prover.addClauses({makeClause({pos(p(x_))}), makeClause({neg(p(a_))})}); const ProverResult kResult = prover.prove(); EXPECT_EQ(kResult, ProverResult::kTheorem); } // ═══════════════════════════════════════════════════════════════════════════ // Multi-step: {P(a)}, {¬P(X), Q(X)}, {¬Q(a)} // Step 1: resolve P(a) with ¬P(X)∨Q(X) → Q(a) // Step 2: resolve Q(a) with ¬Q(a) → empty // ═══════════════════════════════════════════════════════════════════════════ TEST_F(ProverTest, TwoStepRefutation) { Prover prover(bank_, store_); prover.addClauses( {makeClause({pos(p(a_))}), makeClause({neg(p(x_)), pos(q(x_))}), makeClause({neg(q(a_))})}); const ProverResult kResult = prover.prove(); EXPECT_EQ(kResult, ProverResult::kTheorem); } // ═══════════════════════════════════════════════════════════════════════════ // Saturation: {P(a)}, {Q(b)} — no complementary literals // ═══════════════════════════════════════════════════════════════════════════ TEST_F(ProverTest, Saturation) { Prover prover(bank_, store_); prover.addClauses({makeClause({pos(p(a_))}), makeClause({pos(q(b_))})}); const ProverResult kResult = prover.prove(); EXPECT_EQ(kResult, ProverResult::kSaturation); } // ═══════════════════════════════════════════════════════════════════════════ // Timeout: set very low limits // ═══════════════════════════════════════════════════════════════════════════ TEST_F(ProverTest, Timeout) { ProverConfig config; config.max_iterations_ = 1; // Need enough clauses to not saturate in 1 iteration Prover prover(bank_, store_, config); prover.addClauses({makeClause({pos(p(x_)), pos(q(x_))}), makeClause({neg(p(a_)), pos(q(a_))}), makeClause({neg(q(x_)), pos(p(f(x_)))}), makeClause({neg(p(x_)), neg(q(x_))})}); const ProverResult kResult = prover.prove(); EXPECT_EQ(kResult, ProverResult::kTimeout); } // ═══════════════════════════════════════════════════════════════════════════ // Chain reasoning: P(a), ¬P(X)∨Q(X), ¬Q(X)∨R(X), ¬R(a) // ═══════════════════════════════════════════════════════════════════════════ TEST_F(ProverTest, ThreeStepChain) { const SymbolId kSymS = symbols_.intern("S", SymbolKind::kPredicate, 1); auto s = [&](TermId arg) { return bank_.makeTerm(kSymS, {{arg}}); }; Prover prover(bank_, store_); prover.addClauses({ makeClause({pos(p(a_))}), // P(a) makeClause({neg(p(x_)), pos(q(x_))}), // ¬P(X) ∨ Q(X) makeClause({neg(q(x_)), pos(s(x_))}), // ¬Q(X) ∨ S(X) makeClause({neg(s(a_))}) // ¬S(a) }); const ProverResult kResult = prover.prove(); EXPECT_EQ(kResult, ProverResult::kTheorem); } // ═══════════════════════════════════════════════════════════════════════════ // Empty clause in input directly // ═══════════════════════════════════════════════════════════════════════════ TEST_F(ProverTest, EmptyClauseInInput) { Prover prover(bank_, store_); prover.addClauses({makeClause({}), // empty clause directly makeClause({pos(p(a_))})}); // Empty clause should be detected immediately when selected from queue const ProverResult kResult = prover.prove(); EXPECT_EQ(kResult, ProverResult::kTheorem); } } // namespace } // namespace atp