Package kodkod.engine.fol2sat

Source Code of kodkod.engine.fol2sat.SymmetryBreaker$RelationParts

/*
* 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 static kodkod.ast.RelationPredicate.Name.ACYCLIC;
import static kodkod.ast.RelationPredicate.Name.TOTAL_ORDERING;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import kodkod.ast.Formula;
import kodkod.ast.Relation;
import kodkod.ast.RelationPredicate;
import kodkod.ast.RelationPredicate.Name;
import kodkod.engine.bool.BooleanAccumulator;
import kodkod.engine.bool.BooleanConstant;
import kodkod.engine.bool.BooleanFactory;
import kodkod.engine.bool.BooleanMatrix;
import kodkod.engine.bool.BooleanValue;
import kodkod.engine.bool.Operator;
import kodkod.engine.config.Reporter;
import kodkod.instance.Bounds;
import kodkod.instance.TupleFactory;
import kodkod.util.ints.IndexedEntry;
import kodkod.util.ints.IntIterator;
import kodkod.util.ints.IntSet;
import kodkod.util.ints.Ints;

/**
* Breaks symmetries for a given problem.  Symmetries
* are broken for total orders, acyclic relations, and
* via a generic lex-leader predicate.
*
* @specfield bounds: Bounds // problem bounds
* @specfield symmetries: set IntSet
* @specfield broken: set RelationPredicate
* @author Emina Torlak
*/
final class SymmetryBreaker {
  private final Bounds bounds;
  private final Set<IntSet> symmetries;
  private final int usize;
 
  /**
   * Constructs a new symmetry breaker for the given Bounds.
   * <b>Note that the constructor does not make a local copy of the given
   * bounds, so the caller must ensure that all modifications of the
   * given bounds are symmetry preserving.</b> 
   * @effects this.bounds' = bounds && this.symmetries' = SymmetryDetector.partition(bounds) &&
   * no this.broken'
   **/
  SymmetryBreaker(Bounds bounds, Reporter reporter) {
    this.bounds = bounds;
    this.usize = bounds.universe().size();
    reporter.detectingSymmetries(bounds);
    this.symmetries = SymmetryDetector.partition(bounds);
    reporter.detectedSymmetries(symmetries);
//    System.out.println(symmetries);
  }
 
