/*
* Copyright (C) 2017 Alexander Nutz (nutz@informatik.uni-freiburg.de)
* Copyright (C) 2017 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.poset;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashSet;
import java.util.List;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiFunction;
import java.util.stream.Collectors;
import de.uni_freiburg.informatik.ultimate.util.datastructures.UnionFind;
import de.uni_freiburg.informatik.ultimate.util.datastructures.poset.IPartialComparator.ComparisonResult;
import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.HashRelation;
import de.uni_freiburg.informatik.ultimate.util.statistics.BenchmarkWithCounters;
/**
*
* @author Alexander Nutz (nutz@informatik.uni-freiburg.de)
*
*/
public class PartialOrderCache {
private enum PocBmNames {
ORDER_REQUESTS_MADE, ORDER_REQUESTS_ANSWERED, ELEMENTS_ADDED;
static String[] getNames() {
final String[] result = new String[values().length];
for (int i = 0; i < values().length; i++) {
result[i] = values()[i].name();
}
return result;
}
}
private static final boolean BENCHMARK = true;
private final IPartialComparator mComparator;
private final HashRelation mStrictlySmaller;
private final HashRelation mNotStrictlySmaller;
private final UnionFind mEquivalences;
private final Set mMaximalElements;
private final BenchmarkWithCounters mBenchmark;
public PartialOrderCache(final IPartialComparator comparator) {
mComparator = comparator;
mStrictlySmaller = new HashRelation<>();
mNotStrictlySmaller = new HashRelation<>();
mEquivalences = new UnionFind<>();
mMaximalElements = new HashSet<>();
if (BENCHMARK) {
mBenchmark = new BenchmarkWithCounters();
mBenchmark.registerCountersAndWatches(PocBmNames.getNames());
} else {
mBenchmark = null;
}
}
public E addElement(final E elemToAdd) {
bmStart(PocBmNames.ELEMENTS_ADDED);
E rep = mEquivalences.find(elemToAdd);
if (rep != null) {
// we already know this element, return the representative
bmEnd(PocBmNames.ELEMENTS_ADDED);
return rep;
}
// elemToAdd element is new
rep = mEquivalences.findAndConstructEquivalenceClassIfNeeded(elemToAdd);
assert rep == elemToAdd;
mMaximalElements.add(rep);
for (final E otherRep : new ArrayList<>(mEquivalences.getAllRepresentatives())) {
if (otherRep == rep) {
// nothing to do
continue;
}
bmStart(PocBmNames.ORDER_REQUESTS_MADE);
final ComparisonResult comparisonResult = mComparator.compare(elemToAdd, otherRep);
bmEnd(PocBmNames.ORDER_REQUESTS_MADE);
switch (comparisonResult) {
case EQUAL:
mEquivalences.union(rep, otherRep);
final E newRep = mEquivalences.find(rep);
if (newRep == rep) {
// representative has changed
assert mEquivalences.find(otherRep) == rep;
mMaximalElements.remove(otherRep);
mStrictlySmaller.replaceDomainElement(otherRep, newRep);
mStrictlySmaller.replaceRangeElement(otherRep, newRep);
} else {
// representative is the old one but we have already made some entries into the data structures
mMaximalElements.remove(rep);
mStrictlySmaller.replaceDomainElement(rep, newRep);
mStrictlySmaller.replaceRangeElement(rep, newRep);
}
assert assertInvariants();
bmEnd(PocBmNames.ELEMENTS_ADDED);
return newRep;
case STRICTLY_SMALLER:
addStrictlySmaller(elemToAdd, otherRep);
break;
case STRICTLY_GREATER:
addStrictlySmaller(otherRep, elemToAdd);
break;
case INCOMPARABLE:
mNotStrictlySmaller.addPair(elemToAdd, otherRep);
mNotStrictlySmaller.addPair(otherRep, elemToAdd);
break;
}
}
bmEnd(PocBmNames.ELEMENTS_ADDED);
assert assertInvariants();
return rep;
}
private void addStrictlySmaller(final E smaller, final E greater) {
assert mEquivalences.find(smaller) == smaller;
assert mEquivalences.find(greater) == greater;
final E smallerRep = mEquivalences.find(smaller);
final E greaterRep = mEquivalences.find(greater);
mStrictlySmaller.addPair(smallerRep, greaterRep);
mNotStrictlySmaller.addPair(greaterRep, smallerRep);
assert assertStrictlySmaller(smallerRep, greaterRep);
mMaximalElements.remove(smallerRep);
assert assertInvariants();
}
public boolean isSmallerOrEqual(final E elem1, final E elem2) {
bmStart(PocBmNames.ORDER_REQUESTS_ANSWERED);
if (elem1 == elem2) {
return true;
}
assert assertInvariants();
final E rep1 = addElement(elem1);
final E rep2 = addElement(elem2);
if (rep1 == rep2) {
// elements are equal
bmEnd(PocBmNames.ORDER_REQUESTS_ANSWERED);
return true;
}
// elements are not equal
// We need to test if there is a path through the graph mStrictlySmaller from rep1 to rep2.
final boolean result = isStrictlySmaller(rep1, rep2);
bmEnd(PocBmNames.ORDER_REQUESTS_ANSWERED);
return result;
}
/**
* @return true if rep1 is strictly smaller than rep2
*/
private boolean isStrictlySmaller(final E rep1, final E rep2) {
if (mStrictlySmaller.containsPair(rep1, rep2)) {
return true;
}
if (mNotStrictlySmaller.containsPair(rep1, rep2)) {
return false;
}
final Deque worklist = new ArrayDeque<>();
worklist.add(rep1);
while (!worklist.isEmpty()) {
final E current = worklist.pop();
if (current == rep2 && current != rep1) {
// found a path: update the map (caching the transitive closure information) and return true
mStrictlySmaller.addPair(rep1, rep2);
assert assertStrictlySmaller(rep1, rep2);
assert assertInvariants();
return true;
}
worklist.addAll(mStrictlySmaller.getImage(current));
}
// found no path --> not smaller
mNotStrictlySmaller.addPair(rep1, rep2);
return false;
}
/**
* Get the maximal elements from to the given list (or elements equivalent to those)
*
* @param elements
* @return
*/
public Set getMaximalRepresentatives(final Collection elements) {
final Set reps = new HashSet<>();
for (final E el : elements) {
reps.add(addElement(el));
}
final Set result = new HashSet<>(reps);
for (final E rep1 : reps) {
for (final E rep2 : reps) {
if (isStrictlySmaller(rep1, rep2)) {
result.remove(rep1);
}
}
}
return result;
}
/**
* Get overall maximal elements in the map (modulo being equal/only representatives)
*/
public Set getMaximalRepresentatives() {
return mMaximalElements;
}
public List getTopologicalOrdering() {
return topSortIntern(TopologicalSorter::topologicalOrdering);
}
public List getReverseTopologicalOrdering() {
return topSortIntern(TopologicalSorter::reversedTopologicalOrdering);
}
private List topSortIntern(BiFunction, Collection, Optional>> sortingFunction) {
final TopologicalSorter sorter = new TopologicalSorter<>(this::successor);
return sortingFunction.apply(sorter, mMaximalElements)
.orElseThrow(() -> new AssertionError("Cycle in partial order"));
}
/**
* Return all elements that are strictly smaller than the element.
*/
private Collection successor(final E elem) {
return mEquivalences.getAllElements().stream().filter(a -> isStrictlySmaller(elem, a))
.collect(Collectors.toList());
}
private boolean assertStrictlySmaller(final E elem1, final E elem2) {
// order must be correct
final ComparisonResult compres = mComparator.compare(elem1, elem2);
if (compres != ComparisonResult.STRICTLY_SMALLER) {
assert false;
return false;
}
return true;
}
private boolean assertInvariants() {
// the sets mMinimalElemnts and mMaximalElements may only contain representatives
for (final E e : mMaximalElements) {
final E find = mEquivalences.find(e);
if (e != find) {
assert false;
return false;
}
}
for (final Entry en : mStrictlySmaller) {
// key must be a representative
final E findKey = mEquivalences.find(en.getKey());
if (findKey != en.getKey()) {
assert false;
return false;
}
// value must be a representative
final E findValue = mEquivalences.find(en.getValue());
if (findValue != en.getValue()) {
assert false;
return false;
}
}
return true;
}
public boolean hasElement(final E elem) {
return mEquivalences.find(elem) != null;
}
public boolean hasBenchmark() {
return BENCHMARK;
}
public BenchmarkWithCounters getBenchmark() {
if (!hasBenchmark()) {
throw new IllegalStateException();
}
return mBenchmark;
}
private void bmStart(final PocBmNames watch) {
if (!BENCHMARK) {
return;
}
mBenchmark.incrementCounter(watch.name());
mBenchmark.unpauseWatch(watch.name());
}
private void bmEnd(final PocBmNames watch) {
if (!BENCHMARK) {
return;
}
mBenchmark.pauseWatch(watch.name());
}
}