Package kodkod.engine.fol2sat

Source Code of kodkod.engine.fol2sat.Translator

/*
* Kodkod -- Copyright (c) 2005-2007, 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 java.util.IdentityHashMap;
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.util.ints.IntSet;
import kodkod.util.nodes.AnnotatedNode;

import static 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.
   * @return a 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 the 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 TrivialFormulaException - the given formula is reduced to a constant during translation
   * (i.e. the formula is trivially (un)satisfiable).
   * @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 translate(Formula formula, Bounds bounds, Options options) throws TrivialFormulaException {
    return (new Translator(formula,bounds,options)).translate();
  }
 
  /*---------------------- private translation state and methods ----------------------*/
  /**
   * @specfield formula: Formula
   * @specfield bounds: Bounds
   * @specfield options: Options
   * @specfield log: TranslationLog
   */
  private final Formula formula;
  private final Bounds bounds;
  private final Options options;
 
  private TranslationLog log;
 
  /**
   * Constructs a Translator for the given formula, bounds and options.
   * @effects this.formula' = formula and
   *   this.options' = options and
   *   this.bounds' = bounds.clone() and
   *  no this.log'
   */
  private Translator(Formula formula, Bounds bounds, Options options) {
    this.formula = formula;
    this.bounds = bounds.clone();
    this.options = options;
    this.log = null;
  }
 
  /**
   * Translates this.formula with respect to this.bounds and this.options.
   * @return a 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 TrivialFormulaException - this.formula is reduced to a constant during translation
   * (i.e. the formula is trivially (un)satisfiable).
   * @throws UnboundLeafException - this.formula refers to an undeclared variable or a relation not mapped by this.bounds.
   * @throws HigherOrderDeclException - this.formula contains a higher order declaration that cannot
   * be skolemized, or it can be skolemized but this.options.skolemDepth < 0
   */
  private Translation translate() throws TrivialFormulaException  {
    final AnnotatedNode<Formula> annotated = options.logTranslation()>0 ? annotateRoots(formula) : annotate(formula);
    final SymmetryBreaker breaker = optimizeBounds(annotated);
    return toBoolean(optimizeFormula(annotated, breaker), breaker);
  }
 
  /**
   * Removes bindings for unused relations/ints from this.bounds and
   * returns a SymmetryBreaker for the reduced bounds.
   * @requires annotated.node = this.formula
   * @effects this.bounds'.relations = this.formula.*children & Relations
   * @effects !annotated.usesInts() => no this.bounds'.int
   * @return { b: SymmetryBreaker | b.bounds = this.bounds' }
   */
  private SymmetryBreaker optimizeBounds(AnnotatedNode<Formula> annotated) { 
    // remove bindings for unused relations/ints
    bounds.relations().retainAll(annotated.relations());
    if (!annotated.usesInts()) bounds.ints().clear();
   
    // detect symmetries
    return new SymmetryBreaker(bounds, options.reporter());
  }
 
  /**
   * Optimizes annotated.node by first breaking 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.
   * @requires annotated.node = this.formula
   * @requires breaker.bounds = this.bounds
   * @return the skolemization, up to depth this.options.skolemDepth, of annotated.node with
   * the broken predicates replaced with simpler constraints and the remaining predicates inlined.
   */
  private AnnotatedNode<Formula> optimizeFormula(AnnotatedNode<Formula> annotated, SymmetryBreaker breaker) { 
    options.reporter().optimizingBoundsAndFormula();

    if (options.logTranslation()==0) { // no logging
      annotated = inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), true).keySet());
      return options.skolemDepth()>=0 ? Skolemizer.skolemize(annotated, bounds, options) : annotated;
    } else { // logging; inlining of predicates *must* happen last when logging is enabled
      if (options.coreGranularity()==1) {
        annotated = FormulaFlattener.flatten(annotated, false);
      }
      if (options.skolemDepth()>=0) {
        annotated = Skolemizer.skolemize(annotated, bounds, options);
      }
      if (options.coreGranularity()>1) {
        annotated = FormulaFlattener.flatten(annotated, options.coreGranularity()==3);
      }
      return inlinePredicates(annotated, breaker.breakMatrixSymmetries(annotated.predicates(), false));
    }
  }
 
  /**
   * 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 this.options.logTranslation = false
   * @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 this.options.logTranslation = true
   * @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, flattens the result if so specified by this.options,
   * and returns its Translation to CNF.
   * @requires [[annotated.node]] <=> ([[this.formula]] and [[breaker.broken]])
   * @effects this.options.logTranslation => some this.log'
   * @return the result of calling  {@link #generateSBP(BooleanFormula, LeafInterpreter, SymmetryBreaker)}
   * on the translation of annotated.node with respect to this.bounds
   * @throws TrivialFormulaException - the translation of annotated is a constant or can be made into
   * a constant by flattening
   */
  private Translation toBoolean(AnnotatedNode<Formula> annotated, SymmetryBreaker breaker) throws TrivialFormulaException {
   
    options.reporter().translatingToBoolean(annotated.node(), bounds);
   
    final LeafInterpreter interpreter = LeafInterpreter.exact(bounds, options);
   
    if (options.logTranslation()>0) {
      final TranslationLogger logger = options.logTranslation()==1 ? new MemoryLogger(annotated, bounds) : new FileLogger(annotated, bounds);
      final BooleanAccumulator circuit = FOL2BoolTranslator.translate(annotated, interpreter, logger);
      log = logger.log();
      if (circuit.isShortCircuited()) {
        throw new TrivialFormulaException(annotated.node(), bounds, circuit.op().shortCircuit(), log);
      } else if (circuit.size()==0) {
        throw new TrivialFormulaException(annotated.node(), bounds, circuit.op().identity(), log);
      }
      return generateSBP(circuit, interpreter, breaker);
    } else {
      final BooleanValue circuit = (BooleanValue)FOL2BoolTranslator.translate(annotated, interpreter);
      if (circuit.op()==Operator.CONST) {
        throw new TrivialFormulaException(annotated.node(), bounds, (BooleanConstant)circuit, null);
      }
      return generateSBP(annotated, (BooleanFormula)circuit, interpreter, breaker);
    }
  }
 
  /**
   * Adds to given accumulator an SBP generated using the given symmetry breaker and interpreter,
   * and returns the resulting circuit's translation to CNF.
   * @requires circuit is a translation of this.formula with respect to this.bounds
   * @requires interpreter is the leaf interpreter used in generating the given circuit
   * @requires breaker.bounds = this.bounds
   * @return toCNF(circuit && breaker.generateSBP(interpreter))
   */
  private Translation generateSBP(BooleanAccumulator circuit, LeafInterpreter interpreter, SymmetryBreaker breaker) {
    options.reporter().generatingSBP();
    final BooleanFactory factory = interpreter.factory();
    circuit.add(breaker.generateSBP(interpreter, options.symmetryBreaking()));
    return toCNF((BooleanFormula)factory.accumulate(circuit), factory.numberOfVariables(), interpreter.vars());
  }
 
  /**
   * Conjoins the given circuit with an SBP generated using the given symmetry breaker and interpreter,
   * and returns the resulting circuit's translation to CNF.
   * @requires [[annotated.node]] <=> ([[this.formula]] and [[breaker.broken]])
   * @requires circuit is a translation of annotated.node with respect to this.bounds
   * @requires interpreter is the leaf interpreter used in generating the given circuit
   * @requires breaker.bounds = this.bounds
   * @return flatten(circuit && breaker.generateSBP(interpreter), interpreter)
   * @throws TrivialFormulaException - flattening the circuit and the predicate yields a constant
   */
  private Translation generateSBP(AnnotatedNode<Formula> annotated, BooleanFormula circuit, LeafInterpreter interpreter, SymmetryBreaker breaker)
  throws TrivialFormulaException {
    options.reporter().generatingSBP();
    final BooleanFactory factory = interpreter.factory();
    final BooleanValue sbp = breaker.generateSBP(interpreter, options.symmetryBreaking());
    return flatten(annotated, (BooleanFormula)factory.and(circuit, sbp), interpreter);
  }

  /**
   * If this.options.flatten is true, flattens the given circuit and returns its translation to CNF.
   * Otherwise, simply returns the given circuit's translation to CNF.
   * @requires [[annotated.node]] <=> ([[this.formula]] and [[breaker.broken]])
   * @requires circuit is a translation of annotated.node with respect to this.bounds
   * @requires interpreter is the leaf interpreter used in generating the given circuit
   * @return if this.options.flatten then
   *   toCNF(flatten(circuit), interpreter.factory().numberOfVariables(), interpreter.vars()) else
   *  toCNF(circuit, interpreter.factory().numberOfVariables(), interpreter.vars())
   * @throws TrivialFormulaException - flattening the circuit yields a constant
   */
  private Translation flatten(AnnotatedNode<Formula> annotated, BooleanFormula circuit, LeafInterpreter interpreter) throws TrivialFormulaException
    final BooleanFactory factory = interpreter.factory();
    if (options.flatten()) {
      options.reporter().flattening(circuit);
      final BooleanValue flatCircuit = BooleanFormulaFlattener.flatten(circuit, factory);
      if (flatCircuit.op()==Operator.CONST) {
        throw new TrivialFormulaException(annotated.node(), bounds, (BooleanConstant)flatCircuit, null);
      } else {
        return toCNF((BooleanFormula)flatCircuit, factory.numberOfVariables(), interpreter.vars());
      }
    } else {
      return toCNF(circuit, factory.numberOfVariables(), interpreter.vars());
    }
  }
 
  /**
   * 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 circuit is a translation of this.formula with respect to this.bounds
   * @requires primaryVars is the number of primary variables generated by translating
   * this.formula and this.bounds into the given circuit
   * @requires varUsage maps each non-constant relation in this.bounds to the labels of
   * the primary variables used to represent that relation in the given circuit
   * @return Translation constructed from a SAT solver initialized with the CNF translation
   * of the given circuit, the provided arguments, this.bounds, and this.log
   */
  private Translation toCNF(BooleanFormula circuit, int primaryVars, Map<Relation,IntSet> varUsage) { 
    options.reporter().translatingToCNF(circuit);
    final SATSolver cnf = Bool2CNFTranslator.translate((BooleanFormula)circuit, options.solver(), primaryVars);
    return new Translation(cnf, bounds, varUsage, primaryVars, log);
  }
 
}
TOP

Related Classes of kodkod.engine.fol2sat.Translator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.