/*
* 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.fol2sat.FormulaFlattener.flatten;
import static kodkod.engine.fol2sat.Skolemizer.skolemize;
import static kodkod.util.nodes.AnnotatedNode.annotate;
import static kodkod.util.nodes.AnnotatedNode.annotateRoots;
import static kodkod.util.collections.Containers.setDifference;
import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import kodkod.ast.Expression;
import kodkod.ast.Formula;
import kodkod.ast.IntExpression;
import kodkod.ast.Node;
import kodkod.ast.Relation;
import kodkod.ast.RelationPredicate;
import kodkod.ast.visitor.AbstractReplacer;
import kodkod.engine.bool.BooleanAccumulator;
import kodkod.engine.bool.BooleanConstant;
import kodkod.engine.bool.BooleanFactory;
import kodkod.engine.bool.BooleanFormula;
import kodkod.engine.bool.BooleanMatrix;
import kodkod.engine.bool.BooleanValue;
import kodkod.engine.bool.Int;
import kodkod.engine.bool.Operator;
import kodkod.engine.config.Options;
import kodkod.engine.satlab.SATSolver;
import kodkod.instance.Bounds;
import kodkod.instance.Instance;
import kodkod.instance.TupleSet;
import kodkod.util.ints.IndexedEntry;
import kodkod.util.ints.IntSet;
import kodkod.util.nodes.AnnotatedNode;
/**
* Translates, evaluates, and approximates {@link Node nodes} with
* respect to given {@link Bounds bounds} (or {@link Instance instances}) and {@link Options}.
*
* @author Emina Torlak
*/
public final class Translator {
/*---------------------- public methods ----------------------*/
/**
* Overapproximates the value of the given expression using the provided bounds and options.
* @return a BooleanMatrix whose TRUE entries represent the tuples contained in a sound overapproximation
* of the expression.
* @throws expression = null || instance = null || options = null
* @throws UnboundLeafException the expression refers to an undeclared variable or a relation not mapped by the instance
* @throws HigherOrderDeclException the expression contains a higher order declaration
*/
@SuppressWarnings("unchecked")
public static BooleanMatrix approximate(Expression expression, Bounds bounds, Options options) {
return FOL2BoolTranslator.approximate(annotate(expression), LeafInterpreter.overapproximating(bounds, options), Environment.EMPTY);
}
/**
* Evaluates the given formula to a BooleanConstant using the provided instance and options.
*
* @return a BooleanConstant that represents the value of the formula.
* @throws NullPointerException formula = null || instance = null || options = null
* @throws UnboundLeafException the formula refers to an undeclared variable or a relation not mapped by the instance
* @throws HigherOrderDeclException the formula contains a higher order declaration
*/
public static BooleanConstant evaluate(Formula formula, Instance instance, Options options) {
return (BooleanConstant) FOL2BoolTranslator.translate(annotate(formula), LeafInterpreter.exact(instance, options));
}
/**
* Evaluates the given expression to a BooleanMatrix using the provided instance and options.
*
* @return a BooleanMatrix whose TRUE entries represent the tuples contained by the expression.
* @throws NullPointerException expression = null || instance = null || options = null
* @throws UnboundLeafException the expression refers to an undeclared variable or a relation not mapped by the instance
* @throws HigherOrderDeclException the expression contains a higher order declaration
*/
public static BooleanMatrix evaluate(Expression expression,Instance instance, Options options) {
return (BooleanMatrix) FOL2BoolTranslator.translate(annotate(expression), LeafInterpreter.exact(instance, options));
}
/**
* Evalutes the given intexpression to an {@link kodkod.engine.bool.Int} using the provided instance and options.
* @return an {@link kodkod.engine.bool.Int} representing the value of the intExpr with respect
* to the specified instance and options.
* @throws NullPointerException formula = null || instance = null || options = null
* @throws UnboundLeafException the expression refers to an undeclared variable or a relation not mapped by the instance
* @throws HigherOrderDeclException the expression contains a higher order declaration
*/
public static Int evaluate(IntExpression intExpr, Instance instance, Options options) {
return (Int) FOL2BoolTranslator.translate(annotate(intExpr), LeafInterpreter.exact(instance,options));
}
/**
* Translates the given formula using the specified bounds and options.
* The CNF representation of the given formula and bounds is generated so that the magnitude
* of the literal representing the truth value of a given circuit is strictly larger than the magnitudes of
* the literals representing the truth values of the circuit's descendants.
* @return some t: Translation.Whole | t.originalFormula = formula && t.originalBounds = bounds && t.options = options
* @throws NullPointerException any of the arguments are null
* @throws UnboundLeafException the formula refers to 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 options.skolemize is false.
*/
public static Translation.Whole translate(Formula formula, Bounds bounds, Options options) {
return (Translation.Whole) (new Translator(formula,bounds,options)).translate();
}
/**
* Translates the given formula using the specified bounds and options in such a way
* that the resulting translation can be extended with additional formulas and bounds, subject to
* the same options. We require that the options specify an incremental SAT solver, and no translation logging.
* The CNF representation of the given formula and bounds is generated so that the magnitude
* of the literal representing the truth value of a given circuit is strictly larger than the magnitudes of
* the literals representing the truth values of the circuit's descendants.
* @requires options.solver.incremental() && options.logTranslation = 0
* @return some t: Translation.Incremental | t.originalFormula = formula && t.originalBounds = bounds && t.options = options
* @throws NullPointerException any of the arguments are null
* @throws UnboundLeafException the formula refers to an undeclared variable or a relation not mapped by the given bounds
* @throws HigherOrderDeclException the formula contains a higher order declaration
* @throws IllegalArgumentException any of the preconditions on options are violated
*/
public static Translation.Incremental translateIncremental(Formula formula, Bounds bounds, Options options) {
checkIncrementalOptions(options);
return (Translation.Incremental) (new Translator(formula, bounds, options, true)).translate();
}
/**
* Updates the given translation with {@code CNF(formula, translation.originalBounds + bounds, translation.options)}. The
* result of the update is either a new translation instance or the given {@code translation}, modified in place. We assume
* that client did not modify any translation state between invocations to {@code translateIncremental(...)}.
*
* <p>
* We require {@code bounds} and {@code translation} to be consistent in the following sense:
* <ol>
* <li>{@code bounds} and {@code translation.bounds} share the same universe;</li>
* <li>{@code bounds} must not specify any integer bounds;</li>
* <li>{@code bounds.relations} must not contain any members of {@code translation.bounds.relations}
* (which may be a superset of {@code translation.originalBounds.relations} that also includes Skolem constants); and,</li>
* <li>{@code bounds} must induce a coarser set of equivalence classes on the shared universe than {@code translation.originalBounds}.</li>
* </ol>
* </p>
*
* <p>
* The behavior of this method is unspecified if a prior call to {@code translation.cnf.solve()} returned false, or
* if a prior call to this method resulted in an exception.
* </p>
*
* @requires translation.cnf.solve()
* @requires formula.*components & Relation in (translation.bounds + bounds).relations
* @requires translation.bounds.universe = bounds.universe && no bounds.intBound && no (translation.bounds.relations & bounds.relations)
* @requires all s: translation.symmetries |
* some p: {@link SymmetryDetector#partition(Bounds) partition}(bounds) |
* s.ints in p.ints
* @return some t: Translation |
* t.originalFormula = translation.originalFormula.and(formula) &&
* t.originalBounds.relations = translation.originalBounds.relations + bounds.relations &&
* t.originalBounds.upperBound = translation.originalBounds.upperBound + bounds.upperBound &&
* t.originalBounds.lowerBound = translation.originalBounds.lowerBound + bounds.lowerBound &&
* t.originalBounds.intBound = translation.originalBounds.intBound
* @throws NullPointerException any of the arguments are null
* @throws UnboundLeafException the formula refers to an undeclared variable or a relation not mapped by translation.bounds + bounds
* @throws HigherOrderDeclException the formula contains a higher order declaration
* @throws IllegalArgumentException any of the other preconditions on the arguments are violated
*/
public static Translation.Incremental translateIncremental(Formula formula, Bounds bounds, Translation.Incremental translation) {
checkIncrementalOptions(translation.options());
checkIncrementalBounds(bounds, translation);
if (translation.trivial()) {
return translateIncrementalTrivial(formula, bounds, translation);
} else {
return translateIncrementalNonTrivial(formula, bounds, translation);
}
}
/**
* @requires checkIncrementalBounds(bounds, transl)
* @requires checkIncrementalOptions(transl.options)
* @requires transl.trivial()
* @requires transl.cnf.solve()
* @return see {@link #translateIncremental(Formula, Bounds, Options)}
**/
private static Translation.Incremental translateIncrementalTrivial(Formula formula, Bounds bounds, Translation.Incremental transl) {
if (!transl.cnf().solve())
throw new IllegalArgumentException("Expected a satisfiable translation, given " + transl);
transl.cnf().free(); // release the old empty solver since we are going to re-translate
final Options tOptions = transl.options();
final Bounds tBounds = transl.bounds();
// add new relation bindings to the translation bounds. since the given bounds induce
// a coarser set of symmetries on the universe than transl.symmetries, adding their (disjoint) bindings to tBounds
// will not change the symmetries of tBounds. note that the ymmetries of tBounds refine transl.symmetries, and they
// may be strictly finer if some of the symmetries in transl.symmetries were broken via SymmetryBreaker.breakMatrixSymmetries(...)
// during the generation of transl. in particular, any symmetries absent from tBounds are precisely those that were broken based
// on the total ordering and acyclic predicates in transl.originalFormula.
for(Relation r : bounds.relations()) {
tBounds.bound(r, bounds.lowerBound(r), bounds.upperBound(r));
}
// re-translate the given formula with respect to tBounds. note that we don't have to re-translate
// the conjunction of transl.formula and formula since transl.formula is guaranteed to evaluate to
// TRUE with respect to tBounds (since no bindings that were originally in tBounds were changed by the above loop).
final Translation.Incremental updated = translateIncremental(formula, tBounds, tOptions);
// we can't return the updated translation as is, since we have to make sure that updated.symmetries is set to
// transl.symmetries rather than the potentially finer set of symmetries induced by tBounds. note that
// the updated translation currently has updated.originalBounds = tBounds, while updated.bounds is a copy of
// tBounds with possibly additional skolem relations, as well as new bounds for some relations in formula.*components
// due to symmetry breaking.
return new Translation.Incremental(updated.bounds(), tOptions, transl.symmetries(), updated.interpreter(), updated.incrementer());
}
/**
* @requires checkIncrementalBounds(bounds, transl)
* @requires checkIncrementalOptions(transl.options)
* @requires !transl.trivial()
* @return see {@link #translateIncremental(Formula, Bounds, Options)}
**/
private static Translation.Incremental translateIncrementalNonTrivial(Formula formula, Bounds bounds, Translation.Incremental transl) {
final Options tOptions = transl.options();
final Bounds tBounds = transl.bounds();
// save the set of relations bound in the pre-state
final Set<Relation> oldRelations = new LinkedHashSet<Relation>(tBounds.relations());
// add new relation bindings to the translation bounds. note that skolemization (below) may also cause extra relations to be added.
for(Relation r : bounds.relations()) {
tBounds.bound(r, bounds.lowerBound(r), bounds.upperBound(r));
}
final AnnotatedNode<Formula> annotated =
(transl.options().skolemDepth() < 0) ? annotate(formula) : skolemize(annotate(formula), tBounds, tOptions);
// extend the interpreter with variable allocations for new relations, either from given bounds
// or those introduced by skolemization
final LeafInterpreter interpreter = transl.interpreter();
interpreter.extend(setDifference(tBounds.relations(), oldRelations), tBounds.lowerBounds(), tBounds.upperBounds());
final BooleanValue circuit = FOL2BoolTranslator.translate(annotated, interpreter);
if (circuit==BooleanConstant.FALSE) {
// release the old solver and state, and return a fresh trivially false incremental translation.
transl.incrementer().solver().free();
return new Translation.Incremental(tBounds, tOptions, transl.symmetries(),
LeafInterpreter.empty(tBounds.universe(), tOptions),
Bool2CNFTranslator.translateIncremental(BooleanConstant.FALSE, tOptions.solver()));
} else if (circuit==BooleanConstant.TRUE) {
// must add any newly allocated primary variables to the solver for interpretation to work correctly
final int maxVar = interpreter.factory().maxVariable();
final int cnfVar = transl.cnf().numberOfVariables();
if (maxVar > cnfVar) {
transl.cnf().addVariables(maxVar-cnfVar);
}
} else {
// circuit is a formula; add its CNF representation to transl.incrementer.solver()
Bool2CNFTranslator.translateIncremental((BooleanFormula) circuit, interpreter.factory().maxVariable(), transl.incrementer());
}
return transl;
}
/**
* Checks that the given options are suitable for incremental translation.
* @requires options.solver.incremental() && options.logTranslation = 0
* @throws IllegalArgumentException any of the preconditions are violated
*/
public static void checkIncrementalOptions(Options options) {
if (!options.solver().incremental())
throw new IllegalArgumentException("An incremental solver is required for incremental translation: " + options);
if (options.logTranslation() != 0)
throw new IllegalArgumentException("Translation logging must be disabled for incremental translation: " + options);
}
/**
* Checks that the given {@code inc} bounds are incremental with respect to the given {@code translation}.
* @requires translation.bounds.universe = inc.universe && no inc.intBound && no (translation.bounds.relations & inc.relations)
* @requires all s: translation.symmetries |
* some p: {@link SymmetryDetector#partition(Bounds) partition}(inc) |
* s.elements in p.elements
* @throws IllegalArgumentException any of the preconditions are violated
*/
public static void checkIncrementalBounds(Bounds inc, Translation.Incremental translation) {
final Bounds base = translation.bounds();
if (!base.universe().equals(inc.universe()))
incBoundErr(inc.universe(), "universe", "equal to", base.universe());
if (!inc.intBounds().isEmpty())
incBoundErr(inc.intBounds(), "intBound", "empty, with integer bounds fully specified by", base.intBounds());
if (inc.relations().isEmpty()) return;
final Set<Relation> baseRels = base.relations();
for(Relation r : inc.relations()) {
if (baseRels.contains(r)) {
incBoundErr(inc.relations(), "relations", "disjoint from", baseRels);
}
}
final Set<IntSet> symmetries = translation.symmetries();
final Set<IntSet> incSymmetries = SymmetryDetector.partition(inc);
EQUIV_CHECK : for(IntSet part : symmetries) {
for(IntSet incPart : incSymmetries) {
if (incPart.containsAll(part))
continue EQUIV_CHECK;
}
incBoundErr(incSymmetries, "partition", "coarser than", symmetries);
}
}
/**
* Throws an {@link IllegalArgumentException} with an error message that describes why given bounds
* cannot be used for incremental translation.
*/
private static void incBoundErr(Object newObj, String desc, String relatedTo, Object translObj) {
final String newDesc = "bounds." + desc, oldDesc = "translation.originalBounds." + desc;
throw new IllegalArgumentException("Expected " + newDesc + " to be " + relatedTo + " " + oldDesc +
" for incremental translation; given "+ newDesc + " = " + newObj + ", " + oldDesc + " = " + translObj);
}
/*---------------------- private translation state and methods ----------------------*/
/**
* @specfield originalFormula: Formula
* @specfield originalBounds: Bounds
* @specfield bounds: Bounds
* @specfield options: Options
* @specfield incremental: boolean
*/
private final Formula originalFormula;
private final Bounds originalBounds;
private final Bounds bounds;
private final Options options;
private final boolean logging;
private final boolean incremental;
/**
* Constructs a Translator for the given formula, bounds, options and incremental flag.
* If the flag is true, then the translator produces an initial {@linkplain Translation.Incremental incremental translation}.
* Otherwise, the translator produces a {@linkplain Translation.Whole basic translation}.
* @ensures this.originalFormula' = formula and
* this.options' = options and
* this.originalBounds' = bounds and
* this.bounds' = bounds.clone() and
* this.incremental' = incremental
*/
private Translator(Formula formula, Bounds bounds, Options options, boolean incremental) {
this.originalFormula = formula;
this.originalBounds = bounds;
this.bounds = bounds.clone();
this.options = options;
this.logging = options.logTranslation()>0;
this.incremental = incremental;
}
/**
* Constructs a non-incremental Translator for the given formula, bounds and options.
* @ensures this(formula, bounds, options, false)
*/
private Translator(Formula formula, Bounds bounds, Options options) {
this(formula, bounds, options, false);
}
/**
* Translates this.originalFormula with respect to this.bounds and this.options. If this.incremental is true,
* then the returned translation is {@linkplain Translation.Incremental incremental}; otherwise the output is
* a {@linkplain Translation.Whole basic} translation.
* @return a {@linkplain Translation} whose solver is a SATSolver instance initialized with the
* CNF representation of the given formula, with respect to the given bounds. The CNF
* is generated in such a way that the magnitude of a literal representing the truth
* value of a given formula is strictly larger than the magnitudes of the literals representing
* the truth values of the formula's descendants.
* @throws UnboundLeafException this.originalFormula refers to an undeclared variable or a relation not mapped by this.bounds.
* @throws HigherOrderDeclException this.originalFormula contains a higher order declaration that cannot
* be skolemized, or it can be skolemized but this.options.skolemDepth < 0
*/
private Translation translate() {
final AnnotatedNode<Formula> annotated = logging ? annotateRoots(originalFormula) : annotate(originalFormula);
// Remove bindings for unused relations/ints if this is not an incremental translation. If it is
// an incremental translation, we have to keep all bindings since they may be used later on.
if (!incremental) {
bounds.relations().retainAll(annotated.relations());
if (!annotated.usesInts()) bounds.ints().clear();
}
// Detect symmetries.
final SymmetryBreaker breaker = new SymmetryBreaker(bounds, options.reporter());
// Optimize formula and bounds by using symmetry information to tighten bounds and
// eliminate top-level predicates, and also by skolemizing. Then translate the optimize
// formula and bounds to a circuit, augment the circuit with a symmetry breaking predicate
// that eliminates any remaining symmetries, and translate everything to CNF.
return toBoolean(optimizeFormulaAndBounds(annotated, breaker), breaker);
}
/**
* <p>When logging is disabled, optimizes annotated.node by first breaking matrix symmetries on its top-level predicates,
* replacing them with the simpler formulas generated by
* {@linkplain SymmetryBreaker#breakMatrixSymmetries(Map, boolean) breaker.breakMatrixSymmetries(...)},
* and skolemizing the result, if applicable.</p>
*
* <p> When logging is enabled, optimizes annotated.node by first flattening it into a set of conjuncts,
* assuming that core granularity is 1. This involves pushing negations through quantifier-free formulas.
* We then skolemize, followed by an additional layer of flattening (if this.options.coreGranularity > 1),
* possibly through quantifiers (if this.options.coreGranuarity is 3). Predicate inlining and breaking of
* matrix symmetries is performed last to prevent any quantified formulas generated by predicate inlining
* from also being flattened (as this wouldn't be meaningful at the level of the original formula).</p>
*
* @requires SAT(annotated.node, this.bounds, this.options) iff SAT(this.originalFormula, this.originalBounds, this.options)
* @requires annotated.node.*components & Relation = this.originalFormula.*components & Relation
* @requires breaker.bounds = this.bounds
* @ensures this.bounds.relations in this.bounds.relations'
* @ensures this.options.reporter().optimizingBoundsAndFormula()
* @return some f: AnnotatedNode<Formula> | meaning(f.node, this.bounds, this.options) = meaning(this.originalFormula, this.originalBounds, this.options)
*/
private AnnotatedNode<Formula> optimizeFormulaAndBounds(AnnotatedNode<Formula> annotated, SymmetryBreaker breaker) {
options.reporter().optimizingBoundsAndFormula();
if (logging) {
final int coreGranularity = options.coreGranularity();
if (coreGranularity==1) {
annotated = flatten(annotated, false);
}
if (options.skolemDepth()>=0) {
annotated = skolemize(annotated, bounds, options);
}
if (coreGranularity>1) {
annotated = flatten(annotated, options.coreGranularity()==3);
}
return inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), false));
} else {
annotated = inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), true).keySet());
return options.skolemDepth()>=0 ? Skolemizer.skolemize(annotated, bounds, options) : annotated;
}
}
/**
* Returns an annotated formula f such that f.node is equivalent to annotated.node
* with its <tt>truePreds</tt> replaced with the constant formula TRUE and the remaining
* predicates replaced with equivalent constraints.
* @requires truePreds in annotated.predicates()[RelationnPredicate.NAME]
* @requires truePreds are trivially true with respect to this.bounds
* @return an annotated formula f such that f.node is equivalent to annotated.node
* with its <tt>truePreds</tt> replaced with the constant formula TRUE and the remaining
* predicates replaced with equivalent constraints.
*/
private AnnotatedNode<Formula> inlinePredicates(final AnnotatedNode<Formula> annotated, final Set<RelationPredicate> truePreds) {
final AbstractReplacer inliner = new AbstractReplacer(annotated.sharedNodes()) {
public Formula visit(RelationPredicate pred) {
Formula ret = lookup(pred);
if (ret!=null) return ret;
return truePreds.contains(pred) ? cache(pred, Formula.TRUE) : cache(pred, pred.toConstraints());
}
};
return annotate(annotated.node().accept(inliner));
}
/**
* Returns an annotated formula f such that f.node is equivalent to annotated.node
* with its <tt>simplified</tt> predicates replaced with their corresponding Formulas and the remaining
* predicates replaced with equivalent constraints. The annotated formula f will contain transitive source
* information for each of the subformulas of f.node. Specifically, let t be a subformula of f.node, and
* s be a descdendent of annotated.node from which t was derived. Then, f.source[t] = annotated.source[s]. </p>
* @requires simplified.keySet() in annotated.predicates()[RelationPredicate.NAME]
* @requires no disj p, p': simplified.keySet() | simplified.get(p) = simplifed.get(p') // this must hold in order
* to maintain the invariant that each subformula of the returned formula has exactly one source
* @requires for each p in simplified.keySet(), the formulas "p and [[this.bounds]]" and
* "simplified.get(p) and [[this.bounds]]" are equisatisfiable
* @return an annotated formula f such that f.node is equivalent to annotated.node
* with its <tt>simplified</tt> predicates replaced with their corresponding Formulas and the remaining
* predicates replaced with equivalent constraints.
*/
private AnnotatedNode<Formula> inlinePredicates(final AnnotatedNode<Formula> annotated, final Map<RelationPredicate,Formula> simplified) {
final Map<Node,Node> sources = new IdentityHashMap<Node,Node>();
final AbstractReplacer inliner = new AbstractReplacer(annotated.sharedNodes()) {
private RelationPredicate source = null;
protected <N extends Node> N cache(N node, N replacement) {
if (replacement instanceof Formula) {
if (source==null) {
final Node nsource = annotated.sourceOf(node);
if (replacement!=nsource)
sources.put(replacement, nsource);
} else {
sources.put(replacement, source);
}
}
return super.cache(node, replacement);
}
public Formula visit(RelationPredicate pred) {
Formula ret = lookup(pred);
if (ret!=null) return ret;
source = pred;
if (simplified.containsKey(pred)) {
ret = simplified.get(pred).accept(this);
} else {
ret = pred.toConstraints().accept(this);
}
source = null;
return cache(pred, ret);
}
};
return annotate(annotated.node().accept(inliner), sources);
}
/**
* Translates the given annotated formula to a circuit, conjoins the circuit with an
* SBP generated by the given symmetry breaker, and returns its {@linkplain Translation} to CNF.
* The SBP breaks any symmetries that could not be broken during the
* {@linkplain #optimizeFormulaAndBounds(AnnotatedNode, SymmetryBreaker) formula and bounds optimization} step.
* @requires SAT(annotated.node, this.bounds, this.options) iff SAT(this.originalFormula, this.originalBounds, this.options)
* @requires breaker.bounds = this.bounds
* @ensures this.options.logTranslation => some this.log'
* @ensures this.options.reporter().translatingToBoolean(annotated.node(), this.bounds)
* @ensures this.options.reporter().generatingSBP()
* @return the translation of annotated.node with respect to this.bounds
*/
private Translation toBoolean(AnnotatedNode<Formula> annotated, SymmetryBreaker breaker) {
options.reporter().translatingToBoolean(annotated.node(), bounds);
final LeafInterpreter interpreter = LeafInterpreter.exact(bounds, options, incremental);
final BooleanFactory factory = interpreter.factory();
if (logging) {
assert !incremental;
final TranslationLogger logger = options.logTranslation()==1 ? new MemoryLogger(annotated, bounds) : new FileLogger(annotated, bounds);
final BooleanAccumulator circuit = FOL2BoolTranslator.translate(annotated, interpreter, logger);
final TranslationLog log = logger.log();
if (circuit.isShortCircuited()) {
return trivial(circuit.op().shortCircuit(), log);
} else if (circuit.size()==0) {
return trivial(circuit.op().identity(), log);
}
circuit.add(breaker.generateSBP(interpreter, options));
return toCNF((BooleanFormula)factory.accumulate(circuit), interpreter, log);
} else {
final BooleanValue circuit = (BooleanValue)FOL2BoolTranslator.translate(annotated, interpreter);
if (circuit.op()==Operator.CONST) {
return trivial((BooleanConstant)circuit, null);
}
return toCNF((BooleanFormula)factory.and(circuit, breaker.generateSBP(interpreter, options)), interpreter, null);
}
}
/**
* Translates the given circuit to CNF, adds the clauses to a SATSolver returned
* by options.solver(), and returns a Translation object constructed from the solver
* and the provided arguments.
* @requires SAT(circuit) iff SAT(this.originalFormula, this.originalBounds, this.options)
* @requires circuit.factory = interpreter.factory
* @requires interpreter.universe = this.bounds.universe && interpreter.relations = this.bounds.relations() &&
* interpreter.ints = this.bounds.ints() && interpreter.lbounds = this.bounds.lowerBound &&
* this.interpreter.ubounds = bounds.upperBound && interpreter.ibounds = bounds.intBound
* @requires log.originalFormula = this.originalFormula && log.bounds = this.bounds
* @ensures {@link #completeBounds()}
* @ensures this.options.reporter.translatingToCNF(circuit)
* @return some t: Translation |
* t.bounds = completeBounds() && t.originalBounds = this.originalBounds &&
* t.vars = interpreter.vars &&
* t.vars[Relation].int in t.solver.variables &&
* t.solver.solve() iff SAT(this.formula, this.bounds, this.options)
*/
private Translation toCNF(BooleanFormula circuit, LeafInterpreter interpreter, TranslationLog log) {
options.reporter().translatingToCNF(circuit);
final int maxPrimaryVar = interpreter.factory().maxVariable();
if (incremental) {
final Bool2CNFTranslator incrementer = Bool2CNFTranslator.translateIncremental(circuit, maxPrimaryVar, options.solver());
return new Translation.Incremental(completeBounds(), options, SymmetryDetector.partition(originalBounds), interpreter, incrementer);
} else {
final Map<Relation, IntSet> varUsage = interpreter.vars();
interpreter = null; // enable gc
final SATSolver cnf = Bool2CNFTranslator.translate((BooleanFormula)circuit, maxPrimaryVar, options.solver());
return new Translation.Whole(completeBounds(), options, cnf, varUsage, maxPrimaryVar, log);
}
}
/**
* Returns a whole or incremental translation, depending on the value of {@code this.incremental},
* using the given trivial outcome, {@linkplain #completeBounds() completeBounds()}, {@code this.options},
* and the given log.
* @ensures {@link #completeBounds()}
* @return some t: Translation |
* t.bounds = completeBounds() && t.originalBounds = this.originalBounds &&
* no t.solver.variables &&
* no t.vars &&
* (outcome.booleanValue() => no t.solver.clauses else (one t.solver.clauses && no t.solver.clauses.literals))
**/
@SuppressWarnings("unchecked")
private Translation trivial(BooleanConstant outcome, TranslationLog log) {
if (incremental) {
return new Translation.Incremental(completeBounds(), options,
SymmetryDetector.partition(originalBounds),
LeafInterpreter.empty(bounds.universe(), options), // empty interpreter
Bool2CNFTranslator.translateIncremental(outcome, options.solver()));
} else {
return new Translation.Whole(completeBounds(), options,
Bool2CNFTranslator.translate(outcome, options.solver()),
(Map<Relation,IntSet>)Collections.EMPTY_MAP, 0, log);
}
}
/**
* Completes {@code this.bounds} using the bindings from {@code this.originalBounds} so that
* the result satisfies the {@linkplain Translation} invariants. This involves updating
* {@code this.bounds} with bindings from {@code this.originalBounds}, if any, that had been discarded
* in the {@link #translate() first step} of the translation. The first step of a non-incremental
* translation is to discard bounds for relations that are not constrained by {@code this.originalFormula},
* and to discard all integer bounds if {@code this.originalFormula} contains no integer expressions.
* This is sound since any instance of {@code this.originalFormula} with respect to {@code this.originalBounds} only needs to
* satisfy the lower bound constraint on each discarded relation/integer. By updating {@code this.bounds} with
* the bindings for discarded relations/integers for which no variables were allocated, we ensure that any instance returned by
* {@linkplain Translation#interpret()} will bind those relations/integers to their lower bound, therefore satisfying
* the original problem {@code (this.originalFormula, this.originalBounds, this.options)}.
*
* @requires no this.bounds.intBound or this.bounds.intBound = this.originalBounds.intBound
* @ensures this.bounds.relations' = this.bounds.relations + this.originalBounds.relations &&
* this.bounds.intBound' = this.originalBounds.intBound &&
* this.bounds.lowerBound' = this.bounds.lowerBound + (this.originalBounds.relations - this.bounds.relations)<:(this.originalBounds.lowerBound) &&
* this.bounds.upperBound' = bounds.upperBound + (this.originalBounds.relations - this.bounds.relations)<:(this.originalBounds.upperBound)
* @return this.bounds
*/
private Bounds completeBounds() {
final Bounds optimized = this.bounds;
final Bounds original = this.originalBounds;
if (optimized.ints().isEmpty()) {
for(IndexedEntry<TupleSet> entry : original.intBounds()) {
optimized.boundExactly(entry.index(), entry.value());
}
} else {
assert optimized.intBounds().equals(original.intBounds());
}
final Set<Relation> rels = optimized.relations();
for(Relation r : original.relations()) {
if (!rels.contains(r)) {
optimized.bound(r, original.lowerBound(r), original.upperBound(r));
}
}
return optimized;
}
}