package com.juliasoft.beedeedee.factories;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Comparator;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.juliasoft.beedeedee.bdd.Assignment;
/**
* A set of equivalence classes. This is an immutable class
* and can be shared safely.
*/
public class EquivalenceRelation implements Iterable<BitSet> {
private final BitSet[] equivalenceClasses;
private final int hashCode;
/**
* This is the only empty element.
*/
public final static EquivalenceRelation empty = new EquivalenceRelation();
private static Comparator<BitSet> bitSetOrder = new Comparator<BitSet>() {
public int compare(BitSet lhs, BitSet rhs) {
if (lhs == rhs)
return 0;
BitSet xor = (BitSet) lhs.clone();
xor.xor(rhs);
int firstDifferent = xor.length() - 1;
if (firstDifferent < 0)
return 0;
else
return rhs.get(firstDifferent) ? 1 : -1;
}
};
private EquivalenceRelation(List<BitSet> equivalenceClasses, boolean shouldSort) {
this.equivalenceClasses = new BitSet[equivalenceClasses.size()];
int pos = 0;
for (BitSet eqClass: equivalenceClasses)
this.equivalenceClasses[pos++] = eqClass;
if (shouldSort)
sort();
this.hashCode = hashCodeAux();
}
private EquivalenceRelation(BitSet[] equivalenceClasses, boolean shouldSort) {
this.equivalenceClasses = equivalenceClasses;
if (shouldSort)
sort();
this.hashCode = hashCodeAux();
}
private EquivalenceRelation() {
this.equivalenceClasses = new BitSet[0];
this.hashCode = hashCodeAux();
}
public EquivalenceRelation filter(Filter filter) {
if (isEmpty())
return empty;
List<BitSet> filtered = filterClasses(filter);
if (filtered.isEmpty())
return empty;
else
return new EquivalenceRelation(filtered, false); // no need to sort
}
public EquivalenceRelation(int[][] classes) {
this.equivalenceClasses = new BitSet[classes.length];
for (int pos = 0; pos < classes.length; pos++) {
BitSet added = new BitSet();
for (int i: classes[pos])
added.set(i);
equivalenceClasses[pos] = added;
}
sort();
this.hashCode = hashCodeAux();
}
private List<BitSet> filterClasses(Filter filter) {
List<BitSet> equivalenceClasses = new ArrayList<>();
for (BitSet eqClass: this.equivalenceClasses)
if (filter.accept(eqClass))
equivalenceClasses.add(eqClass);
return equivalenceClasses;
}
private void sort() {
if (equivalenceClasses.length > 1)
Arrays.sort(equivalenceClasses, bitSetOrder);
}
/**
* Computes the intersection of two E's.
*
* @param other the other set
* @return the resulting set
*/
public EquivalenceRelation intersection(EquivalenceRelation other) {
if (isEmpty() || other.isEmpty())
return empty;
List<BitSet> intersection = new ArrayList<>();
for (BitSet set1: equivalenceClasses) {
for (BitSet set2: other.equivalenceClasses) {
BitSet element = (BitSet) set1.clone();
element.and(set2);
if (element.cardinality() > 1)
intersection.add(element);
}
}
if (intersection.isEmpty())
return empty;
else
return new EquivalenceRelation(intersection, true);
}
public boolean isEmpty() {
// the following works since this class enforces the invariant
// that "empty" is the only empty set of equivalence relations
return equivalenceClasses.length == 0; //this == empty;
}
public int size() {
return equivalenceClasses.length;
}
@Override
public Iterator<BitSet> iterator() {
return Arrays.asList((BitSet[]) equivalenceClasses).iterator();
}
/**
* Generates a list containing all pairs of equivalent variables
*
* @return the list of pairs
*/
public Collection<Pair> pairs() {
Collection<Pair> pairs = new ArrayList<>();
for (BitSet bs: equivalenceClasses)
for (int i = bs.nextSetBit(0); i >= 0; i = bs.nextSetBit(i + 1))
for (int j = bs.nextSetBit(i + 1); j >= 0; j = bs.nextSetBit(j + 1))
pairs.add(new Pair(i, j));
return pairs;
}
/**
* Adds pairs to this set.
*
* @param pairs the pairs to add
* @return the new set of pairs. Yields the same set if and only if nothing changed
*/
public EquivalenceRelation addPairs(Iterable<Pair> pairs) {
List<BitSet> newEquivalenceClasses = new ArrayList<>();
for (BitSet eqClass: equivalenceClasses)
newEquivalenceClasses.add(eqClass);
boolean changed = false;
for (Pair pair: pairs)
changed |= addPair(pair, newEquivalenceClasses);
if (changed)
return new EquivalenceRelation(newEquivalenceClasses, true);
else
return this;
}
/**
* Adds a pair to this set.
*
* @param pair the pair to add
*/
private static boolean addPair(Pair pair, List<BitSet> where) {
int pos1 = findClass(pair.first, where);
int pos2 = findClass(pair.second, where);
if (pos1 >= 0) {
if (pos2 >= 0) {
BitSet c1 = where.get(pos1);
BitSet c2 = where.get(pos2);
// != instead of !equals() is correct since sets are disjoint
if (c1 != c2) {
c1 = (BitSet) c1.clone();
c1.or(c2);
where.set(pos1, c1);
where.remove(pos2);
return true;
}
}
else {
BitSet c1 = (BitSet) where.get(pos1).clone();
c1.set(pair.second);
where.set(pos1, c1);
return true;
}
}
else
if (pos2 >= 0) {
BitSet c2 = (BitSet) where.get(pos2).clone();
c2.set(pair.first);
where.set(pos2, c2);
return true;
}
else {
BitSet eqClass = new BitSet();
eqClass.set(pair.first);
eqClass.set(pair.second);
where.add(eqClass);
return true;
}
return false;
}
public EquivalenceRelation addClasses(EquivalenceRelation other) {
if (other.isEmpty())
return this;
else if (isEmpty())
return other;
List<BitSet> newEquivalenceClasses = new ArrayList<>();
for (BitSet eqClass: equivalenceClasses)
newEquivalenceClasses.add(eqClass);
for (BitSet added: other.equivalenceClasses)
addClass(added, newEquivalenceClasses);
return new EquivalenceRelation(newEquivalenceClasses, true);
}
private static void addClass(BitSet added, List<BitSet> where) {
BitSet unionOfIntersected = null;
LinkedList<Integer> toRemove = new LinkedList<>();
for (int pos = 0; pos < where.size(); pos++) {
BitSet cursor = where.get(pos);
if (cursor.intersects(added)) {
if (unionOfIntersected == null) {
where.set(pos, unionOfIntersected = (BitSet) cursor.clone());
unionOfIntersected.or(added);
}
else {
unionOfIntersected.or(cursor);
toRemove.addFirst(pos);
}
}
}
if (unionOfIntersected == null)
where.add((BitSet) added.clone());
else
for (int pos: toRemove)
where.remove(pos);
}
private BitSet findClass(int n) {
for (BitSet eqClass: equivalenceClasses)
if (eqClass.get(n))
return eqClass;
return null;
}
private int findIndexOfClass(int n) {
for (int pos = 0; pos < equivalenceClasses.length; pos++) {
BitSet eqClass = equivalenceClasses[pos];
if (eqClass.get(n))
return pos;
}
return -1;
}
private static int findClass(int n, List<BitSet> where) {
int pos = 0;
for (BitSet eqClass: where) {
if (eqClass.get(n))
return pos;
pos++;
}
return -1;
}
@Override
public boolean equals(Object obj) {
return obj instanceof EquivalenceRelation &&
Arrays.equals(((EquivalenceRelation) obj).equivalenceClasses, equivalenceClasses);
}
@Override
public int hashCode() {
return hashCode;
}
private int hashCodeAux() {
return Arrays.hashCode(equivalenceClasses);
}
@Override
public String toString() {
return Arrays.toString(equivalenceClasses);
}
public int maxVar() {
int max = 0;
for (BitSet eqClass: equivalenceClasses)
max = Math.max(max, eqClass.length() - 1);
return max;
}
/**
* Updates the given assignment with information on equivalent variables.
*
* @param a the assignment to update
*/
void updateAssignment(Assignment a) {
classIteration:
for (BitSet eqClass: equivalenceClasses) {
for (int i = eqClass.nextSetBit(0); i >= 0; i = eqClass.nextSetBit(i + 1)) {
try {
if (a.holds(i)) {
setAll(a, eqClass, true);
continue classIteration;
}
}
catch (Exception e) {
// ignore exception if variable not in assignment
}
}
setAll(a, eqClass, false);
}
}
private static void setAll(Assignment a, BitSet eqClass, boolean value) {
for (int i = eqClass.nextSetBit(0); i >= 0; i = eqClass.nextSetBit(i + 1))
a.put(i, value);
}
public EquivalenceRelation removeVar(int var) {
int pos = findIndexOfClass(var);
if (pos >= 0) {
BitSet c = equivalenceClasses[pos];
if (c.cardinality() > 2) {
BitSet[] newEquivalenceClasses = equivalenceClasses.clone();
BitSet eqClass = (BitSet) c.clone();
eqClass.clear(var);
newEquivalenceClasses[pos] = eqClass;
return new EquivalenceRelation(newEquivalenceClasses, true);
}
else {
int length = equivalenceClasses.length;
if (length == 1)
return empty;
BitSet[] newEquivalenceClasses = new BitSet[length - 1];
for (int i = 0, j = 0; i < length; i++)
if (i != pos)
newEquivalenceClasses[j++] = equivalenceClasses[i];
return new EquivalenceRelation(newEquivalenceClasses, false);
}
}
else
return this;
}
public int nextLeader(int var) {
return findClass(var).nextSetBit(var + 1);
}
public int nextLeader(int var, BitSet excludedVars) {
BitSet c = findClass(var);
int leader = c.nextSetBit(0);
while (excludedVars.get(leader) || leader == var) {
leader = c.nextSetBit(leader + 1);
if (leader < 0)
return -1;
}
return leader;
}
public EquivalenceRelation replace(Map<Integer, Integer> renaming) {
BitSet[] where = null;
for (int i: renaming.keySet()) {
int pos = findIndexOfClass(i);
if (pos >= 0) {
if (where == null)
where = equivalenceClasses.clone();
BitSet c = where[pos];
c = (BitSet) c.clone();
c.clear(i);
c.set(renaming.get(i));
where[pos] = c;
}
}
if (where == null)
return this;
else
return new EquivalenceRelation(where, true);
}
public boolean containsVar(int var) {
for (BitSet eqClass: equivalenceClasses)
if (eqClass.get(var))
return true;
return false;
}
public int getLeader(int var) {
for (BitSet eqClass: equivalenceClasses)
if (eqClass.get(var))
return eqClass.nextSetBit(0);
return var;
}
public int getLeader(int var, Filter filter) {
for (BitSet eqClass: equivalenceClasses)
if (eqClass.get(var) && filter.accept(eqClass))
return eqClass.nextSetBit(0);
return -1;
}
/**
* Finds the minimum leader that is greater or equal to c
*
* @param c
* @return the minimum leader >= c, or -1 if it does not exist
*/
public int getMinLeaderGreaterOrEqualtTo(int c, int var, Filter filter) {
int min = -1;
for (BitSet eqClass: equivalenceClasses) {
int leader = eqClass.nextSetBit(0);
if (leader >= c && (min < 0 || leader < min) && leader < var && filter.accept(eqClass))
min = leader;
}
return min;
}
public static interface Filter {
public boolean accept(BitSet eqClass);
}
}