Package kodkod.engine.fol2sat

Source Code of kodkod.engine.fol2sat.SymmetryDetector

/*
* 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.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;

import kodkod.ast.Relation;
import kodkod.instance.Bounds;
import kodkod.instance.TupleSet;
import kodkod.util.ints.IntIterator;
import kodkod.util.ints.IntSet;
import kodkod.util.ints.Ints;

/**
* Partitions a universe into equivalence classes based
* on the bounding constraints given by a Bounds object.
* @specfield bounds: Bounds // bounds on which the partitioning is based
* @author Emina Torlak
*/
final class SymmetryDetector {
  private final Bounds bounds;
  /* invariant: representatives always holds a sequence of IntSets that partition bounds.universe */
  private final List<IntSet> parts;
  private final int usize;
 
  /**
   * Constructs a new SymmetryDetector for the given bounds.
   * @effects this.bounds' = bounds
   */
  private SymmetryDetector(Bounds bounds) {
    this.bounds = bounds;
    this.usize = bounds.universe().size();
   
        //  start with the maximum partition -- the whole universe.
    this.parts = new LinkedList<IntSet>();
    final IntSet set = Ints.bestSet(usize);
    for(int i = 0; i < usize; i++) { set.add(i); }
    this.parts.add(set);
  }
 
  /**
   * Returns a sound partitioning of bounds.universe
   * into symmetry classes.  Each intset in the returned
   * collection represents the indices of the atoms in
   * this.bounds.universe that belong to the same equivalence class.
   * @return a sound partitioning of bounds.universe
   * into symmetry classes
   */
  static Set<IntSet> partition(Bounds bounds) {   
    final SymmetryDetector detector = new SymmetryDetector(bounds);
    detector.computePartitions();
    final Set<IntSet> parts = new LinkedHashSet<IntSet>(detector.parts);
    assert parts.size()==detector.parts.size(); // sanity check
    return parts;
  }

 
  /**
   * Partitions this.bounds.universe into sets of equivalent atoms.
   * @effects all disj s, q: this.parts'[int] |
   *           some s.ints && some q.ints && (no s.ints & q.ints) &&
   *           this.parts'[int].ints = [0..this.bounds.universe.size()) &&
   *           (all ts: this.bounds.lowerBound[Relation] + this.bounds.upperBound[Relation] |
   *             all s: this.parts'[int] | all a1, a2: this.bounds.universe.atoms[s.ints] |
   *              all t1, t2: ts.tuples | t1.atoms[0] = a1 && t2.atoms[0] = a2 =>
   *                t1.atoms[1..ts.arity) = t1.atoms[1..ts.arity) ||
   *                t1.atoms[1..ts.arity) = a1 && t1.atoms[1..ts.arity) = a2)
   */
  private final void computePartitions() {
    if (usize==1) return; // nothing more to do
 
    final Map<IntSet, IntSet> range2domain = new HashMap<IntSet, IntSet>((usize*2) / 3);
   
    // refine the partitions based on the bounds for each integer
    for(IntIterator iter = bounds.ints().iterator(); iter.hasNext();) {
      TupleSet exact = bounds.exactBound(iter.next());
      refinePartitions(exact.indexView(), 1, range2domain);
    }
   
    // refine the partitions based on the upper/lower bounds for each relation
    for(TupleSet s : sort(bounds)) {
      if (parts.size()==usize) return;
      refinePartitions(s.indexView(), s.arity(), range2domain);     
    }
   
  }
 
  /**
   * Returns an array that contains unique non-empty tuplesets in the given bounds,
   * sorted in the order of increasing size.
   * @return unique non-empty tuplesets in the given bounds,
   * sorted in the order of increasing size.
   */
  private static TupleSet[] sort(Bounds bounds) {
    final List<TupleSet> sets = new ArrayList<TupleSet>(bounds.relations().size());
    for(Relation r : bounds.relations()) {
      final TupleSet lower = bounds.lowerBound(r);
      final TupleSet upper = bounds.upperBound(r);
      if (!lower.isEmpty() && lower.size()<upper.size()) { sets.add(lower); }
      if (!upper.isEmpty()) {  sets.add(upper); }
    }
    final TupleSet[] sorted = sets.toArray(new TupleSet[sets.size()]);
    Arrays.sort(sorted, new Comparator<TupleSet>(){
      public int compare(TupleSet o1, TupleSet o2) {
        return o1.size() - o2.size();
      }
    });
    return sorted;
  }
 