  /**
   * Breaks matrix symmetries on the relations in this.bounds that are constrained by 
   * the total ordering and acyclic predicates, drawn from preds.values(), that make up the
   * keyset of the returned map.  After this method returns, the following constraint holds.
   * Let m be the map returned by the method, and m.keySet() be the subset of preds.values()
   * used for symmetry breaking.  Then, if we let [[b]] denote the set of constraints
   * specified by a Bounds object b, the formulas "p and [[this.bounds]]" and "m.get(p) and [[this.bounds']]"
   * are equisatisfiable for all p in m.keySet().
   *
   * <p>The value of the "aggressive" flag determines how the symmetries are broken.  In particular, if
   * the aggressive flag is true, then the symmetries are broken efficiently, at the cost of
   * losing the information needed to determine whether a predicate in m.keySet() belongs to an unsatisfiable
   * core or not.  If the aggressive flag is false, then a less efficient algorithm is applied, which preserves
   * the information necessary for unsatisfiable core extraction. </p>
   *
   * <p>The aggressive symmetry breaking algorithm works as follows.  Let t1...tn and c1...ck
   * be the total ordering and acyclic predicates in m.keySet().  For each t in {t1...tn}, this.bounds
   * is modified so that the bounds for t.first, t.last, t.ordered and t.relation are the following constants:
   * t.first is the first atom in the upper bound of t.ordered, t.last is the last atom in the upper bound of t.ordered, t.ordered's
   * lower bound is changed to be equal to its upper bound, and t.relation imposes a total ordering on t.ordered
   * that corresponds to the ordering of the atoms in this.bounds.universe. Then, m is updated with a binding
   * from t to the constant formula TRUE.  For each c in {c1...ck}, 
   * this.bounds is modified so that the upper bound for c.relation is a tupleset whose equivalent matrix
   * has FALSE in the entries on or below the main diagonal.  Then, m is updated with a binding from c to the
   * constant formula TRUE.</p>
   *
   * <p>The lossless symmetry breaking algorithm works as follows. Let t1...tn and c1...ck be the total ordering
   * and acyclic predicates in m.keySet(). For each t in {t1...tn}, three fresh relations are added to this.bounds--
   * t_first, t_last, t_ordered, and t_order--and constrained as follows: t_first is the first atom
   * in the upper bound of t.ordered, t_last is the last atom in the upper bound of t.ordered, t_ordered is
   * the upper bound of t.ordered, and t_order imposes a total ordering on t.ordered that corresponds to the
   * ordering of the atoms in this.bounds.universe.  Then, m is updated with a binding from t to the formula
   * "t.first = t_first and t.last = t_last and t.ordered = t_ordered and t.relation = t.order." 
   * For each c in {c1...ck}, a fresh relation c_acyclic is added to this.bounds and constrained to be a constant
   * whose equivalent matrix has no entries on or below the main diagonal. The map m is then
   * updated with a binding from c to the constraint "c in c_acyclic".</p>
   *
   * @effects this.bounds' is modified as described above
   * @effects this.symmetries' is modified to no longer contain the partitions that made up the bounds of
   * the relations on which symmetries have been broken
   * @return a map m such that m.keySet() in preds.values(), and for all predicates p in m.keySet(), the formulas
   * "p and [[this.bounds]]" and "m.get(p) and [[this.bounds']]" are equisatisfiable
   */
  Map<RelationPredicate, Formula> breakMatrixSymmetries(Map<Name, Set<RelationPredicate>> preds, boolean aggressive) {
    final Set<RelationPredicate> totals = preds.get(TOTAL_ORDERING);
    final Set<RelationPredicate> acyclics = preds.get(ACYCLIC);
    final Map<RelationPredicate, Formula> broken = new IdentityHashMap<RelationPredicate, Formula>();
   
    for(RelationPredicate.TotalOrdering pred : sort(totals.toArray(new RelationPredicate.TotalOrdering[totals.size()]))) {
      Formula replacement = breakTotalOrder(pred,aggressive);
      if (replacement!=null)
        broken.put(pred, replacement);
    }
   
    for(RelationPredicate.Acyclic pred : sort(acyclics.toArray(new RelationPredicate.Acyclic[acyclics.size()]))) {
      Formula replacement = breakAcyclic(pred,aggressive);
      if (replacement!=null)
        broken.put(pred, replacement);
    }
   
    return broken;
  }
   
  /**
   * Generates a lex leader symmetry breaking predicate for this.symmetries
   * (if any), using the specified leaf interpreter and the specified predicate length.
   * @requires interpreter.relations in this.bounds.relations
   * @return a symmetry breaking predicate for this.symmetries
   */
  final BooleanValue generateSBP(LeafInterpreter interpreter, int predLength) {
    if (symmetries.isEmpty() || predLength==0) return BooleanConstant.TRUE;
   
    final List<RelationParts> relParts = relParts();
    final BooleanFactory factory = interpreter.factory();
    final BooleanAccumulator sbp = BooleanAccumulator.treeGate(Operator.AND);
    final List<BooleanValue> original = new ArrayList<BooleanValue>(predLength);
    final List<BooleanValue> permuted = new ArrayList<BooleanValue>(predLength);
   
    for(IntSet sym : symmetries) {
   
      IntIterator indeces = sym.iterator();
      for(int prevIndex = indeces.next(); indeces.hasNext(); ) {
        int curIndex = indeces.next();
        for(Iterator<RelationParts> rIter = relParts.iterator(); rIter.hasNext() && original.size() < predLength;) {
         
          RelationParts rparts = rIter.next();
          Relation r = rparts.relation;
         
          if (!rparts.representatives.contains(sym.min())) continue// r does not range over sym
         
          BooleanMatrix m = interpreter.interpret(r);
          for(IndexedEntry<BooleanValue> entry : m) {
            int permIndex = permutation(r.arity(), entry.index(), prevIndex, curIndex);
            BooleanValue permValue = m.get(permIndex);
            if (permIndex==entry.index() || atSameIndex(original, permValue, permuted, entry.value()))
              continue;
           
            original.add(entry.value());
            permuted.add(permValue);     
          }
        }
               
        sbp.add(leq(factory, original, permuted));
        original.clear();
        permuted.clear();
        prevIndex = curIndex;
      }
    }
   
    return factory.accumulate(sbp);
  }
 
