/* * Copyright (C) 2016 Matthias Heizmann (heizmann@informatik.uni-freiburg.de) * Copyright (C) 2016 University of Freiburg * * This file is part of the ULTIMATE Util Library. * * The ULTIMATE Util Library is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * The ULTIMATE Util Library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with the ULTIMATE Util Library. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE Util Library, or any covered work, by linking * or combining it with Eclipse RCP (or a modified version of Eclipse RCP), * containing parts covered by the terms of the Eclipse Public License, the * licensors of the ULTIMATE Util Library grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.util.datastructures; import java.util.Collection; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Map.Entry; import java.util.Optional; import java.util.Set; import java.util.function.Function; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.HashRelation; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Triple; /** * Memory efficient data structure that stores for a given equivalence relation if pairs are in the relation, not in the * relation, if the membership status is unknown. * * @author Matthias Heizmann (heizmann@informatik.uni-freiburg.de) * * @param * type of the elements in the equivalence relation */ public class ThreeValuedEquivalenceRelation { private final UnionFind mUnionFind; private final HashRelation mDisequalities; private boolean mIsInconsistent; public ThreeValuedEquivalenceRelation() { mUnionFind = new UnionFind<>(); mDisequalities = new HashRelation<>(); mIsInconsistent = false; } public ThreeValuedEquivalenceRelation(final Comparator elementComparator) { assert elementComparator != null : "use other constructor in this case!"; mUnionFind = new UnionFind<>(elementComparator); mDisequalities = new HashRelation<>(); mIsInconsistent = false; } public ThreeValuedEquivalenceRelation(final ThreeValuedEquivalenceRelation tver) { this.mUnionFind = tver.mUnionFind.clone(); this.mDisequalities = new HashRelation<>(tver.mDisequalities); this.mIsInconsistent = tver.mIsInconsistent; assert sanityCheck(); } public ThreeValuedEquivalenceRelation(final UnionFind newPartition, final HashRelation newDisequalities) { mUnionFind = newPartition.clone(); mDisequalities = new HashRelation<>(newDisequalities); mIsInconsistent = false; assert sanityCheck(); } /** * @return true iff elem was not contained in relation before (i.e. we made a change) */ public boolean addElement(final E elem) { if (mUnionFind.find(elem) == null) { mUnionFind.findAndConstructEquivalenceClassIfNeeded(elem); return true; } return false; } /** * Remove the given element (first argument) from this Tver. * * If elem is not the only element in its equivalence class and is the representative before removal, * we need to choose another representative, the second argument allows the caller to participate in that choice. * * @param elem * @param newRepChoice if newRepChoice is non-null, and elem was representative before removal, newRepChoice will * be picked as new representative, otherwise this method picks some candidate (nondeterministically, depending on * the hashing order) * @return the representative of elem's equivalence class after removal of elem, null if it was the only element of * its equivalence class */ public E removeElement(final E elem, final E newRepChoice) { assert newRepChoice == null || getRepresentative(elem) == getRepresentative(newRepChoice); final E rep = mUnionFind.find(elem); final Set equivalenceClassCopy = new HashSet<>(mUnionFind.getEquivalenceClassMembers(elem)); mUnionFind.remove(elem, newRepChoice); /* * update mDistinctElements */ if (rep != elem) { // elem was not the representative of its equivalence class --> nothing to do for disequalities assert getRepresentative(rep) == rep; return rep; } // elem was the representative of its equivalence class --> we need to update mDistinctElements equivalenceClassCopy.remove(elem); if (equivalenceClassCopy.isEmpty()) { // elem was the only element in its equivalence class --> just remove it from disequalities mDisequalities.removeDomainElement(elem); mDisequalities.removeRangeElement(elem); assert sanityCheck(); return null; } assert rep == elem; // elem was the representative of its equivalence class, but not the only element // --> replace it by the new representative in mDistinctElements final E newRep; if (newRepChoice == null) { newRep = mUnionFind.find(equivalenceClassCopy.iterator().next()); } else { newRep = newRepChoice; } assert newRep!= null; mDisequalities.replaceDomainElement(elem, newRep); mDisequalities.replaceRangeElement(elem, newRep); assert sanityCheck(); assert getRepresentative(newRep) == newRep : "the returned element must be a representative, " + newRep + " is its own rep, but " + getRepresentative(newRep) + " is."; return newRep; } /** * * @param elem1 * @param elem2 * @return true iff this data structure did not already store the equality of the specified pair */ public boolean reportEquality(final E elem1, final E elem2) { if (mIsInconsistent) { throw new IllegalStateException(); } final E oldRep1 = mUnionFind.find(elem1); if (oldRep1 == null) { throw new IllegalArgumentException("unknown element " + elem1); } final E oldRep2 = mUnionFind.find(elem2); if (oldRep2 == null) { throw new IllegalArgumentException("unknown element " + elem2); } if (oldRep1 == oldRep2) { // the elements already are in the same equivalence class, do nothing return false; } if (getEqualityStatus(elem1, elem2) == EqualityStatus.NOT_EQUAL) { reportInconsistency(); return true; } mUnionFind.union(elem1, elem2); /* * Because either oldRep1 or oldRep2 is no longer a representative now. By our convention, the element of the * relation mDisequalities may only be representatives. Thus we may have to update mDisequalities accordingly. */ if (isRepresentative(oldRep1)) { // elem1 has kept its representative, elem2 has a new representative now assert mUnionFind.find(elem2) == oldRep1; mDisequalities.replaceDomainElement(oldRep2, oldRep1); mDisequalities.replaceRangeElement(oldRep2, oldRep1); } else { // elem2 has kept its representative, elem1 has a new representative now assert mUnionFind.find(elem1) == oldRep2; mDisequalities.replaceDomainElement(oldRep1, oldRep2); mDisequalities.replaceRangeElement(oldRep1, oldRep2); } assert sanityCheck(); return true; } /** * Sets the state of this TVER to "inconsistent". */ private void reportInconsistency() { mIsInconsistent = true; } /** * * @param elem1 * @param elem2 * @return true iff this data structure did not already store the disequality of the specified pair */ public boolean reportDisequality(final E elem1, final E elem2) { if (mIsInconsistent) { throw new IllegalStateException(); } final E rep1 = mUnionFind.find(elem1); if (rep1 == null) { throw new IllegalArgumentException("unknown element " + elem1); } final E rep2 = mUnionFind.find(elem2); if (rep2 == null) { throw new IllegalArgumentException("unknown element " + elem2); } if (getEqualityStatus(rep1, rep2) == EqualityStatus.NOT_EQUAL) { return false; } if (rep1 == rep2) { reportInconsistency(); return true; } mDisequalities.addPair(rep1, rep2); assert sanityCheck(); return true; } public E getRepresentativeAndAddElementIfNeeded(final E elem) { return mUnionFind.findAndConstructEquivalenceClassIfNeeded(elem); } /** * Returns the representative of the given element's equivalence class. Returns null if the given element has not * been added yet. * * @param elem * element to get the representative for * @return representative or null */ public E getRepresentative(final E elem) { return mUnionFind.find(elem); } public boolean isRepresentative(final E elem) { if (!getAllElements().contains(elem)) { throw new IllegalArgumentException("only call this for elements that are present!"); } return getRepresentative(elem) == elem; } /** * @return true iff the equalities and disequalities that have been reported are inconsistent */ public boolean isInconsistent() { return mIsInconsistent; } public EqualityStatus getEqualityStatus(final E elem1, final E elem2) { if (mIsInconsistent) { throw new IllegalStateException( "Cannot get equality status from inconsistent " + this.getClass().getSimpleName()); } final E elem1Rep = mUnionFind.find(elem1); if (elem1Rep == null) { throw new IllegalArgumentException("Unknown element: " + elem1); } final E elem2Rep = mUnionFind.find(elem2); if (elem2Rep == null) { throw new IllegalArgumentException("Unknown element: " + elem2); } if (elem1Rep.equals(elem2Rep)) { return EqualityStatus.EQUAL; } else if (mDisequalities.containsPair(elem1Rep, elem2Rep) || mDisequalities.containsPair(elem2Rep, elem1Rep)) { return EqualityStatus.NOT_EQUAL; } else { return EqualityStatus.UNKNOWN; } } /** * * Note that this sanity check currently forbids null entries for the relation -- if we want null entries, we * have to revise this. * * @return true iff sanity check is passed */ public boolean sanityCheck() { // call the sanityCheck of the underlying Unionfind if (!mUnionFind.sanityCheck()) { return false; } // mDisequalities may not contain null entries for (final Entry en : mDisequalities.getSetOfPairs()) { if (en.getKey() == null) { return false; } if (en.getValue() == null) { return false; } } // disequalites only contain representatives for (final Entry en : mDisequalities.getSetOfPairs()) { if (!isRepresentative(en.getKey())) { return false; } if (!isRepresentative(en.getValue())) { return false; } } return true; } public Collection getAllRepresentatives() { return mUnionFind.getAllRepresentatives(); } public Collection> getAllEquivalenceClasses() { return mUnionFind.getAllEquivalenceClasses(); } /** * Returns a String representation of this equivalence relation. Represents it by the partition and the list of * disequalities. Exceptions: *
  • If this is tautological (i.e. the partition only contains singletons and the set of disequalities is empty), * this is represented by "True". *
  • If this is inconsistent (i.e., the partition and the disequalities contradict), this is represented by * "False". */ @Override public String toString() { if (isTautological()) { return "True"; } if (isInconsistent()) { return "False"; } final StringBuilder sb = new StringBuilder(); sb.append("Equivalences: "); sb.append(mUnionFind); sb.append("\n"); sb.append("Non-Equivalences: "); sb.append(mDisequalities); return sb.toString(); } public Set getAllElements() { return mUnionFind.getAllElements(); } public Set getRepresentativesUnequalTo(final E rep) { assert isRepresentative(rep); final Set result = new HashSet<>(); result.addAll(mDisequalities.getImage(rep)); for (final E domEl : mDisequalities.getDomain()) { if (mDisequalities.getImage(domEl).contains(rep)) { result.add(domEl); } } return result; } /** * @return Set of all elements that are known by this * {@link ThreeValuedEquivalenceRelation} and are equivalent to * elem. */ public Set getEquivalenceClass(final E elem) { return mUnionFind.getEquivalenceClassMembers(elem); } public ThreeValuedEquivalenceRelation join(final ThreeValuedEquivalenceRelation other) { final UnionFind newPartition = UnionFind.intersectPartitionBlocks(this.mUnionFind, other.mUnionFind).getFirst(); return new ThreeValuedEquivalenceRelation<>(newPartition, xJoinDisequalities(this, other, newPartition, true)); } public ThreeValuedEquivalenceRelation meet(final ThreeValuedEquivalenceRelation other) { final UnionFind newPartition = UnionFind.unionPartitionBlocks(this.mUnionFind, other.mUnionFind); return new ThreeValuedEquivalenceRelation<>(newPartition, xJoinDisequalities(this, other, newPartition, false)); } public Triple, HashRelation, HashRelation> joinPartitions( final ThreeValuedEquivalenceRelation other) { return UnionFind.intersectPartitionBlocks(this.mUnionFind, other.mUnionFind); } /** * Conjoin or disjoin two disequality relations. * * @param tver1 * @param tver2 * @param newElemPartition * @param conjoin * conjoin or disjoin? * @return */ private static HashRelation xJoinDisequalities(final ThreeValuedEquivalenceRelation tver1, final ThreeValuedEquivalenceRelation tver2, final UnionFind newElemPartition, final boolean conjoin) { final HashRelation result = new HashRelation<>(); for (final Entry pair : CrossProducts .binarySelectiveCrossProduct(newElemPartition.getAllRepresentatives(), false, false)) { final boolean addDisequality; if (conjoin) { addDisequality = tver1.getEqualityStatus(pair.getKey(), pair.getValue()) == EqualityStatus.NOT_EQUAL && tver2.getEqualityStatus(pair.getKey(), pair.getValue()) == EqualityStatus.NOT_EQUAL; } else { addDisequality = tver1.getEqualityStatus(pair.getKey(), pair.getValue()) == EqualityStatus.NOT_EQUAL || tver2.getEqualityStatus(pair.getKey(), pair.getValue()) == EqualityStatus.NOT_EQUAL; } if (addDisequality) { result.addPair(pair.getKey(), pair.getValue()); } } return result; } public Map getSupportingEqualities() { final Map result = new HashMap<>(); for (final Set eqc : mUnionFind.getAllEquivalenceClasses()) { E lastElement = null; ; for (final E e : eqc) { if (lastElement != null) { result.put(e, lastElement); } lastElement = e; } } return result; } public HashRelation getDisequalities() { assert !mDisequalities.getSetOfPairs().stream().anyMatch(pr -> pr.getValue() == null); // TODO: make a copy before returning or not? (safer but slower) return new HashRelation<>(mDisequalities); } /** * * @return true iff the equality relation represented by this constraint is empty, i.e., for any two elements e1, e2 * getEqualityStatus(e1, e2) returns UNKNOWN. */ public boolean isTautological() { return getSupportingEqualities().isEmpty() && mDisequalities.isEmpty(); } public void transformElements(final Function transformer) { mUnionFind.transformElements(transformer); final HashRelation disequalitiesCopy = new HashRelation<>(mDisequalities); for (final Entry pair : disequalitiesCopy) { mDisequalities.removePair(pair.getKey(), pair.getValue()); mDisequalities.addPair(transformer.apply(pair.getKey()), transformer.apply(pair.getValue())); } assert sanityCheck(); } /** * Computes a ThreeValuedEquivalenceRelation that has the same constraints as this except some are filtered out. * In Particular only constraints are kept where on at least one side an element from the given set elems occurs. * (if we think of this TVER as two sets pairs, equalities and disequalities) * The elements that become unconstrained through this are omitted from the result. * * Example, in the partition view: * elems {q1, q2} * this: {q1, a, b}, {d, e, f}, {q2}, q1 != e * result: {q1, a, b}, {q2}, q1 != e * * @author Alexander Nutz (nutz@informatik.uni-freiburg.de) * * @param elem element constraints over which are to be kept * @return a new, projected ThreeValuedEquivalenceRelation */ public ThreeValuedEquivalenceRelation filterAndKeepOnlyConstraintsThatIntersectWith(final Set elems) { final UnionFind newUf = mUnionFind.getElementComparator() != null ? new UnionFind<>(mUnionFind.getElementComparator()) : new UnionFind<>(); for (final E elem : elems) { if (newUf.find(elem) != null) { // already constructed current elem's equivalence class continue; } if (mUnionFind.find(elem) == null) { // the current elem has never been added to this TVER; don't add it to the new TVER either continue; } final ImmutableSet elemEqc = mUnionFind.getEquivalenceClassMembers(elem); // retain representatives because otherwise we have to do extra work for literal set constraints newUf.addEquivalenceClass(elemEqc, mUnionFind.find(elem)); // newUf.addEquivalenceClass(elemEqc); } final HashRelation newDisequalities = new HashRelation<>(); for (final Entry deq : mDisequalities.getSetOfPairs()) { // if (elems.contains(deq.getKey()) || elems.contains(deq.getValue())) { if (DataStructureUtils.getSomeCommonElement(getEquivalenceClass(deq.getKey()), elems).isPresent() || DataStructureUtils.getSomeCommonElement(getEquivalenceClass(deq.getValue()), elems).isPresent()) { newDisequalities.addPair(newUf.findAndConstructEquivalenceClassIfNeeded(deq.getKey()), newUf.findAndConstructEquivalenceClassIfNeeded(deq.getValue())); } } return new ThreeValuedEquivalenceRelation<>(newUf, newDisequalities); } /** * Constructs a new {@link ThreeValuedEquivalenceRelation} that is similar to * this but restricted to contraints where both elements occur in the set * elems. */ public ThreeValuedEquivalenceRelation projectTo(final Set elems) { final UnionFind newUf = mUnionFind.getElementComparator() != null ? new UnionFind<>(mUnionFind.getElementComparator()) : new UnionFind<>(); for (final E elem : elems) { if (newUf.find(elem) != null) { // already constructed current elem's equivalence class continue; } if (mUnionFind.find(elem) == null) { // the current elem has never been added to this TVER; don't add it to the new TVER either continue; } final Set elemEqc = mUnionFind.getEquivalenceClassMembers(elem); newUf.addEquivalenceClass(ImmutableSet.of(DataStructureUtils.intersection(elemEqc, elems))); } final HashRelation newDisequalities = new HashRelation<>(); for (final Entry deq : mDisequalities.getSetOfPairs()) { final Optional lhsRep = DataStructureUtils.getSomeCommonElement(elems, getEquivalenceClass(deq.getKey())); if (lhsRep.isPresent()) { final Optional rhsRep = DataStructureUtils.getSomeCommonElement(elems, getEquivalenceClass(deq.getValue())); if (rhsRep.isPresent()) { newDisequalities.addPair(newUf.find(lhsRep.get()), newUf.find(rhsRep.get())); } } } return new ThreeValuedEquivalenceRelation<>(newUf, newDisequalities); } /** * We call an element constrained iff this TVER puts any non-tautological constraint on it. In particular, the * element e is constrained if both of the following conditions hold *
  • e is the only member of its equivalence class *
  • e does not appear in a disequality * * @param elem * the element to check * @return true iff elem is constrained by this */ public boolean isConstrained(final E elem) { if (mUnionFind.find(elem) == null) { throw new IllegalArgumentException(); } if (getEquivalenceClass(elem).size() > 1) { return true; } if (mDisequalities.getImage(elem).size() > 0) { return true; } for (final Entry en : mDisequalities.getSetOfPairs()) { if (en.getValue().equals(elem)) { return true; } } return false; } public void removeDisequality(final E elem1, final E elem2) { mDisequalities.removePair(elem1, elem2); mDisequalities.removePair(elem2, elem1); } }