  /**
   * Refines the atomic partitions in this.parts based on the contents of the given tupleset,
   * decomposed into its constituent IntSet and arity.  The range2domain map is used for
   * intermediate computations for efficiency (to avoid allocating it in each recursive call).
   * @requires all disj s, q: this.parts[int] |
   *            some s.ints && some q.ints && (no s.ints & q.ints) &&
   *            this.parts[int].ints = [0..this.bounds.universe.size())
   * @effects  let usize = this.bounds.universe.size(), firstColFactor = usize^(arit-1) |
   *            all disj s, q: this.parts'[int] |
   *             some s.ints && some q.ints && (no s.ints & q.ints) &&
   *             this.parts'[int].ints = [0..usize) &&
   *             all s: this.parts'[int] | all a1, a2: this.bounds.universe.atoms[s.ints] |
   *               all t1, t2: set.ints | t1 / firstColFactor = a1 && t2 / firstColFactor = a2 =>
   *                 t1 % firstColFactor = t2 % firstColFactor  ||
   *                 t1 = a1*((1 - firstColFactor) / (1 - usize)) &&
   *                 t2 = a2*((1 - firstColFactor) / (1 - usize)))
   */
  private void refinePartitions(IntSet set, int arity, Map<IntSet, IntSet> range2domain) {
    if (arity==1) {
      refinePartitions(set);
      return;
    }
   
    final List<IntSet> otherColumns = new LinkedList<IntSet>();
    int firstColFactor = (int) StrictMath.pow(usize, arity-1);
    IntSet firstCol = Ints.bestSet(usize);
    for(IntIterator rbIter = set.iterator(); rbIter.hasNext(); ) {
      firstCol.add(rbIter.next() / firstColFactor);
    }
    refinePartitions(firstCol);
   
    int idenFactor = (1 - firstColFactor) / (1 - usize);
    for(ListIterator<IntSet> partsIter = parts.listIterator(); partsIter.hasNext(); ) {
      IntSet part = partsIter.next();
      if (firstCol.contains(part.min())) { // contains one, contains them all
        range2domain.clear();
        for(IntIterator atoms = part.iterator(); atoms.hasNext(); ) {
          int atom = atoms.next();
          IntSet atomRange = Ints.bestSet(firstColFactor);
          for(IntIterator rbIter = set.iterator(atom*firstColFactor, (atom+1)*firstColFactor - 1);
          rbIter.hasNext(); ) {
            atomRange.add(rbIter.next() % firstColFactor);
          }
          IntSet atomDomain = range2domain.get(atomRange);
          if (atomDomain != null) atomDomain.add(atom);
          else range2domain.put(atomRange, oneOf(usize, atom));
        }
        partsIter.remove();
        IntSet idenPartition = Ints.bestSet(usize);
        for(Map.Entry<IntSet, IntSet> entry : range2domain.entrySet()) {
          if (entry.getValue().size()==1 && entry.getKey().size()==1 &&
            entry.getKey().min() == entry.getValue().min() * idenFactor) {
            idenPartition.add(entry.getValue().min());
          } else {
            partsIter.add(entry.getValue());
            otherColumns.add(entry.getKey());
          }
        }
        if (!idenPartition.isEmpty())
          partsIter.add(idenPartition);     
      }
    }
   
    // refine based on the remaining columns
    for(IntSet otherCol : otherColumns) {
      refinePartitions(otherCol, arity-1, range2domain);
    }
  }
 
  /**
   * Refines the atomic partitions this.parts based on the contents of the given set.
   * @requires all disj s, q: this.parts[int] |
   *            some s.ints && some q.ints && (no s.ints & q.ints) &&
   *            this.parts[int].ints = [0..this.bounds.universe.size())
   * @effects  all disj s, q: this.parts'[int] |
   *            some s.ints && some q.ints && (no s.ints & q.ints) &&
   *            this.parts'[int].ints = [0..this.bounds.universe.size()) &&
   *            (all i: [0..this.parts'.size()) |
   *             this.parts'[i].ints in set.ints || no this.parts'[i].ints & set.ints)
   */
  private void refinePartitions(IntSet set) {
    for(ListIterator<IntSet> partsIter = parts.listIterator(); partsIter.hasNext(); ) {
      IntSet part = partsIter.next();
      IntSet intersection = Ints.bestSet(part.min(), part.max());
      intersection.addAll(part);
      intersection.retainAll(set);
      if (!intersection.isEmpty() && intersection.size() < part.size()) {
        part.removeAll(intersection);
        partsIter.add(intersection);
      }
    }
  }
 
  /**
   * Returns an IntSet that can store elements
   * in the range [0..size), and that holds
   * the given number.
   * @requries 0 <= num < size
   * @return {s: IntSet | s.ints = num }
   */
  private static final IntSet oneOf(int size, int num) {
    final IntSet set = Ints.bestSet(size);
    set.add(num);
    return set;
  }
}
TOP

Related Classes of kodkod.engine.fol2sat.SymmetryDetector

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.