  /**
   * Returns a list of RelationParts that map each non-constant r in this.bounds.relations to
   * the representatives of the sets from this.symmetries contained in the upper bound of r. 
   * The entries are sorted by relations' arities and names.
   * @return a list of RelationParts that contains an entry for each non-constant r in this.bounds.relations and
   * the representatives of sets from this.symmetries contained in the upper bound of r.
   */
  private List<RelationParts> relParts() {
    final List<RelationParts> relParts = new ArrayList<RelationParts>(bounds.relations().size());
    for(Relation r: bounds.relations()) {   
      IntSet upper = bounds.upperBound(r).indexView();
      if (upper.size()==bounds.lowerBound(r).size()) continue; // skip constant relation
      IntSet reps = Ints.bestSet(usize);
      for(IntIterator tuples = upper.iterator(); tuples.hasNext(); ) {
        for(int tIndex = tuples.next(), i = r.arity(); i > 0; i--, tIndex /= usize) {
          for(IntSet symm : symmetries) {
            if (symm.contains(tIndex%usize)) {
              reps.add(symm.min());
              break;
            }
          }
        }
      }
      relParts.add(new RelationParts(r, reps));
    }
    final Comparator<RelationParts> cmp = new Comparator<RelationParts>() {
      public int compare(RelationParts o1, RelationParts o2) {
        final int acmp = o1.relation.arity() - o2.relation.arity();
        return acmp!=0 ? acmp : String.valueOf(o1.relation.name()).compareTo(String.valueOf(o2.relation.name()));
      }
    };
    Collections.sort(relParts, cmp);
    return relParts;
  }
 
  /**
   * Returns a BooleanValue that is true iff the string of bits
   * represented by l0 is lexicographically less than or equal
   * to the string of bits reprented by l1.
   * @requires l0.size()==l1.size()
   * @return a circuit that compares l0 and l1
   */
  private static final BooleanValue leq(BooleanFactory f, List<BooleanValue> l0, List<BooleanValue> l1) {
    final BooleanAccumulator cmp = BooleanAccumulator.treeGate(Operator.AND);
    BooleanValue prevEquals = BooleanConstant.TRUE;
    for(int i = 0; i < l0.size(); i++) {
      cmp.add(f.implies(prevEquals, f.implies(l0.get(i), l1.get(i))));
      prevEquals = f.and(prevEquals, f.iff(l0.get(i), l1.get(i)));
    }
    return f.accumulate(cmp);
  }
 
  /**
   * Let t be the tuple represent by the given arity and tupleIndex.
   * This method returns the tuple index of the tuple t' such t'
   * is equal to t with each occurence of atomIndex0
   * replaced by atomIndex1 and vice versa.
   * @return the index of the tuple to which the given symmetry
   * maps the tuple specified by arith and tupleIndex
   */
  private final int permutation(int arity, int tupleIndex, int atomIndex0, int atomIndex1) {
    int permIndex = 0;
    for(int u = 1; arity > 0; arity--, tupleIndex /= usize, u *= usize ) {
      int atomIndex = tupleIndex%usize;
      if (atomIndex==atomIndex0)
        permIndex += atomIndex1 * u;
      else if (atomIndex==atomIndex1) {
        permIndex += atomIndex0 * u;
      } else {
        permIndex += atomIndex * u;
      }
    }
    return permIndex;
  }
 
  /**
   * Returns true if there is some index i such that l0[i] = v0 and l1[i] = v1.
   * @requires l0.size()=l1.size()
   * @return some i: int | l0[i] = v0 && l1[i] = v1
   */
  private static boolean atSameIndex(List<BooleanValue> l0, BooleanValue v0, List<BooleanValue> l1, BooleanValue v1) {
    for(int i = 0; i < l0.size(); i++) {
      if (l0.get(i).equals(v0) && l1.get(i).equals(v1))
        return true;
    }
    return false;
  }
 
