/*
* 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/search/prover.h"
#include "atp/core/types.h"
#include "atp/infer/resolution.h"
#include "atp/infer/unification.h"
#include "atp/simplify/subsumption.h"
#include "atp/simplify/tautology.h"
#include
namespace atp {
Prover::Prover(TermBank& bank, ClauseStore& store, ProverConfig config)
: bank_(bank),
store_(store),
config_(std::move(config)),
unprocessed_(config_.comparator) {}
void Prover::addClauses(std::vector clauses) {
for (auto& clause : clauses) {
clause.depth = 0;
ClauseId id = store_.addClause(clause);
unprocessed_.push(store_.getClause(id));
}
}
ProverResult Prover::prove() {
size_t iterations = 0;
while (!unprocessed_.empty()) {
// ─── Resource limits ──────────────────────────────────────────
if (iterations >= config_.max_iterations ||
store_.size() >= config_.max_clauses) {
return ProverResult::kTimeout;
}
++iterations;
// ─── 1. Select the best clause from unprocessed ──────────────
Clause given = unprocessed_.top();
unprocessed_.pop();
// ─── 2. Check if given is itself the empty clause ────────────
if (given.isEmpty()) {
empty_clause_id_ = given.id;
return ProverResult::kTheorem;
}
// ─── 3. Forward redundancy checks ────────────────────────────
if (isTautology(given)) {
continue;
}
bool forward_subsumed = false;
for (ClauseId pid : processed_) {
if (subsumes(store_.getClause(pid), given)) {
forward_subsumed = true;
break;
}
}
if (forward_subsumed) {
continue;
}
// ─── 4. Backward subsumption ─────────────────────────────────
// Remove from processed all clauses that are subsumed by given.
// Use erase-remove idiom for O(n) scan instead of repeated erase.
std::erase_if(processed_, [&](ClauseId pid) {
return subsumes(given, store_.getClause(pid));
});
// ─── 5. Generate new clauses ─────────────────────────────────
// Inference BEFORE adding given to processed — matches doc §4.6.
std::vector new_clauses;
const UnificationConfig uconfig{.enable_occurs_check = config_.enable_occurs_check};
// 5a. Resolve given with every processed clause (both directions)
for (ClauseId pid : processed_) {
const Clause& pc = store_.getClause(pid);
auto resolvents1 = allResolvents(bank_, given, pc, uconfig);
auto resolvents2 = allResolvents(bank_, pc, given, uconfig);
for (auto& r : resolvents1) {
new_clauses.push_back(std::move(r));
}
for (auto& r : resolvents2) {
new_clauses.push_back(std::move(r));
}
}
// 5b. Factor given (only once)
auto factors = factor(bank_, given, uconfig);
for (auto& f : factors) {
new_clauses.push_back(std::move(f));
}
// ─── 6. Add given to processed AFTER inference ───────────────
processed_.push_back(given.id);
// ─── 7. Process new clauses ──────────────────────────────────
for (auto& nc : new_clauses) {
// 7a. Check for empty clause → proof found!
if (nc.isEmpty()) {
ClauseId eid = store_.addClause(std::move(nc));
empty_clause_id_ = eid;
return ProverResult::kTheorem;
}
// 7b. Skip tautologies
if (isTautology(nc)) {
continue;
}
// 7c. Set depth
uint32_t d1 = (nc.parent1 != kInvalidId)
? store_.getClause(nc.parent1).depth
: 0;
uint32_t d2 = (nc.parent2 != kInvalidId)
? store_.getClause(nc.parent2).depth
: 0;
nc.depth = static_cast(std::max(d1, d2) + 1);
// 7d. Store the clause (needed for forward subsumption check)
ClauseId nid = store_.addClause(nc);
const Clause& stored_nc = store_.getClause(nid);
// 7e. Forward subsumption on new clause
bool new_subsumed = false;
for (ClauseId pid : processed_) {
if (subsumes(store_.getClause(pid), stored_nc)) {
new_subsumed = true;
break;
}
}
if (new_subsumed) {
continue;
}
// 7f. Enqueue
unprocessed_.push(stored_nc);
}
}
return ProverResult::kSaturation;
}
std::optional Prover::getEmptyClauseId() const {
return empty_clause_id_;
}
} // namespace atp