/*
* Kodkod -- Copyright (c) 2005-present, Emina Torlak
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package kodkod.engine.fol2sat;
import static kodkod.engine.bool.Operator.AND;
import kodkod.engine.bool.BooleanConstant;
import kodkod.engine.bool.BooleanFactory;
import kodkod.engine.bool.BooleanFormula;
import kodkod.engine.bool.BooleanVariable;
import kodkod.engine.bool.BooleanVisitor;
import kodkod.engine.bool.ITEGate;
import kodkod.engine.bool.MultiGate;
import kodkod.engine.bool.NotGate;
import kodkod.engine.bool.Operator;
import kodkod.engine.satlab.SATFactory;
import kodkod.engine.satlab.SATSolver;
import kodkod.util.ints.IntSet;
import kodkod.util.ints.IntTreeSet;
/**
* Transforms a boolean circuit into a formula in conjunctive
* normal form.
* @specfield factory: {@link BooleanFactory}
* @specfield roots: some factory.components
* @specfield cnf: {@link SATSolver}
* @invariant max(abs(roots.label)) = max(cnf.variables)
* @invariant meaning(roots) = meaning(cnf.clauses)
* @author Emina Torlak
*/
abstract class Bool2CNFTranslator implements BooleanVisitor<int[], Object> {
/**
* Creates a new instance of SATSolver using the provided factory
* and uses it to translate the given circuit into conjunctive normal form
* using the <i>definitional translation algorithm</i>.
* The {@code maxPrimaryVar} parameter is required to contain the maximum label of any primary variable
* allocated during translation from FOL to boolean. This method assumes that
* all variables allocated during translation have contiguous labels.
* @requires let boolFactory = components.circuit |
* boolFactory.maxVariable() = maxPrimaryVar &&
* no f: boolFactory.components - BooleanVariable | 1 <= f.label <= maxPrimaryVar
* @return some cnf: SATSolver | cnf in factory.instance() &&
* max(cnf.variables) = max(abs(circuit.label), maxPrimaryVar) &&
* meaning(circuit) = meaning(cnf.clauses)
*/
static SATSolver translate(final BooleanFormula circuit, final int maxPrimaryVar, final SATFactory factory) {
final int maxLiteral = StrictMath.abs(circuit.label());
final Bool2CNFTranslator translator = new Bool2CNFTranslator(factory.instance()) {
final PolarityDetector pdetector = (new PolarityDetector(maxPrimaryVar, maxLiteral)).apply(circuit);
boolean positive(int label) { return pdetector.positive(label); }
boolean negative(int label) { return pdetector.negative(label); }
};
return translator.translate(circuit, maxPrimaryVar).solver;
}
/**
* Creates a new instance of SATSolver using the provided factory
* and initializes it with the trivial translation of the given boolean value.
* If {@code value} is true, the translation is a solver with no variables and no clauses. Otherwise, the
* translation is a solver with no variables and a single empty (conflict) clause.
* @return some cnf : SATSolver |
* no cnf.variables &&
* (value.booleanValue() => no cnf.clauses else (one cnf.clauses && no cnf.clauses.literals))
*/
static SATSolver translate(BooleanConstant value, final SATFactory factory) {
final SATSolver cnf = factory.instance();
if (!value.booleanValue()) {
cnf.addClause(new int[0]); // unsat
} // sat
return cnf;
}
/**
* Returns a new Bool2CNFTranslator that is initialized with the translation of the given circuit.
* The {@code maxPrimaryVar} parameter is required to contain the maximum label of any primary variable
* allocated during translation from FOL to boolean.
* @requires let boolFactory = components.circuit | boolFactory.maxVariable() = maxPrimaryVar
* @requires factory.incremental
* @return some t: Bool2CNFTranslator | t.roots = circuit && t.factory = components.circuit &&
* max(t.cnf.variables) = max(abs(circuit.label), maxPrimaryVar) &&
* meaning(circuit) = meaning(t.cnf.clauses)
*/
static Bool2CNFTranslator translateIncremental(final BooleanFormula circuit, final int maxPrimaryVar, final SATFactory factory) {
assert factory.incremental();
final Bool2CNFTranslator translator = new Bool2CNFTranslator(factory.instance()) { };
return translator.translate(circuit, maxPrimaryVar);
}
/**
* Returns a new Bool2CNFTranslator that is initialized with the trivial translation of the given boolean value.
* If {@code value} is true, the translation is a solver with no variables and no clauses. Otherwise, the
* translation is a solver with no variables and a single empty (conflict) clause.
* @requires factory.incremental
* @return some t: Bool2CNFTranslator | t.roots = value &&
* no t.cnf.variables &&
* (value.booleanValue() => no t.cnf.clauses else (one t.cnf.clauses && no t.cnf.clauses.literals))
*/
static Bool2CNFTranslator translateIncremental(BooleanConstant value, final SATFactory factory) {
assert factory.incremental();
return new Bool2CNFTranslator(translate(value, factory)) { };
}
/**
* Updates the given Bool2CNFTranslator with the translation of the given circuit.
* The behavior of this method is undefined if it is called
* after translator.solver has returned UNSAT. The {@code maxPrimaryVar} parameter is required
* to contain the maximum label of any primary variable
* allocated during translation from FOL to boolean.
* @requires circuit in translator.factory.components
* @requires maxPrimaryVar = translator.factory.maxVariable()
* @requires translator.solver.solve()
* @ensures translator.roots' = translator.roots + circuit &&
* max(translator.cnf.variables) = max(abs(circuit.label), abs(translator.roots.label), maxPrimaryVar) &&
* translator.cnf.clauses in translator.cnf.clauses' &&
* translator.cnf.clauses' = CNF(circuit) + translator.cnf.clauses
* @return translator
*/
static Bool2CNFTranslator translateIncremental(final BooleanFormula circuit, final int maxPrimaryVar, final Bool2CNFTranslator translator) {
return translator.translate(circuit, maxPrimaryVar);
}
private final SATSolver solver;
private final IntSet visited;
private final int[] unaryClause = new int[1];
private final int[] binaryClause = new int[2];
private final int[] ternaryClause = new int[3];
/**
* Constructs a translator for the given circuit.
* @requires no solver.variables && solver.clauses
* @ensures this.solver' = solver
*/
private Bool2CNFTranslator(SATSolver solver) {
this.solver = solver;
this.visited = new IntTreeSet();
}
/**
* Applies this translator to the given circuit, adding the translation of the
* circuit to this.solver, and returns the translator.
* @requires circuit in this.factory.components
* @requires maxPrimaryVar = this.factory.maxPrimaryVariable()
* @ensures this.solver.variables' = this.solver.variables +
* { i: int | solver.numberOfVariables() < i <= max(abs(circuit.label), maxPrimaryVar) }
* @effects this.solver.clauses' = this.solver.clauses + CNF(circuit)
* @return this
*/
private Bool2CNFTranslator translate(BooleanFormula circuit, int maxPrimaryVar) {
final int newVars = Math.max(Math.abs(circuit.label()), maxPrimaryVar) - solver.numberOfVariables();
// System.out.println("circuit.label=" + Math.abs(circuit.label()));
// System.out.println("maxPrimaryVar=" + maxPrimaryVar);
// System.out.println("solver.vars=" + solver.numberOfVariables());
if (newVars > 0)
solver.addVariables(newVars);
if (circuit.op()==Operator.AND) {
for(BooleanFormula input : circuit) {
input.accept(this, null);
}
for(BooleanFormula input : circuit) {
unaryClause[0] = input.label();
solver.addClause(unaryClause);
}
} else {
solver.addClause(circuit.accept(this, null));
}
return this;
}
/**
* Returns this.solver.
* @return this.solver
*/
public SATSolver solver() { return solver; }
/**
* Returns true if the gate with the given label occurs (or may occur) positively in this.roots.
* @requires some f: (MultiGate + ITEGate) & components.(this.roots) | f.label = label
* @return true if the gate with the given label occurs (or may occur) positively in this.roots
*/
boolean positive(int label) { return true; }
/**
* Returns true if the gate with the given label occurs (or may occur) negatively in this.roots.
* @requires some f: (MultiGate + ITEGate) & components.(this.roots) | f.label = label
* @return true if the gate with the given label occurs (or may occur) negatively in this.roots.
*/
boolean negative(int label) { return true; }
/** @return 0->lit */
private final int[] clause(int lit) {
unaryClause[0] = lit;
return unaryClause;
}
/** @return 0->lit0 + 1->lit1 */
private final int[] clause(int lit0, int lit1) {
binaryClause[0] = lit0; binaryClause[1] = lit1;
return binaryClause;
}
/** @return 0->lit0 + 1->lit1 + 2->lit2 */
private final int[] clause(int lit0, int lit1, int lit2) {
ternaryClause[0] = lit0; ternaryClause[1] = lit1; ternaryClause[2] = lit2;
return ternaryClause;
}
/**
* Adds translation clauses to the solver and returns an array containing the
* gate's literal. The CNF clauses are generated according to the standard SAT to CNF translation:
* o = AND(i1, i2, ... ik) ---> (i1 | !o) & (i2 | !o) & ... & (ik | !o) & (!i1 | !i2 | ... | !ik | o),
* o = OR(i1, i2, ... ik) ---> (!i1 | o) & (!i2 | o) & ... & (!ik | o) & (i1 | i2 | ... | ik | !o).
* @return o: int[] | o.length = 1 && o.[0] = multigate.literal
* @ensures if the multigate has not yet been visited, its children are visited
* and the clauses are added to the solver connecting the multigate's literal to
* its input literal, as described above.
*/
public final int[] visit(MultiGate multigate, Object arg) {
final int oLit = multigate.label();
if (visited.add(oLit)) {
final int sgn; final boolean p, n;
if (multigate.op()==AND) {
sgn = 1; p = positive(oLit); n = negative(oLit);
} else { // multigate.op()==OR
sgn = -1; n = positive(oLit); p = negative(oLit);
}
final int[] lastClause = n ? new int[multigate.size()+1] : null;
final int output = oLit * -sgn;
int i = 0;
for(BooleanFormula input : multigate) {
int iLit = input.accept(this, arg)[0];
if (p) {
solver.addClause(clause(iLit * sgn, output));
}
if (n) {
lastClause[i++] = iLit * -sgn;
}
}
if (n) {
lastClause[i] = oLit * sgn;
solver.addClause(lastClause);
}
}
return clause(oLit);
}
/**
* Adds translation clauses to the solver and returns an array containing the
* gate's literal. The CNF clauses are generated according to the standard SAT to CNF translation:
* o = ITE(i, t, e) ---> (!i | !t | o) & (!i | t | !o) & (i | !e | o) & (i | e | !o)
* @return o: int[] | o.length = 1 && o.[0] = itegate.literal
* @ensures if the itegate has not yet been visited, its children are visited
* and the clauses are added to the solver connecting the multigate's literal to
* its input literal, as described above.
*/
public final int[] visit(ITEGate itegate, Object arg) {
final int oLit = itegate.label();
if (visited.add(oLit)) {
final int i = itegate.input(0).accept(this, arg)[0];
final int t = itegate.input(1).accept(this, arg)[0];
final int e = itegate.input(2).accept(this, arg)[0];
final boolean p = positive(oLit), n = negative(oLit);
if (p) {
solver.addClause(clause(-i, t, -oLit));
solver.addClause(clause(i, e, -oLit));
// redundant clause that strengthens unit propagation
solver.addClause(clause(t, e, -oLit));
}
if (n) {
solver.addClause(clause(-i, -t, oLit));
solver.addClause(clause(i, -e, oLit));
// redundant clause that strengthens unit propagation
solver.addClause(clause(-t, -e, oLit));
}
}
return clause(oLit);
}
/**
* Returns the negation of the result of visiting negation.input, wrapped in an array.
* @return o: int[] | o.length = 1 && o[0] = - translate(negation.inputs)[0]
* */
public final int[] visit(NotGate negation, Object arg) {
return clause(-negation.input(0).accept(this, arg)[0]);
}
/**
* Returns the variable's literal wrapped in a an array.
* @return o: int[] | o.length = 1 && o[0] = variable.literal
*/
public final int[] visit(BooleanVariable variable, Object arg) {
return clause(variable.label());
}
/**
* Helper visitor that detects polarity of subformulas.
* @specfield root: BooleanFormula // the root of the DAG for whose components we are storing pdetector information
*/
private static final class PolarityDetector implements BooleanVisitor<Object, Integer> {
final int offset;
/**
* @invariant all i : [0..polarity.length) |
* pdetector[i] = 0 <=> formula with label offset + i has not been visited,
* pdetector[i] = 1 <=> formula with label offset + i has been visited with positive pdetector only,
* pdetector[i] = 2 <=> formula with label offset + i has been visited with negative pdetector only,
* pdetector[i] = 3 <=> formula with label offset + i has been visited with both polarities
*/
private final int[] polarity;
private final Integer[] ints = { Integer.valueOf(3), Integer.valueOf(1), Integer.valueOf(2) };
/**
* Creates a new pdetector detector and applies it to the given circuit.
* This constructor assumes that all primary variables have contiguous labels, which
* may not be the case during incremental translation.
* @requires maxLiteral = |root.label()|
*/
PolarityDetector(int numPrimaryVars, int maxLiteral) {
this.offset = numPrimaryVars+1;
this.polarity = new int[StrictMath.max(0, maxLiteral-numPrimaryVars)];
}
/**
* Applies this detector to the given formula, and returns this.
* @requires this.root = root
* @ensures this.visit(root)
* @return this
*/
PolarityDetector apply(BooleanFormula root) {
root.accept(this, ints[1]);
return this;
}
/**
* Returns true if the formula with the given label occurs positively in this.root.
* @requires this visitor has been applied to this.root
* @requires label in (MultiGate + ITEGate).label
* @return true if the formula with the given label occurs positively in this.root.
*/
boolean positive(int label) {
return (polarity[label-offset] & 1) > 0;
}
/**
* Returns true if the formula with the given label occurs negatively in this.root.
* @requires this visitor has been applied to this.root
* @requires label in (MultiGate + ITEGate).label
* @return true if the formula with the given label occurs negatively in this.root.
*/
boolean negative(int label) {
return (polarity[label-offset] & 2) > 0;
}
/**
* Returns true if the given formula has been visited with the specified
* pdetector (1 = positive, 2 = negative, 3 = both). Otherwise records the visit and returns false.
* @requires formula in (MultiGate + ITEGate)
* @requires pdetector in this.ints
* @return true if the given formula has been visited with the specified
* pdetector. Otherwise records the visit and returns false.
*/
private boolean visited(BooleanFormula formula, Integer polarity) {
final int index = formula.label() - offset;
final int value = this.polarity[index];
return (this.polarity[index] = value | polarity) == value;
}
public Object visit(MultiGate multigate, Integer arg) {
if (!visited(multigate, arg)) {
for(BooleanFormula input : multigate) {
input.accept(this, arg);
}
}
return null;
}
public Object visit(ITEGate ite, Integer arg) {
if (!visited(ite, arg)) {
// the condition occurs both positively and negative in an ITE gate
ite.input(0).accept(this, ints[0]);
ite.input(1).accept(this, arg);
ite.input(2).accept(this, arg);
}
return null;
}
public Object visit(NotGate negation, Integer arg) {
return negation.input(0).accept(this, ints[3-arg]);
}
public Object visit(BooleanVariable variable, Integer arg) {
return null; // nothing to do
}
}
}