  /**
   * Sorts the predicates in the given array in the ascending order of
   * the names of the predicates' relations, and returns it.
   * @return broken'
   * @effects all i: [0..preds.size()) | all j: [0..i) |
   *            broken[j].relation.name <= broken[i].relation.name
   */
  private static final <P extends RelationPredicate> P[] sort(final P[] preds) {
    final Comparator<RelationPredicate> cmp = new Comparator<RelationPredicate>() {
      public int compare(RelationPredicate o1, RelationPredicate o2) {
        return String.valueOf(o1.relation().name()).compareTo(String.valueOf(o2.relation().name()));
      }
    };
    Arrays.sort(preds, cmp);
    return preds;
 
 
  /**
   * If possible, breaks symmetry on the given acyclic predicate and returns a formula
   * f such that the meaning of acyclic with respect to this.bounds is equivalent to the
   * meaning of f with respect to this.bounds'. If symmetry cannot be broken on the given predicate, returns null. 
   *
   * <p>We break symmetry on the relation constrained by the given predicate iff
   * this.bounds.upperBound[acyclic.relation] is the cross product of some partition in this.symmetries with
   * itself. Assuming that this is the case, we then break symmetry on acyclic.relation using one of the methods
   * described in {@linkplain #breakMatrixSymmetries(Map, boolean)}; the method used depends
   * on the value of the "agressive" flag.
   * The partition that formed the upper bound of acylic.relation is removed from this.symmetries.</p>
   *
   * @return null if symmetry cannot be broken on acyclic; otherwise returns a formula
   * f such that the meaning of acyclic with respect to this.bounds is equivalent to the
   * meaning of f with respect to this.bounds'
   * @effects this.symmetries and this.bounds are modified as described in {@linkplain #breakMatrixSymmetries(Map, boolean)} iff this.bounds.upperBound[acyclic.relation] is the
   * cross product of some partition in this.symmetries with itself
   *
   * @see #breakMatrixSymmetries(Map,boolean)
   */
  private final Formula breakAcyclic(RelationPredicate.Acyclic acyclic, boolean aggressive) {
    final IntSet[] colParts = symmetricColumnPartitions(acyclic.relation());
    if (colParts!=null) {
      final Relation relation = acyclic.relation();
      final IntSet upper = bounds.upperBound(relation).indexView();
      final IntSet reduced = Ints.bestSet(usize*usize);
      for(IntIterator tuples = upper.iterator(); tuples.hasNext(); ) {
        int tuple = tuples.next();
        int mirror = (tuple / usize) + (tuple % usize)*usize;
        if (tuple != mirror) {
          if (!upper.contains(mirror)) return null;
          if (!reduced.contains(mirror))
            reduced.add(tuple)
        }
      }
     
      // remove the partition from the set of symmetric partitions
      removePartition(colParts[0].min());
     
      if (aggressive) {
        bounds.bound(relation, bounds.universe().factory().setOf(2, reduced));
        return Formula.TRUE;
      } else {
        final Relation acyclicConst = Relation.binary("SYM_BREAK_CONST_"+acyclic.relation().name());
        bounds.boundExactly(acyclicConst, bounds.universe().factory().setOf(2, reduced));
        return relation.in(acyclicConst);
      }
    }
    return null;
  }
 
  /**
   * If possible, breaks symmetry on the given total ordering predicate and returns a formula
   * f such that the meaning of total with respect to this.bounds is equivalent to the
   * meaning of f with respect to this.bounds'. If symmetry cannot be broken on the given predicate, returns null. 
   *
   * <p>We break symmetry on the relation constrained by the given predicate iff
   * total.first, total.last, and total.ordered have the same upper bound, which, when
   * cross-multiplied with itself gives the upper bound of total.relation. Assuming that this is the case,
   * we then break symmetry on total.relation, total.first, total.last, and total.ordered using one of the methods
   * described in {@linkplain #breakMatrixSymmetries(Map, boolean)}; the method used depends
   * on the value of the "agressive" flag.
   * The partition that formed the upper bound of total.ordered is removed from this.symmetries.</p>
   *
   * @return null if symmetry cannot be broken on total; otherwise returns a formula
   * f such that the meaning of total with respect to this.bounds is equivalent to the
   * meaning of f with respect to this.bounds'
   * @effects this.symmetries and this.bounds are modified as desribed in {@linkplain #breakMatrixSymmetries(Map, boolean)}
   * iff total.first, total.last, and total.ordered have the same upper bound, which, when
   * cross-multiplied with itself gives the upper bound of total.relation
   *
   * @see #breakMatrixSymmetries(Map,boolean)
   */
  private final Formula breakTotalOrder(RelationPredicate.TotalOrdering total, boolean aggressive) {
    final Relation first = total.first(), last = total.last(), ordered = total.ordered(), relation = total.relation();
    final IntSet domain = bounds.upperBound(ordered).indexView();   
 
    if (symmetricColumnPartitions(ordered)!=null &&
      bounds.upperBound(first).indexView().contains(domain.min()) &&
      bounds.upperBound(last).indexView().contains(domain.max())) {
     
      // construct the natural ordering that corresponds to the ordering of the atoms in the universe
      final IntSet ordering = Ints.bestSet(usize*usize);
      int prev = domain.min();
      for(IntIterator atoms = domain.iterator(prev+1, usize); atoms.hasNext(); ) {
        int next = atoms.next();
        ordering.add(prev*usize + next);
        prev = next;
      }
     
      if (ordering.containsAll(bounds.lowerBound(relation).indexView()) &&
        bounds.upperBound(relation).indexView().containsAll(ordering)) {
       
        // remove the ordered partition from the set of symmetric partitions
        removePartition(domain.min());
       
        final TupleFactory f = bounds.universe().factory();
       
        if (aggressive) {
          bounds.boundExactly(first, f.setOf(f.tuple(1, domain.min())));
          bounds.boundExactly(last, f.setOf(f.tuple(1, domain.max())));
          bounds.boundExactly(ordered, bounds.upperBound(total.ordered()));
          bounds.boundExactly(relation, f.setOf(2, ordering));
         
          return Formula.TRUE;
         
        } else {
          final Relation firstConst = Relation.unary("SYM_BREAK_CONST_"+first.name());
          final Relation lastConst = Relation.unary("SYM_BREAK_CONST_"+last.name());
          final Relation ordConst = Relation.unary("SYM_BREAK_CONST_"+ordered.name());
          final Relation relConst = Relation.binary("SYM_BREAK_CONST_"+relation.name());
          bounds.boundExactly(firstConst, f.setOf(f.tuple(1, domain.min())));
          bounds.boundExactly(lastConst, f.setOf(f.tuple(1, domain.max())));
          bounds.boundExactly(ordConst, bounds.upperBound(total.ordered()));
          bounds.boundExactly(relConst, f.setOf(2, ordering));
         
          return Formula.and(first.eq(firstConst), last.eq(lastConst), ordered.eq(ordConst), relation.eq(relConst));
//          return first.eq(firstConst).and(last.eq(lastConst)).and( ordered.eq(ordConst)).and( relation.eq(relConst));
        }

      }
    }
   
    return null;
  }

  /**
   * Removes from this.symmetries the partition that contains the specified atom.
   * @effects this.symmetries' = { s: this.symmetries | !s.contains(atom) }
   */
  private final void removePartition(int atom) {
    for(Iterator<IntSet> symIter = symmetries.iterator(); symIter.hasNext(); ) {
      if (symIter.next().contains(atom)) {
        symIter.remove();
        break;
      }     
    }
  }
 
  /**
   * If all columns of the upper bound of r are symmetric partitions,
   * those partitions are returned.  Otherwise null is returned.
   * @return (all i: [0..r.arity) | some s: symmetries[int] |
   *          bounds.upperBound[r].project(i).indexView() = s) =>
   *         {colParts: [0..r.arity)->IntSet |
   *          all i: [0..r.arity()) | colParts[i] = bounds.upperBound[r].project(i).indexView() },
   *         null
   */
  private final IntSet[] symmetricColumnPartitions(Relation r) {
    final IntSet upper = bounds.upperBound(r).indexView();
    if (upper.isEmpty()) return null;
   
    final IntSet[] colParts = new IntSet[r.arity()];
    for(int i = r.arity()-1, min = upper.min(); i >= 0; i--, min /= usize) {
      for(IntSet part : symmetries) {
        if (part.contains(min%usize)) {
          colParts[i] = part;
          break;
        }
      }
      if (colParts[i]==null)
        return null;
    }
    for(IntIterator tuples = upper.iterator(); tuples.hasNext(); ) {
      for(int i = r.arity()-1, tuple = tuples.next(); i >= 0; i--, tuple /= usize) {
        if (!colParts[i].contains(tuple%usize))
          return null;
      }   
    }
    return colParts; 
  }
 
  /**
   * An entry for a relation and the representative (least atom) for each
   * symmetry class in the relation's upper bound.
   */
  private static final class RelationParts {
    final Relation relation;
    final IntSet representatives;
   
    RelationParts(Relation relation, IntSet representatives) {
      this.relation = relation;
      this.representatives = representatives;
    }
  }
}
TOP

Related Classes of kodkod.engine.fol2sat.SymmetryBreaker$RelationParts

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.