/* * Kodkod -- Copyright (c) 2005-2012, 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 kodkod.ast.Formula; import kodkod.engine.config.Options; import kodkod.engine.fol2sat.HigherOrderDeclException; import kodkod.engine.fol2sat.SymmetryDetector; import kodkod.engine.fol2sat.Translation; import kodkod.engine.fol2sat.Translator; import kodkod.engine.fol2sat.UnboundLeafException; import kodkod.engine.satlab.SATAbortedException; import kodkod.engine.satlab.SATFactory; import kodkod.engine.satlab.SATSolver; import kodkod.instance.Bounds; import kodkod.instance.Universe; /** * A computational engine for solving a sequence of related relational * {@linkplain Formula formulas} and {@linkplain Bounds bounds} * (e.g., generated by a software verification or synthesis tool) * with respect to a given set of configuration {@link kodkod.engine.config.Options options}. * * <p> * An incremental Kodkod solver is initialized with an options instance <code>opt</code> which * cannot be modified further during the lifetime of the solver instance. If the * first problem <code>(f0, b0, opt)</code> passed to the solver via the {@link #solve(Formula, Bounds) solve} method is satisfiable, * the resulting {@linkplain Solution solution} and the underlying incremental * {@linkplain SATSolver SAT solver} are saved. When the {@linkplain #solve(Formula, Bounds) solve} * method is called again with a new formula <code>f1</code> and bounds <code>b1</code>, the translation * of <code>(f1, b1, opt)</code> is added to the stored SAT solver, which is then called to * obtain a solution for the problem <code>f0 && f1</code> and <code>b0 + b1</code>. We define * <code>b0 + b1</code> to be a disjoint union of the bindings in <code>b0</code> and <code>b1</code>, where * {@code b0.universe = b1.universe}, {@code b1.intBound} is empty, and {@code b1} contains no bindings for * relations that are bound by {@code b0}. This process can be repeated until the solver yields UNSAT. * Calling {@linkplain #solve(Formula, Bounds) solve} on a solver that has already returned UNSAT or that * has thrown an exception during a prior call to {@linkplain #solve(Formula, Bounds) solve} * will result in an {@link IllegalStateException}. * </p> * * <p> * To simplify the implementation, an {@linkplain IncrementalSolver} currently places * the following restriction on the sequence of bounds passed to its {@linkplain #solve(Formula, Bounds)} method: * the equivalence classes on the {@linkplain Universe universe} of interpretation that are * induced by the initial bounds <code>b0</code> must refine the equivalence classes induced by all subsequent bounds. * In particular, let <code>{ s0, ..., sn }</code> be the coarsest partition of <code>b0.universe</code> such * that every tupleset in <code>b0.lowerBound</code>, <code>b0.upperBound</code>, and * <code>b0.intBound</code> can be expressed as a union of cross-products of sets drawn from * <code>{ s0, ..., sn }</code>. Then, for each bound <code>bi</code> with which the solver's * {@linkplain #solve(Formula, Bounds) solve} method is called in the future, it must also be the case that * each tupleset in <code>bi.lowerBound</code>, <code>bi.upperBound</code> and * <code>bi.intBound</code> can be expressed in the same way, as a union of cross-products of sets drawn from * <code>{ s0, ..., sn }</code>. </p> * * <p> * The above requirement can be satisfied by making sure that <code>b0</code> * contains a set of unary relations <code>{ r0, ..., rn }</code> such that the lower/upper bounds * on all other relations that will ever be introduced can be expressed as a union of cross-products of * a subset of <code>b0</code>'s lower/upper bounds on <code>{ r0, ..., rn }</code>. One can think of the bounds on * <code>{ r0, ..., rn }</code> as representing sorts or types, and every relation's bounds should be * expressible in terms of these types. Note that having each atom in the universe appear in a lower/upper * bound by itself (e.g., <code>b0.lowerBound[ri] = b0.upperBound[ri] = {<ai>}</code>) will trivially satisfy * this requirement, but it is better to group related atoms into sets to enable symmetry breaking. * </p> * * <p>We additionally require {@linkplain Options#logTranslation() opt.logTranslation} to be * {@linkplain Options#setLogTranslation(int) disabled} and {@linkplain Options#solver() opt.solver} * to specify an {@linkplain SATFactory#incremental() incremental} SAT solver. Note that these * restrictions prevent unsat core extraction.</p> * * @specfield options: {@link Options} * @specfield bounds: lone {@link Bounds} * @specfield formulas: set {@link Formula} * @invariant formulas.*components & Relation in bounds.relations * @invariant some formulas iff some bounds * @invariant options.solver.incremental() && options.logTranslation = 0 * * @see SymmetryDetector * @see kodkod.engine.fol2sat.Translation.Incremental * @see Translator#translateIncremental(Formula, Bounds, Options) * @see Translator#translateIncremental(Formula, Bounds, kodkod.engine.fol2sat.Translation.Incremental) * * @author Emina Torlak */ public final class IncrementalSolver implements KodkodSolver { private final Options options; private Translation.Incremental translation; private Boolean outcome; /** * Initializes the solver with the given options. * @ensures no this.solution' && no this.formulas' && * no this.bounds'&& this.options' = options */ private IncrementalSolver(Options options) { this.options = options; this.outcome = null; } /** * Returns a new {@link IncrementalSolver} using the given options. * @requires options.solver.incremental() && options.logTranslation = 0 * @return some s: IncrementalSolver | no s.formulas && no s.bounds && s.options = options.clone() * @throws NullPointerException any of the arguments are null * @throws IllegalArgumentException any of the preconditions on options are violated */ public static IncrementalSolver solver(Options options) { Translator.checkIncrementalOptions(options); return new IncrementalSolver(options.clone()); } /** * Adds the specified formula and bounds to the solver's state, modifies * the current solution to reflect the updated state (if needed), * and returns this solver. This solver should not be used again if a * call to this method results in an exception. * @requires this.{@link #usable() usable}() * @requires f.*components & Relation in (this.bounds + b).relations * @requires some this.bounds => this.bounds.universe = b.universe && no b.intBound && no (this.bounds.relations & b.relations) * @requires some this.bounds => * all s: {@link SymmetryDetector#partition(Bounds) partition}(this.bounds) | * some p: {@link SymmetryDetector#partition(Bounds) partition}(b) | * s.elements in p.elements * @ensures this.formulas' = this.formulas + f * @ensures some this.bounds => * (this.bounds.relations' = this.bounds.relations + b.relations && * this.bounds.upperBound' = this.bounds.upperBound + b.upperBound && * this.bounds.lowerBound' = this.bounds.lowerBound + b.lowerBound) else * (this.bounds' = bounds) * @return some sol: Solution | sol.instance() = null => * UNSAT(this.formulas', this.bounds', this.options) else * sol.instance() in MODELS(Formula.and(this.formulas'), this.bounds', this.options) * @throws IllegalStateException a prior call returned an UNSAT solution or resulted in an exception * @throws NullPointerException any of the arguments are null * @throws UnboundLeafException the formula refers to an undeclared variable or a relation not mapped by this.bounds + b * @throws HigherOrderDeclException the formula contains a higher order declaration * @throws IllegalArgumentException any of the remaining preconditions on {@code f} and {@code b} are violated * @throws AbortedException this solving task has been aborted */ public Solution solve(Formula f, Bounds b) throws HigherOrderDeclException, UnboundLeafException, AbortedException { if (outcome==Boolean.FALSE) throw new IllegalStateException("Cannot use this solver since a prior call to solve(...) produced an UNSAT solution."); if (outcome != null && translation==null) throw new IllegalStateException("Cannot use this solver since a prior call to solve(...) resulted in an exception."); final Solution solution; try { final long startTransl = System.currentTimeMillis(); translation = translation==null ? Translator.translateIncremental(f, b, options) : Translator.translateIncremental(f, b, translation); final long endTransl = System.currentTimeMillis(); if (translation.trivial()) { final Statistics stats = new Statistics(translation, endTransl - startTransl, 0); if (translation.cnf().solve()) { solution = Solution.triviallySatisfiable(stats, translation.interpret()); } else { solution = Solution.triviallyUnsatisfiable(stats, null); } } else { final SATSolver cnf = translation.cnf(); translation.options().reporter().solvingCNF(translation.numPrimaryVariables(), cnf.numberOfVariables(), cnf.numberOfClauses()); final long startSolve = System.currentTimeMillis(); final boolean sat = cnf.solve(); final long endSolve = System.currentTimeMillis(); final Statistics stats = new Statistics(translation, endTransl - startTransl, endSolve - startSolve); if (sat) { solution = Solution.satisfiable(stats, translation.interpret()); } else { solution = Solution.unsatisfiable(stats, null); } } } catch (SATAbortedException sae) { free(); throw new AbortedException(sae); } catch (RuntimeException e) { free(); throw e; } if (solution.sat()) { outcome = Boolean.TRUE; } else { outcome = Boolean.FALSE; free(); } return solution; } /** * Returns true iff this solver has neither returned an UNSAT solution so far * nor thrown an exception during solving. * @return true iff this solver has neither returned an UNSAT solution so far * nor thrown an exception during solving */ public boolean usable() { return (outcome == Boolean.TRUE && translation != null) || (outcome == null); } /** * Returns a copy of {@code this.options}. * @return this.options.clone() */ public Options options() { return options.clone(); } /** * Releases the resources, if any, associated with this solver. */ public void free() { if (translation != null) { translation.cnf().free(); translation = null; } } }