mirror of
https://github.com/llvm/llvm-project.git
synced 2025-05-01 12:36:09 +00:00

This component can be useful when creating implementations of `Solver`, as some SAT solvers require the input to be in 3-CNF. As part of making `CNFFormula` externally accessible, I have moved some member variables out of it that aren't really part of the representation of a 3-CNF formula and thus live better elsewhere: * `WatchedHead` and `NextWatched` have been moved to `WatchedLiteralsSolverImpl`, as they're part of the specific algorithm used by that SAT solver. * `Atomics` has become an output parameter of `buildCNF()` because it has to do with the relationship between a `CNFFormula` and the set of `Formula`s it is derived from rather than being an integral part of the representation of a 3-CNF formula. I have also made all member variables private and added appropriate accessors.
304 lines
11 KiB
C++
304 lines
11 KiB
C++
//===- CNFFormula.cpp -------------------------------------------*- C++ -*-===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// A representation of a boolean formula in 3-CNF.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "clang/Analysis/FlowSensitive/CNFFormula.h"
|
|
#include "llvm/ADT/DenseSet.h"
|
|
|
|
#include <queue>
|
|
|
|
namespace clang {
|
|
namespace dataflow {
|
|
|
|
namespace {
|
|
|
|
/// Applies simplifications while building up a BooleanFormula.
|
|
/// We keep track of unit clauses, which tell us variables that must be
|
|
/// true/false in any model that satisfies the overall formula.
|
|
/// Such variables can be dropped from subsequently-added clauses, which
|
|
/// may in turn yield more unit clauses or even a contradiction.
|
|
/// The total added complexity of this preprocessing is O(N) where we
|
|
/// for every clause, we do a lookup for each unit clauses.
|
|
/// The lookup is O(1) on average. This method won't catch all
|
|
/// contradictory formulas, more passes can in principle catch
|
|
/// more cases but we leave all these and the general case to the
|
|
/// proper SAT solver.
|
|
struct CNFFormulaBuilder {
|
|
// Formula should outlive CNFFormulaBuilder.
|
|
explicit CNFFormulaBuilder(CNFFormula &CNF) : Formula(CNF) {}
|
|
|
|
/// Adds the `L1 v ... v Ln` clause to the formula. Applies
|
|
/// simplifications, based on single-literal clauses.
|
|
///
|
|
/// Requirements:
|
|
///
|
|
/// `Li` must not be `NullLit`.
|
|
///
|
|
/// All literals must be distinct.
|
|
void addClause(ArrayRef<Literal> Literals) {
|
|
// We generate clauses with up to 3 literals in this file.
|
|
assert(!Literals.empty() && Literals.size() <= 3);
|
|
// Contains literals of the simplified clause.
|
|
llvm::SmallVector<Literal> Simplified;
|
|
for (auto L : Literals) {
|
|
assert(L != NullLit &&
|
|
llvm::all_of(Simplified, [L](Literal S) { return S != L; }));
|
|
auto X = var(L);
|
|
if (trueVars.contains(X)) { // X must be true
|
|
if (isPosLit(L))
|
|
return; // Omit clause `(... v X v ...)`, it is `true`.
|
|
else
|
|
continue; // Omit `!X` from `(... v !X v ...)`.
|
|
}
|
|
if (falseVars.contains(X)) { // X must be false
|
|
if (isNegLit(L))
|
|
return; // Omit clause `(... v !X v ...)`, it is `true`.
|
|
else
|
|
continue; // Omit `X` from `(... v X v ...)`.
|
|
}
|
|
Simplified.push_back(L);
|
|
}
|
|
if (Simplified.empty()) {
|
|
// Simplification made the clause empty, which is equivalent to `false`.
|
|
// We already know that this formula is unsatisfiable.
|
|
Formula.addClause(Simplified);
|
|
return;
|
|
}
|
|
if (Simplified.size() == 1) {
|
|
// We have new unit clause.
|
|
const Literal lit = Simplified.front();
|
|
const Variable v = var(lit);
|
|
if (isPosLit(lit))
|
|
trueVars.insert(v);
|
|
else
|
|
falseVars.insert(v);
|
|
}
|
|
Formula.addClause(Simplified);
|
|
}
|
|
|
|
/// Returns true if we observed a contradiction while adding clauses.
|
|
/// In this case then the formula is already known to be unsatisfiable.
|
|
bool isKnownContradictory() { return Formula.knownContradictory(); }
|
|
|
|
private:
|
|
CNFFormula &Formula;
|
|
llvm::DenseSet<Variable> trueVars;
|
|
llvm::DenseSet<Variable> falseVars;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
CNFFormula::CNFFormula(Variable LargestVar)
|
|
: LargestVar(LargestVar), KnownContradictory(false) {
|
|
Clauses.push_back(0);
|
|
ClauseStarts.push_back(0);
|
|
}
|
|
|
|
void CNFFormula::addClause(ArrayRef<Literal> lits) {
|
|
assert(llvm::all_of(lits, [](Literal L) { return L != NullLit; }));
|
|
|
|
if (lits.empty())
|
|
KnownContradictory = true;
|
|
|
|
const size_t S = Clauses.size();
|
|
ClauseStarts.push_back(S);
|
|
Clauses.insert(Clauses.end(), lits.begin(), lits.end());
|
|
}
|
|
|
|
CNFFormula buildCNF(const llvm::ArrayRef<const Formula *> &Formulas,
|
|
llvm::DenseMap<Variable, Atom> &Atomics) {
|
|
// The general strategy of the algorithm implemented below is to map each
|
|
// of the sub-values in `Vals` to a unique variable and use these variables in
|
|
// the resulting CNF expression to avoid exponential blow up. The number of
|
|
// literals in the resulting formula is guaranteed to be linear in the number
|
|
// of sub-formulas in `Vals`.
|
|
|
|
// Map each sub-formula in `Vals` to a unique variable.
|
|
llvm::DenseMap<const Formula *, Variable> FormulaToVar;
|
|
// Store variable identifiers and Atom of atomic booleans.
|
|
Variable NextVar = 1;
|
|
{
|
|
std::queue<const Formula *> UnprocessedFormulas;
|
|
for (const Formula *F : Formulas)
|
|
UnprocessedFormulas.push(F);
|
|
while (!UnprocessedFormulas.empty()) {
|
|
Variable Var = NextVar;
|
|
const Formula *F = UnprocessedFormulas.front();
|
|
UnprocessedFormulas.pop();
|
|
|
|
if (!FormulaToVar.try_emplace(F, Var).second)
|
|
continue;
|
|
++NextVar;
|
|
|
|
for (const Formula *Op : F->operands())
|
|
UnprocessedFormulas.push(Op);
|
|
if (F->kind() == Formula::AtomRef)
|
|
Atomics[Var] = F->getAtom();
|
|
}
|
|
}
|
|
|
|
auto GetVar = [&FormulaToVar](const Formula *F) {
|
|
auto ValIt = FormulaToVar.find(F);
|
|
assert(ValIt != FormulaToVar.end());
|
|
return ValIt->second;
|
|
};
|
|
|
|
CNFFormula CNF(NextVar - 1);
|
|
std::vector<bool> ProcessedSubVals(NextVar, false);
|
|
CNFFormulaBuilder builder(CNF);
|
|
|
|
// Add a conjunct for each variable that represents a top-level conjunction
|
|
// value in `Vals`.
|
|
for (const Formula *F : Formulas)
|
|
builder.addClause(posLit(GetVar(F)));
|
|
|
|
// Add conjuncts that represent the mapping between newly-created variables
|
|
// and their corresponding sub-formulas.
|
|
std::queue<const Formula *> UnprocessedFormulas;
|
|
for (const Formula *F : Formulas)
|
|
UnprocessedFormulas.push(F);
|
|
while (!UnprocessedFormulas.empty()) {
|
|
const Formula *F = UnprocessedFormulas.front();
|
|
UnprocessedFormulas.pop();
|
|
const Variable Var = GetVar(F);
|
|
|
|
if (ProcessedSubVals[Var])
|
|
continue;
|
|
ProcessedSubVals[Var] = true;
|
|
|
|
switch (F->kind()) {
|
|
case Formula::AtomRef:
|
|
break;
|
|
case Formula::Literal:
|
|
CNF.addClause(F->literal() ? posLit(Var) : negLit(Var));
|
|
break;
|
|
case Formula::And: {
|
|
const Variable LHS = GetVar(F->operands()[0]);
|
|
const Variable RHS = GetVar(F->operands()[1]);
|
|
|
|
if (LHS == RHS) {
|
|
// `X <=> (A ^ A)` is equivalent to `(!X v A) ^ (X v !A)` which is
|
|
// already in conjunctive normal form. Below we add each of the
|
|
// conjuncts of the latter expression to the result.
|
|
builder.addClause({negLit(Var), posLit(LHS)});
|
|
builder.addClause({posLit(Var), negLit(LHS)});
|
|
} else {
|
|
// `X <=> (A ^ B)` is equivalent to `(!X v A) ^ (!X v B) ^ (X v !A v
|
|
// !B)` which is already in conjunctive normal form. Below we add each
|
|
// of the conjuncts of the latter expression to the result.
|
|
builder.addClause({negLit(Var), posLit(LHS)});
|
|
builder.addClause({negLit(Var), posLit(RHS)});
|
|
builder.addClause({posLit(Var), negLit(LHS), negLit(RHS)});
|
|
}
|
|
break;
|
|
}
|
|
case Formula::Or: {
|
|
const Variable LHS = GetVar(F->operands()[0]);
|
|
const Variable RHS = GetVar(F->operands()[1]);
|
|
|
|
if (LHS == RHS) {
|
|
// `X <=> (A v A)` is equivalent to `(!X v A) ^ (X v !A)` which is
|
|
// already in conjunctive normal form. Below we add each of the
|
|
// conjuncts of the latter expression to the result.
|
|
builder.addClause({negLit(Var), posLit(LHS)});
|
|
builder.addClause({posLit(Var), negLit(LHS)});
|
|
} else {
|
|
// `X <=> (A v B)` is equivalent to `(!X v A v B) ^ (X v !A) ^ (X v
|
|
// !B)` which is already in conjunctive normal form. Below we add each
|
|
// of the conjuncts of the latter expression to the result.
|
|
builder.addClause({negLit(Var), posLit(LHS), posLit(RHS)});
|
|
builder.addClause({posLit(Var), negLit(LHS)});
|
|
builder.addClause({posLit(Var), negLit(RHS)});
|
|
}
|
|
break;
|
|
}
|
|
case Formula::Not: {
|
|
const Variable Operand = GetVar(F->operands()[0]);
|
|
|
|
// `X <=> !Y` is equivalent to `(!X v !Y) ^ (X v Y)` which is
|
|
// already in conjunctive normal form. Below we add each of the
|
|
// conjuncts of the latter expression to the result.
|
|
builder.addClause({negLit(Var), negLit(Operand)});
|
|
builder.addClause({posLit(Var), posLit(Operand)});
|
|
break;
|
|
}
|
|
case Formula::Implies: {
|
|
const Variable LHS = GetVar(F->operands()[0]);
|
|
const Variable RHS = GetVar(F->operands()[1]);
|
|
|
|
// `X <=> (A => B)` is equivalent to
|
|
// `(X v A) ^ (X v !B) ^ (!X v !A v B)` which is already in
|
|
// conjunctive normal form. Below we add each of the conjuncts of
|
|
// the latter expression to the result.
|
|
builder.addClause({posLit(Var), posLit(LHS)});
|
|
builder.addClause({posLit(Var), negLit(RHS)});
|
|
builder.addClause({negLit(Var), negLit(LHS), posLit(RHS)});
|
|
break;
|
|
}
|
|
case Formula::Equal: {
|
|
const Variable LHS = GetVar(F->operands()[0]);
|
|
const Variable RHS = GetVar(F->operands()[1]);
|
|
|
|
if (LHS == RHS) {
|
|
// `X <=> (A <=> A)` is equivalent to `X` which is already in
|
|
// conjunctive normal form. Below we add each of the conjuncts of the
|
|
// latter expression to the result.
|
|
builder.addClause(posLit(Var));
|
|
|
|
// No need to visit the sub-values of `Val`.
|
|
continue;
|
|
}
|
|
// `X <=> (A <=> B)` is equivalent to
|
|
// `(X v A v B) ^ (X v !A v !B) ^ (!X v A v !B) ^ (!X v !A v B)` which
|
|
// is already in conjunctive normal form. Below we add each of the
|
|
// conjuncts of the latter expression to the result.
|
|
builder.addClause({posLit(Var), posLit(LHS), posLit(RHS)});
|
|
builder.addClause({posLit(Var), negLit(LHS), negLit(RHS)});
|
|
builder.addClause({negLit(Var), posLit(LHS), negLit(RHS)});
|
|
builder.addClause({negLit(Var), negLit(LHS), posLit(RHS)});
|
|
break;
|
|
}
|
|
}
|
|
if (builder.isKnownContradictory()) {
|
|
return CNF;
|
|
}
|
|
for (const Formula *Child : F->operands())
|
|
UnprocessedFormulas.push(Child);
|
|
}
|
|
|
|
// Unit clauses that were added later were not
|
|
// considered for the simplification of earlier clauses. Do a final
|
|
// pass to find more opportunities for simplification.
|
|
CNFFormula FinalCNF(NextVar - 1);
|
|
CNFFormulaBuilder FinalBuilder(FinalCNF);
|
|
|
|
// Collect unit clauses.
|
|
for (ClauseID C = 1; C <= CNF.numClauses(); ++C) {
|
|
if (CNF.clauseSize(C) == 1) {
|
|
FinalBuilder.addClause(CNF.clauseLiterals(C)[0]);
|
|
}
|
|
}
|
|
|
|
// Add all clauses that were added previously, preserving the order.
|
|
for (ClauseID C = 1; C <= CNF.numClauses(); ++C) {
|
|
FinalBuilder.addClause(CNF.clauseLiterals(C));
|
|
if (FinalBuilder.isKnownContradictory()) {
|
|
break;
|
|
}
|
|
}
|
|
// It is possible there were new unit clauses again, but
|
|
// we stop here and leave the rest to the solver algorithm.
|
|
return FinalCNF;
|
|
}
|
|
|
|
} // namespace dataflow
|
|
} // namespace clang
|