/*
* 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/literal.h"
#include "atp/core/symbol_table.h"
#include "atp/core/term_bank.h"
#include "atp/core/types.h"
#include "atp/simplify/subsumption.h"
#include "atp/simplify/tautology.h"
#include
#include
#include
namespace atp {
namespace {
class SimplifyTest : public ::testing::Test {
protected:
SymbolTable symbols_;
TermBank bank_{symbols_};
SymbolId sym_a_{}, sym_b_{}, sym_c_{};
SymbolId sym_p_{}, sym_q_{}, sym_r_{};
SymbolId sym_x_{};
TermId a_{}, b_{}, c_{}, x_{};
void SetUp() override {
sym_a_ = symbols_.intern("a", SymbolKind::kConstant);
sym_b_ = symbols_.intern("b", SymbolKind::kConstant);
sym_c_ = symbols_.intern("c", SymbolKind::kConstant);
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);
a_ = bank_.makeTerm(sym_a_, {});
b_ = bank_.makeTerm(sym_b_, {});
c_ = bank_.makeTerm(sym_c_, {});
x_ = bank_.makeVar(sym_x_);
}
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}}); }
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;
}
};
// ═══════════════════════════════════════════════════════════════════════════
// Tautology Tests
// ═══════════════════════════════════════════════════════════════════════════
TEST_F(SimplifyTest, TautologyComplementaryLiterals) {
// {P(a), ¬P(a)} → tautology
Clause const cl = makeClause({pos(p(a_)), neg(p(a_))});
EXPECT_TRUE(isTautology(cl));
}
TEST_F(SimplifyTest, TautologyWithExtraLiterals) {
// {Q(b), P(a), ¬P(a)} → tautology
Clause const cl = makeClause({pos(q(b_)), pos(p(a_)), neg(p(a_))});
EXPECT_TRUE(isTautology(cl));
}
TEST_F(SimplifyTest, NotTautologySamePolarity) {
// {P(a), P(a)} → NOT tautology (same polarity)
Clause const cl = makeClause({pos(p(a_)), pos(p(a_))});
EXPECT_FALSE(isTautology(cl));
}
TEST_F(SimplifyTest, NotTautologyDifferentAtoms) {
// {P(a), ¬P(b)} → NOT tautology (different atoms)
Clause const cl = makeClause({pos(p(a_)), neg(p(b_))});
EXPECT_FALSE(isTautology(cl));
}
TEST_F(SimplifyTest, NotTautologyDifferentPredicates) {
// {P(a), ¬Q(a)} → NOT tautology (different predicates)
Clause const cl = makeClause({pos(p(a_)), neg(q(a_))});
EXPECT_FALSE(isTautology(cl));
}
TEST_F(SimplifyTest, NotTautologyEmptyClause) {
Clause const cl = makeClause({});
EXPECT_FALSE(isTautology(cl));
}
TEST_F(SimplifyTest, NotTautologySingleLiteral) {
Clause const cl = makeClause({pos(p(a_))});
EXPECT_FALSE(isTautology(cl));
}
TEST_F(SimplifyTest, TautologyVariableAtom) {
// {P(X), ¬P(X)} → tautology (same variable term)
Clause const cl = makeClause({pos(p(x_)), neg(p(x_))});
EXPECT_TRUE(isTautology(cl));
}
TEST_F(SimplifyTest, NotTautologyVarVsConst) {
// {P(X), ¬P(a)} → NOT tautology (syntactically different)
Clause const cl = makeClause({pos(p(x_)), neg(p(a_))});
EXPECT_FALSE(isTautology(cl));
}
// ═══════════════════════════════════════════════════════════════════════════
// Subsumption Tests
// ═══════════════════════════════════════════════════════════════════════════
TEST_F(SimplifyTest, SubsumesSubset) {
// {P(a)} subsumes {P(a), Q(b)}
Clause const gen = makeClause({pos(p(a_))});
Clause const spec = makeClause({pos(p(a_)), pos(q(b_))});
EXPECT_TRUE(subsumes(gen, spec));
}
TEST_F(SimplifyTest, SubsumesIdentical) {
// {P(a), Q(b)} subsumes {P(a), Q(b)}
Clause const c1 = makeClause({pos(p(a_)), pos(q(b_))});
Clause const c2 = makeClause({pos(p(a_)), pos(q(b_))});
EXPECT_TRUE(subsumes(c1, c2));
}
TEST_F(SimplifyTest, SubsumesEmptyClause) {
// {} subsumes everything
Clause const empty = makeClause({});
Clause const cl = makeClause({pos(p(a_)), neg(q(b_))});
EXPECT_TRUE(subsumes(empty, cl));
}
TEST_F(SimplifyTest, SubsumesNotLarger) {
// {P(a), Q(b)} does NOT subsume {P(a)}
Clause const gen = makeClause({pos(p(a_)), pos(q(b_))});
Clause const spec = makeClause({pos(p(a_))});
EXPECT_FALSE(subsumes(gen, spec));
}
TEST_F(SimplifyTest, SubsumesRequiresSamePolarity) {
// {P(a)} does NOT subsume {¬P(a)}
Clause const gen = makeClause({pos(p(a_))});
Clause const spec = makeClause({neg(p(a_))});
EXPECT_FALSE(subsumes(gen, spec));
}
TEST_F(SimplifyTest, SubsumesRequiresSameAtom) {
// {P(a)} does NOT subsume {P(b)}
Clause const gen = makeClause({pos(p(a_))});
Clause const spec = makeClause({pos(p(b_))});
EXPECT_FALSE(subsumes(gen, spec));
}
TEST_F(SimplifyTest, SubsumesNegativeLiterals) {
// {¬P(a)} subsumes {¬P(a), Q(b)}
Clause const gen = makeClause({neg(p(a_))});
Clause const spec = makeClause({neg(p(a_)), pos(q(b_))});
EXPECT_TRUE(subsumes(gen, spec));
}
TEST_F(SimplifyTest, SubsumesOrderIndependent) {
// {Q(b), P(a)} subsumes {P(a), Q(b), R(a,b)} regardless of order
Clause const gen = makeClause({pos(q(b_)), pos(p(a_))});
Clause const spec = makeClause({pos(p(a_)), pos(q(b_)), pos(r(a_, b_))});
EXPECT_TRUE(subsumes(gen, spec));
}
} // namespace
} // namespace atp