/*
* 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;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import kodkod.ast.Formula;
import kodkod.ast.IntExpression;
import kodkod.ast.Relation;
import kodkod.engine.config.Options;
import kodkod.engine.fol2sat.HigherOrderDeclException;
import kodkod.engine.fol2sat.Translation;
import kodkod.engine.fol2sat.TranslationLog;
import kodkod.engine.fol2sat.Translator;
import kodkod.engine.fol2sat.UnboundLeafException;
import kodkod.engine.satlab.SATAbortedException;
import kodkod.engine.satlab.SATProver;
import kodkod.engine.satlab.SATSolver;
import kodkod.instance.Bounds;
import kodkod.instance.Instance;
import kodkod.instance.TupleSet;
/**
* A computational engine for solving relational satisfiability problems.
* Such a problem is described by a {@link kodkod.ast.Formula formula} in
* first order relational logic; finite {@link kodkod.instance.Bounds bounds} on
* the value of each {@link Relation relation} constrained by the formula; and
* a set of {@link kodkod.engine.config.Options options} specifying, among other global
* parameters, the length of bitvectors that describe the meaning of
* {@link IntExpression integer expressions} in the given formula. The options are
* usually reused between invocations to the {@linkplain #solve(Formula, Bounds) solve}
* methods, so they are specified as a part of the {@linkplain KodkodSolver solver} state.
*
* <p>
* A {@link Solver} takes as input a relational problem and produces a
* satisfying model or an {@link Instance instance} of it, if one exists. It can also
* produce a {@link Proof proof} of unsatisfiability for problems with no models,
* if the {@link kodkod.engine.config.Options options} specify the use of a
* {@linkplain SATProver proof logging SAT solver}.
* </p>
*
* @specfield options: Options
* @author Emina Torlak
*/
public final class Solver implements KodkodSolver {
private final Options options;
/**
* Constructs a new Solver with the default options.
* @ensures this.options' = new Options()
*/
public Solver() {
this.options = new Options();
}
/**
* Constructs a new Solver with the given options.
* @ensures this.options' = options
* @throws NullPointerException options = null
*/
public Solver(Options options) {
if (options == null)
throw new NullPointerException();
this.options = options;
}
/**
* Returns the Options object used by this Solver
* to guide translation of formulas from first-order
* logic to cnf.
* @return this.options
*/
public Options options() {
return options;
}
/**
* {@inheritDoc}
* @see kodkod.engine.KodkodSolver#free()
*/
public void free() {}
/**
* Attempts to satisfy the given {@code formula} and {@code bounds} with respect to
* {@code this.options} or, optionally, prove the problem's unsatisfiability. If the method
* completes normally, the result is a {@linkplain Solution solution} containing either an
* {@linkplain Instance instance} of the given problem or, optionally, a {@linkplain Proof proof} of
* its unsatisfiability. An unsatisfiability
* proof will be constructed iff {@code this.options.solver} specifies a {@linkplain SATProver} and
* {@code this.options.logTranslation > 0}.
*
* @return some sol: {@link Solution} |
* some sol.instance() =>
* sol.instance() in MODELS(formula, bounds, this.options) else
* UNSAT(formula, bound, this.options)
*
* @throws NullPointerException formula = null || bounds = null
* @throws UnboundLeafException the formula contains an undeclared variable or a relation not mapped by the given bounds
* @throws HigherOrderDeclException the formula contains a higher order declaration that cannot
* be skolemized, or it can be skolemized but {@code this.options.skolemDepth} is insufficiently large
* @throws AbortedException this solving task was aborted
* @see Options
* @see Solution
* @see Instance
* @see Proof
*/
public Solution solve(Formula formula, Bounds bounds) throws HigherOrderDeclException, UnboundLeafException, AbortedException {
final long startTransl = System.currentTimeMillis();
try {
final Translation.Whole translation = Translator.translate(formula, bounds, options);
final long endTransl = System.currentTimeMillis();
if (translation.trivial())
return trivial(translation, endTransl - startTransl);
final SATSolver cnf = translation.cnf();
options.reporter().solvingCNF(translation.numPrimaryVariables(), cnf.numberOfVariables(), cnf.numberOfClauses());
final long startSolve = System.currentTimeMillis();
final boolean isSat = cnf.solve();
final long endSolve = System.currentTimeMillis();
final Statistics stats = new Statistics(translation, endTransl - startTransl, endSolve - startSolve);
return isSat ? sat(translation, stats) : unsat(translation, stats);
} catch (SATAbortedException sae) {
throw new AbortedException(sae);
}
}
/**
* Attempts to find all solutions to the given formula with respect to the specified bounds or
* to prove the formula's unsatisfiability.
* If the operation is successful, the method returns an iterator over n Solution objects. The outcome
* of the first n-1 solutions is SAT or trivially SAT, and the outcome of the nth solution is UNSAT
* or tirivally UNSAT. Note that an unsatisfiability
* proof will be constructed for the last solution iff this.options specifies the use of a core extracting SATSolver.
* Additionally, the CNF variables in the proof can be related back to the nodes in the given formula
* iff this.options has variable tracking enabled. Translation logging also requires that
* there are no subnodes in the given formula that are both syntactically shared and contain free variables.
*
* @return an iterator over all the Solutions to the formula with respect to the given bounds
* @throws NullPointerException formula = null || bounds = null
* @throws kodkod.engine.fol2sat.UnboundLeafException the formula contains an undeclared variable or
* a relation not mapped by the given bounds
* @throws kodkod.engine.fol2sat.HigherOrderDeclException the formula contains a higher order declaration that cannot
* be skolemized, or it can be skolemized but this.options.skolemize is false.
* @throws AbortedException this solving task was interrupted with a call to Thread.interrupt on this thread
* @throws IllegalStateException !this.options.solver().incremental()
* @see Solution
* @see Options
* @see Proof
*/
public Iterator<Solution> solveAll(final Formula formula, final Bounds bounds)
throws HigherOrderDeclException, UnboundLeafException, AbortedException {
if (!options.solver().incremental())
throw new IllegalArgumentException("cannot enumerate solutions without an incremental solver.");
return new SolutionIterator(formula, bounds, options);
}
/**
* {@inheritDoc}
* @see java.lang.Object#toString()
*/
public String toString() {
return options.toString();
}
/**
* Returns the result of solving a sat formula.
* @param bounds Bounds with which solve() was called
* @param translation the translation
* @param stats translation / solving stats
* @return the result of solving a sat formula.
*/
private static Solution sat(Translation.Whole translation, Statistics stats) {
final Solution sol = Solution.satisfiable(stats, translation.interpret());
translation.cnf().free();
return sol;
}
/**
* Returns the result of solving an unsat formula.
* @param translation the translation
* @param stats translation / solving stats
* @return the result of solving an unsat formula.
*/
private static Solution unsat(Translation.Whole translation, Statistics stats) {
final SATSolver cnf = translation.cnf();
final TranslationLog log = translation.log();
if (cnf instanceof SATProver && log != null) {
return Solution.unsatisfiable(stats, new ResolutionBasedProof((SATProver) cnf, log));
} else { // can free memory
final Solution sol = Solution.unsatisfiable(stats, null);
cnf.free();
return sol;
}
}
/**
* Returns the result of solving a trivially (un)sat formula.
* @param translation trivial translation produced as the result of {@code translation.formula}
* simplifying to a constant with respect to {@code translation.bounds}
* @param translTime translation time
* @return the result of solving a trivially (un)sat formula.
*/
private static Solution trivial(Translation.Whole translation, long translTime) {
final Statistics stats = new Statistics(0, 0, 0, translTime, 0);
final Solution sol;
if (translation.cnf().solve()) {
sol = Solution.triviallySatisfiable(stats, translation.interpret());
} else {
sol = Solution.triviallyUnsatisfiable(stats, trivialProof(translation.log()));
}
translation.cnf().free();
return sol;
}
/**
* Returns a proof for the trivially unsatisfiable log.formula,
* provided that log is non-null. Otherwise returns null.
* @requires log != null => log.formula is trivially unsatisfiable
* @return a proof for the trivially unsatisfiable log.formula,
* provided that log is non-null. Otherwise returns null.
*/
private static Proof trivialProof(TranslationLog log) {
return log==null ? null : new TrivialProof(log);
}
/**
* An iterator over all solutions of a model.
* @author Emina Torlak
*/
private static final class SolutionIterator implements Iterator<Solution> {
private Translation.Whole translation;
private long translTime;
private int trivial;
/**
* Constructs a solution iterator for the given formula, bounds, and options.
*/
SolutionIterator(Formula formula, Bounds bounds, Options options) {
this.translTime = System.currentTimeMillis();
this.translation = Translator.translate(formula, bounds, options);
this.translTime = System.currentTimeMillis() - translTime;
this.trivial = 0;
}
/**
* Returns true if there is another solution.
* @see java.util.Iterator#hasNext()
*/
public boolean hasNext() { return translation != null; }
/**
* Returns the next solution if any.
* @see java.util.Iterator#next()
*/
public Solution next() {
if (!hasNext()) throw new NoSuchElementException();
try {
return translation.trivial() ? nextTrivialSolution() : nextNonTrivialSolution();
} catch (SATAbortedException sae) {
translation.cnf().free();
throw new AbortedException(sae);
}
}
/** @throws UnsupportedOperationException */
public void remove() { throw new UnsupportedOperationException(); }
/**
* Solves {@code translation.cnf} and adds the negation of the
* found model to the set of clauses. The latter has the
* effect of forcing the solver to come up with the next solution
* or return UNSAT. If {@code this.translation.cnf.solve()} is false,
* sets {@code this.translation} to null.
* @requires this.translation != null
* @ensures this.translation.cnf is modified to eliminate
* the current solution from the set of possible solutions
* @return current solution
*/
private Solution nextNonTrivialSolution() {
final Translation.Whole transl = translation;
final SATSolver cnf = transl.cnf();
final int primaryVars = transl.numPrimaryVariables();
transl.options().reporter().solvingCNF(primaryVars, cnf.numberOfVariables(), cnf.numberOfClauses());
final long startSolve = System.currentTimeMillis();
final boolean isSat = cnf.solve();
final long endSolve = System.currentTimeMillis();
final Statistics stats = new Statistics(transl, translTime, endSolve - startSolve);
final Solution sol;
if (isSat) {
// extract the current solution; can't use the sat(..) method because it frees the sat solver
sol = Solution.satisfiable(stats, transl.interpret());
// add the negation of the current model to the solver
final int[] notModel = new int[primaryVars];
for(int i = 1; i <= primaryVars; i++) {
notModel[i-1] = cnf.valueOf(i) ? -i : i;
}
cnf.addClause(notModel);
} else {
sol = unsat(transl, stats); // this also frees up solver resources, if any
translation = null; // unsat, no more solutions
}
return sol;
}
/**
* Returns the trivial solution corresponding to the trivial translation stored in {@code this.translation},
* and if {@code this.translation.cnf.solve()} is true, sets {@code this.translation} to a new translation
* that eliminates the current trivial solution from the set of possible solutions. The latter has the effect
* of forcing either the translator or the solver to come up with the next solution or return UNSAT.
* If {@code this.translation.cnf.solve()} is false, sets {@code this.translation} to null.
* @requires this.translation != null
* @ensures this.translation is modified to eliminate the current trivial solution from the set of possible solutions
* @return current solution
*/
private Solution nextTrivialSolution() {
final Translation.Whole transl = this.translation;
final Solution sol = trivial(transl, translTime); // this also frees up solver resources, if unsat
if (sol.instance()==null) {
translation = null; // unsat, no more solutions
} else {
trivial++;
final Bounds bounds = transl.bounds();
final Bounds newBounds = bounds.clone();
final List<Formula> changes = new ArrayList<Formula>();
for(Relation r : bounds.relations()) {
final TupleSet lower = bounds.lowerBound(r);
if (lower != bounds.upperBound(r)) { // r may change
if (lower.isEmpty()) {
changes.add(r.some());
} else {
final Relation rmodel = Relation.nary(r.name()+"_"+trivial, r.arity());
newBounds.boundExactly(rmodel, lower);
changes.add(r.eq(rmodel).not());
}
}
}
// nothing can change => there can be no more solutions (besides the current trivial one).
// note that transl.formula simplifies to the constant true with respect to
// transl.bounds, and that newBounds is a superset of transl.bounds.
// as a result, finding the next instance, if any, for transl.formula.and(Formula.or(changes))
// with respect to newBounds is equivalent to finding the next instance of Formula.or(changes) alone.
final Formula formula = changes.isEmpty() ? Formula.FALSE : Formula.or(changes);
final long startTransl = System.currentTimeMillis();
translation = Translator.translate(formula, newBounds, transl.options());
translTime += System.currentTimeMillis() - startTransl;
}
return sol;
}
}
}