/* * Copyright (C) 2017 Alexander Nutz (nutz@informatik.uni-freiburg.de) * Copyright (C) 2017 University of Freiburg * * This file is part of the ULTIMATE AbstractInterpretationV2 plug-in. * * The ULTIMATE AbstractInterpretationV2 plug-in 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 AbstractInterpretationV2 plug-in 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 AbstractInterpretationV2 plug-in. If not, see . * * Additional permission under GNU GPL version 3 section 7: * If you modify the ULTIMATE AbstractInterpretationV2 plug-in, 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 AbstractInterpretationV2 plug-in grant you additional permission * to convey the resulting work. */ package de.uni_freiburg.informatik.ultimate.util.datastructures.congruenceclosure; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; import java.util.function.BinaryOperator; import java.util.function.Function; import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger; import de.uni_freiburg.informatik.ultimate.util.datastructures.DataStructureUtils; import de.uni_freiburg.informatik.ultimate.util.datastructures.EqualityStatus; import de.uni_freiburg.informatik.ultimate.util.datastructures.ThreeValuedEquivalenceRelation; import de.uni_freiburg.informatik.ultimate.util.datastructures.poset.IPartialComparator; import de.uni_freiburg.informatik.ultimate.util.datastructures.poset.PartialOrderCache; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.HashRelation; import de.uni_freiburg.informatik.ultimate.util.datastructures.relation.Pair; import de.uni_freiburg.informatik.ultimate.util.statistics.BenchmarkWithCounters; public class CcManager> { private final IPartialComparator> mCcComparator; private final ILogger mLogger; private final CongruenceClosure mInconsistentCc; private final CongruenceClosure mEmptyFrozenCc; private final PartialOrderCache> mPartialOrderCache; private final boolean mBenchmarkMode; private BenchmarkWithCounters mBenchmark; private final SetConstraintManager mSetConstraintManager; public CcManager(final ILogger logger, final IPartialComparator> ccComparator) { mLogger = logger; mCcComparator = ccComparator; mSetConstraintManager = new SetConstraintManager<>(this); mInconsistentCc = new CongruenceClosure<>(true); mInconsistentCc.freezeAndClose(); mEmptyFrozenCc = new CongruenceClosure<>(this); mEmptyFrozenCc.freezeAndClose(); if (CcSettings.UNIFY_CCS) { mPartialOrderCache = new PartialOrderCache<>(mCcComparator); } else { mPartialOrderCache = null; } mBenchmarkMode = !true; if (mBenchmarkMode) { mBenchmark = new BenchmarkWithCounters(); mBenchmark.registerCountersAndWatches(CcBmNames.getNames()); } else { mBenchmark = null; } } private CongruenceClosure addToPartialOrderCache(final CongruenceClosure cc) { assert mPartialOrderCache != null; // assert cc.isFrozen(); freezeIfNecessary(cc); final CongruenceClosure result = mPartialOrderCache.addElement(cc); assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_1 || (result.isStrongerThanNoCaching(cc) && cc.isStrongerThanNoCaching(result)); /* * TODO: we do not work with/return the representative here because it might have a different expset! (should * make this method void probably) */ return cc; } public CongruenceClosure meet(final CongruenceClosure cc1, final CongruenceClosure cc2, final boolean inplace) { assert !CcSettings.FORBID_INPLACE || !inplace; CongruenceClosure result = meet(cc1, cc2, null, inplace); result = postProcessCcResult(result); return result; } private CongruenceClosure postProcessCcResult(final CongruenceClosure cc) { CongruenceClosure result = cc; if (CcSettings.UNIFY_CCS) { // assert result.isFrozen(); if (cc.isFrozen()) { result = addToPartialOrderCache(cc); } } return result; } /** * * @param cc1 * @param cc2 * @param remInfo * @param inplace * the result is the same object as the first argument, with all constraints from the second argument * added * @return */ public CongruenceClosure meet(final CongruenceClosure cc1, final CongruenceClosure cc2, final IRemovalInfo remInfo, final boolean inplace) { bmStart(CcBmNames.MEET); assert !CcSettings.FORBID_INPLACE || !inplace; if (!inplace) { freezeIfNecessary(cc1); freezeIfNecessary(cc2); } assert inplace != cc1.isFrozen(); if (!CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_1) { assert cc1.sanityCheck(); assert cc2.sanityCheck(); } if (cc1.isTautological() && !inplace) { // assert cc1.isFrozen() && cc2.isFrozen() : "unforeseen case, when does this happen?"; freezeIfNecessary(cc2); bmEnd(CcBmNames.MEET); return cc2; } if (cc2.isTautological()) { bmEnd(CcBmNames.MEET); return cc1; } if (cc1.isInconsistent()) { bmEnd(CcBmNames.MEET); return cc1; } if (cc2.isInconsistent() && !inplace) { bmEnd(CcBmNames.MEET); return getInconsistentCc(); } final CongruenceClosure result; if (remInfo == null) { result = cc1.meetRec(cc2, inplace); } else { result = cc1.meetRec(cc2, remInfo, inplace); } assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || result.sanityCheck(); final CongruenceClosure resultPp = postProcessCcResult(result); bmEnd(CcBmNames.MEET); return resultPp; } private CongruenceClosure merge(final CongruenceClosure cc1, final CongruenceClosure cc2, final boolean modifiable, final BinaryOperator>> literalMergeOperator) { bmStart(CcBmNames.JOIN); /* * Freeze-before-join politics.. -- might not be strictly necessary here, because CongruenceClosure-freeze * triggers no propagations */ freezeIfNecessary(cc1); freezeIfNecessary(cc2); if (cc1.isInconsistent()) { bmEnd(CcBmNames.JOIN); return cc2; } if (cc2.isInconsistent()) { bmEnd(CcBmNames.JOIN); return cc1; } if (cc1.isTautological() || cc2.isTautological()) { bmEnd(CcBmNames.JOIN); return getEmptyCc(modifiable); } final CongruenceClosure result = cc1.merge(cc2, literalMergeOperator); if (!modifiable) { result.freezeAndClose(); } assert modifiable != result.isFrozen(); final CongruenceClosure resultPp = postProcessCcResult(result); bmEnd(CcBmNames.JOIN); return resultPp; } /** * note: join always happens immutable/non-inplace style. Thus before a join everything has to be frozen. The result * is not frozen by default (but guaranteed to be a fresh object). * * (even though not frozen, the result is probably always completely reduced/closed) * * @param cc1 * @param cc2 * @param modifiable * result Cc should be modifiable or frozen? * @return */ public CongruenceClosure join(final CongruenceClosure cc1, final CongruenceClosure cc2, final boolean modifiable) { return merge(cc1, cc2, modifiable, mSetConstraintManager::join); } public CongruenceClosure widen(final CongruenceClosure cc1, final CongruenceClosure cc2, final boolean modifiable) { return merge(cc1, cc2, modifiable, mSetConstraintManager::widen); } /** * The given list is implicitly a disjunction. If one element in the disjunction is stronger than another, we can * drop it. * * TODO: poor man's solution, could be done much nicer with lattice representation.. * * @param unionList * @return */ public Set> filterRedundantCcs(final Set> unionList) { final PartialOrderCache> poc = new PartialOrderCache<>(mCcComparator); final Set> result = filterRedundantCcs(unionList, poc); return result; } public IPartialComparator> getCcComparator() { return mCcComparator; } public Set> filterRedundantCcs(final Set> unionList, final PartialOrderCache> ccPoCache) { bmStart(CcBmNames.FILTERREDUNDANT); if (unionList.isEmpty()) { bmEnd(CcBmNames.FILTERREDUNDANT); return unionList; } final Set> maxReps = ccPoCache.getMaximalRepresentatives(unionList); // some additional processing: if maxReps is {False}, return the empty set assert !maxReps.stream().anyMatch(cc -> cc.isInconsistent()) || maxReps.size() == 1; if (maxReps.iterator().next().isInconsistent()) { bmEnd(CcBmNames.FILTERREDUNDANT); return Collections.emptySet(); } bmEnd(CcBmNames.FILTERREDUNDANT); return maxReps; } public CongruenceClosure reportEquality(final ELEM node1, final ELEM node2, final CongruenceClosure origCc, final boolean inplace) { bmStart(CcBmNames.REPORT_EQUALITY); assert !CcSettings.FORBID_INPLACE || !inplace; if (inplace) { origCc.reportEquality(node1, node2); bmEnd(CcBmNames.REPORT_EQUALITY); return origCc; } else { final CongruenceClosure unfrozen = unfreeze(origCc); unfrozen.reportEquality(node1, node2); unfrozen.freezeAndClose(); final CongruenceClosure resultPp = postProcessCcResult(unfrozen); bmEnd(CcBmNames.REPORT_EQUALITY); return resultPp; } } public CongruenceClosure reportDisequality(final ELEM node1, final ELEM node2, final CongruenceClosure origCc, final boolean inplace) { bmStart(CcBmNames.REPORT_DISEQUALITY); assert !CcSettings.FORBID_INPLACE || !inplace; if (inplace) { origCc.reportDisequality(node1, node2); bmEnd(CcBmNames.REPORT_DISEQUALITY); return origCc; } else { final CongruenceClosure unfrozen = unfreeze(origCc); unfrozen.reportDisequality(node1, node2); unfrozen.freezeAndClose(); assert unfrozen.isInconsistent() || unfrozen.getEqualityStatus(node1, node2) == EqualityStatus.NOT_EQUAL; final CongruenceClosure resultPp = postProcessCcResult(unfrozen); bmEnd(CcBmNames.REPORT_DISEQUALITY); return resultPp; } } public CongruenceClosure reportContainsConstraint(final ELEM elem, final Collection elementSet, final CongruenceClosure origCc, final boolean inplace) { return reportContainsConstraint(elem, Collections.singleton(mSetConstraintManager.buildSetConstraint(elementSet)), origCc, inplace); } public CongruenceClosure reportContainsConstraint(final ELEM elem, final Set> elementSet, final CongruenceClosure origCc, final boolean inplace) { bmStart(CcBmNames.REPORTCONTAINS); assert !CcSettings.FORBID_INPLACE || !inplace; if (inplace) { // final SetConstraintConjunction newSetCc = buildSetConstraintConjunction( // origCc.getLiteralSetConstraints(), elem, // elementSet); // Collections.singleton(SetConstraint.buildSetConstraint(elementSet))); // origCc.reportContainsConstraint(elem, newSetCc.getSetConstraints()); origCc.reportContainsConstraint(elem, elementSet); bmEnd(CcBmNames.REPORTCONTAINS); return origCc; } else { final CongruenceClosure unfrozen = unfreeze(origCc); // final SetConstraintConjunction newSetCc = buildSetConstraintConjunction( // unfrozen.getLiteralSetConstraints(), elem, // elementSet); //// Collections.singleton(SetConstraint.buildSetConstraint(elementSet))); // unfrozen.reportContainsConstraint(elem, newSetCc.getSetConstraints()); unfrozen.reportContainsConstraint(elem, elementSet); unfrozen.freezeAndClose(); final CongruenceClosure resultPp = postProcessCcResult(unfrozen); bmEnd(CcBmNames.REPORTCONTAINS); return resultPp; } } public CongruenceClosure removeSimpleElement(final ELEM elem, final CongruenceClosure origCc, final boolean modifiable) { // freeze-before-.. politics freezeIfNecessary(origCc); final CongruenceClosure result = unfreeze(origCc); RemoveCcElement.removeSimpleElement(result, elem); if (!modifiable) { result.freezeAndClose(); } assert modifiable != result.isFrozen(); final CongruenceClosure resultPp = postProcessCcResult(result); return resultPp; } public CongruenceClosure removeSimpleElementDontIntroduceNewNodes(final ELEM elem, final CongruenceClosure origCc, final boolean modifiable) { // freeze-before-.. politics freezeIfNecessary(origCc); final CongruenceClosure result = unfreeze(origCc); RemoveCcElement.removeSimpleElementDontIntroduceNewNodes(result, elem); if (!modifiable) { result.freezeAndClose(); } assert modifiable != result.isFrozen(); final CongruenceClosure resultPp = postProcessCcResult(result); return resultPp; } public CongruenceClosure unfreeze(final CongruenceClosure origCc) { assert origCc.isFrozen(); return new CongruenceClosure<>(origCc); } private CongruenceClosure unfreeze(final CongruenceClosure cc, final IRemovalInfo remInfo) { assert cc.isFrozen(); return new CongruenceClosure<>(cc, remInfo); } public CongruenceClosure addElement(final CongruenceClosure congruenceClosure, final ELEM elem, final boolean inplace, final boolean omitSanityCheck) { assert !CcSettings.FORBID_INPLACE || !inplace; return addElement(congruenceClosure, elem, congruenceClosure, inplace, omitSanityCheck); } public CongruenceClosure addElement(final CongruenceClosure congruenceClosure, final ELEM elem, final IEqualityReportingTarget newEqualityTarget, final boolean inplace, final boolean omitSanityCheck) { assert !CcSettings.FORBID_INPLACE || !inplace; assert inplace != congruenceClosure.isFrozen(); if (inplace) { congruenceClosure.addElement(elem, newEqualityTarget, omitSanityCheck); return congruenceClosure; } else { final CongruenceClosure unfrozen = unfreeze(congruenceClosure); unfrozen.addElement(elem, newEqualityTarget, omitSanityCheck); unfrozen.freezeAndClose(); final CongruenceClosure resultPp = postProcessCcResult(unfrozen); return resultPp; } } /** * (always works in place) * * @param congruenceClosure * @param elem1 * @param b * @return */ public boolean addElementReportChange(final CongruenceClosure congruenceClosure, final ELEM elem, final boolean omitSanityCheck) { return congruenceClosure.addElement(elem, congruenceClosure, omitSanityCheck); } public boolean isDebugMode() { return true; } public ILogger getLogger() { return mLogger; } public CongruenceClosure getSingleEqualityCc(final ELEM node1, final ELEM node2, final boolean modifiable) { final CongruenceClosure cc = getEmptyCc(modifiable); final CongruenceClosure result = reportEquality(node1, node2, cc, modifiable); final CongruenceClosure resultPp = postProcessCcResult(result); return resultPp; } public CongruenceClosure getSingleDisequalityCc(final ELEM node1, final ELEM node2, final boolean modifiable) { final CongruenceClosure cc = getEmptyCc(modifiable); final CongruenceClosure result = reportDisequality(node1, node2, cc, modifiable); final CongruenceClosure resultPp = postProcessCcResult(result); return resultPp; } public CongruenceClosure getEmptyCc(final boolean modifiable) { if (modifiable) { return new CongruenceClosure<>(this); } else { return mEmptyFrozenCc; } } public CongruenceClosure getInconsistentCc() { return mInconsistentCc; } public CongruenceClosure getCongruenceClosureFromTver(final ThreeValuedEquivalenceRelation tver, // final CCLiteralSetConstraints literalConstraints, final boolean modifiable) { final CongruenceClosure result = new CongruenceClosure<>(this, tver);// , literalConstraints); if (!modifiable) { result.freezeAndClose(); } return result; } public CongruenceClosure getCongruenceClosureFromTver(final ThreeValuedEquivalenceRelation tver, final IRemovalInfo removeElementInfo, final CCLiteralSetConstraints literalConstraints, final boolean modifiable) { final CongruenceClosure result = new CongruenceClosure<>(this, tver, literalConstraints, removeElementInfo); if (!modifiable) { result.freezeAndClose(); } return result; } public CongruenceClosure getCopyWithRemovalInfo(final CongruenceClosure cc, final IRemovalInfo remInfo) { return new CongruenceClosure<>(cc, remInfo); } public CongruenceClosure copyNoRemInfo(final CongruenceClosure cc) { if (cc.isInconsistent()) { // no need for a real copy, as there is no operation that changes an inconsistent cc anyway.. return cc; } final CongruenceClosure result = new CongruenceClosure<>(cc); if (cc.isFrozen()) { result.freezeAndClose(); } // assert result.isFrozen() == cc.isFrozen(); // if (CcSettings.FREEZE_ALL_IN_MANAGER) { // result.freeze(); // } return result; } public CongruenceClosure copyNoRemInfoUnfrozen(final CongruenceClosure cc) { return new CongruenceClosure<>(cc); } public CongruenceClosure transformElements(final CongruenceClosure cc, final Function transformer, final boolean inplace) { assert !CcSettings.FORBID_INPLACE || !inplace; final CongruenceClosure result; if (inplace) { // assert false : "inplace does not seem to work, currently (problem with representatives...)"; cc.transformElementsAndFunctions(transformer); result = cc; } else { final CongruenceClosure unfrozen = unfreezeIfNecessary(cc); unfrozen.transformElementsAndFunctions(transformer); unfrozen.freezeAndClose(); // TODO: implement a result check here? result = unfrozen; } assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3 || result.sanityCheck(); final CongruenceClosure resultPp = postProcessCcResult(result); return resultPp; } public CongruenceClosure projectToElements(final CongruenceClosure cc, final Set nodesToKeep, final IRemovalInfo remInfo) { bmStart(CcBmNames.PROJECT_TO_ELEMENTS); if (CcSettings.PROJECTTOELEMENTS_INPLACE) { throw new AssertionError( "CongruenceClosure.projectToElements is currently not suited for inplace " + "operation"); } else { /* * the operation might be lossy, but here we treat a CongruenceClosure, so freezing for closing is not * needed */ final CongruenceClosure unfrozen = unfreezeIfNecessary(cc); final CongruenceClosure result = unfrozen.projectToElements(nodesToKeep, remInfo); result.freezeAndClose(); // TODO: implement a result check here? final CongruenceClosure resultPp = postProcessCcResult(result); bmEnd(CcBmNames.PROJECT_TO_ELEMENTS); return resultPp; } } public CongruenceClosure addAllElements(final CongruenceClosure cc, final Collection elemsToAdd, final IRemovalInfo remInfo, final boolean inplace) { bmStart(CcBmNames.ADD_ALL_ELEMENTS); assert !CcSettings.FORBID_INPLACE || !inplace; assert !cc.isInconsistent(); final CongruenceClosure result; if (inplace) { result = cc; } else { freezeIfNecessary(cc); // TODO: is it redundant to add remInfo to the result Cc and give it to addElementRec?? result = unfreeze(cc, remInfo); } for (final ELEM elem : elemsToAdd) { addElement(result, elem, true, true); } if (!inplace) { result.freezeAndClose(); } final CongruenceClosure resultPp = postProcessCcResult(result); bmEnd(CcBmNames.ADD_ALL_ELEMENTS); return resultPp; } public CongruenceClosure unfreezeIfNecessary(final CongruenceClosure cc) { if (cc.isFrozen()) { return unfreeze(cc); } else { return cc; } } public void freezeIfNecessary(final CongruenceClosure cc) { if (!cc.isFrozen()) { cc.freezeAndClose(); } } public CongruenceClosure getCopy(final CongruenceClosure congruenceClosure, final boolean modifiable) { final CongruenceClosure copy = new CongruenceClosure<>(congruenceClosure); /* * remark: if there were any closure operations during a CongruenceClosure.freeze, we would have to trigger them * here. */ if (!modifiable) { copy.freezeAndClose(); } return copy; } public boolean isStrongerThan(final CongruenceClosure cc1, final CongruenceClosure cc2) { if (CcSettings.UNIFY_CCS) { bmStart(CcBmNames.IS_STRONGER_THAN_W_CACHING); final CongruenceClosure cc1Cached = addToPartialOrderCache(cc1); final CongruenceClosure cc2Cached = addToPartialOrderCache(cc2); final boolean result = mPartialOrderCache.isSmallerOrEqual(cc1Cached, cc2Cached); bmEnd(CcBmNames.IS_STRONGER_THAN_W_CACHING); return result; } return isStrongerThanNoCaching(cc1, cc2); } /** * This is the "base" isStrongerThan insofar it is the backend to the cache, and does not use the cache! * * @param cc1 * @param cc2 * @return */ public boolean isStrongerThanNoCaching(final CongruenceClosure cc1, final CongruenceClosure cc2) { bmStart(CcBmNames.IS_STRONGER_THAN_NO_CACHING); if (cc1.isInconsistent()) { bmEnd(CcBmNames.IS_STRONGER_THAN_NO_CACHING); return true; } if (cc2.isInconsistent()) { // we know this != False, and other = False bmEnd(CcBmNames.IS_STRONGER_THAN_NO_CACHING); return false; } if (cc2.isTautological()) { bmEnd(CcBmNames.IS_STRONGER_THAN_NO_CACHING); return true; } if (cc1.isTautological()) { // we know other != True, and this = True bmEnd(CcBmNames.IS_STRONGER_THAN_NO_CACHING); return false; } final Pair, CongruenceClosure> aligned = alignElements(cc1, cc2, CcSettings.ALIGN_INPLACE && !cc1.isFrozen() && !cc2.isFrozen()); final CongruenceClosure thisAligned = aligned.getFirst(); final CongruenceClosure otherAligned = aligned.getSecond(); assert assertElementsAreSuperset(thisAligned, otherAligned); assert assertElementsAreSuperset(otherAligned, thisAligned); final boolean result = checkIsStrongerThan(thisAligned, otherAligned); bmEnd(CcBmNames.IS_STRONGER_THAN_NO_CACHING); return result; } /** * We check for each equivalence representative in "other" if its equivalence class is a subset of the equivalence * class of the representative in "this". * * (going through the representatives in "this" would be unsound because we might not see all relevant equivalence * classes in "other") * * assumes that this and other have the same elements and functions * * Induces a non-strict (antisymmetric) partial ordering of the CongruenceClosure instances. */ private boolean checkIsStrongerThan(final CongruenceClosure thisAligned, final CongruenceClosure otherAligned) { assert !thisAligned.isInconsistent() && !otherAligned.isInconsistent(); assert assertElementsAreSuperset(thisAligned, otherAligned); assert assertElementsAreSuperset(otherAligned, thisAligned); if (!isPartitionStronger(thisAligned.mElementTVER, otherAligned.mElementTVER)) { return false; } /* * We check if each disequality that holds in "other" also holds in "this". */ if (!areDisequalitiesStrongerThan(thisAligned, otherAligned)) { return false; } if (!thisAligned.getLiteralSetConstraints().isStrongerThan(otherAligned.getLiteralSetConstraints())) { return false; } return true; } public boolean isEquivalent(final CongruenceClosure cc1, final CongruenceClosure cc2) { if (cc1.isInconsistent() && cc2.isInconsistent()) { return true; } if (cc1.isTautological() && cc2.isTautological()) { return true; } if (cc2.isInconsistent() || cc1.isInconsistent()) { return false; } if (cc2.isTautological() || cc1.isTautological()) { return false; } final Pair, CongruenceClosure> aligned = alignElements(cc1, cc2, CcSettings.ALIGN_INPLACE && !cc1.isFrozen() && !cc2.isFrozen()); final CongruenceClosure thisAligned = aligned.getFirst(); final CongruenceClosure otherAligned = aligned.getSecond(); return checkIsStrongerThan(thisAligned, otherAligned) && checkIsStrongerThan(otherAligned, thisAligned); } public Pair, CongruenceClosure> alignElements(final CongruenceClosure cc1, final CongruenceClosure cc2, final boolean inplace) { assert !inplace || !cc1.isFrozen(); assert !inplace || !cc2.isFrozen(); if (inplace) { bmStart(CcBmNames.ALIGN_ELEMENTS); // addAllElements(cc1, cc2.getAllElements(), null, true); // addAllElements(cc2, cc1.getAllElements(), null, true); /* * this single call is not enough for aligning because constant arrays may introduce elements when other * elements are added based on equalities in that constraint */ while (!cc1.getAllElements().containsAll(cc2.getAllElements()) || !cc2.getAllElements().containsAll(cc1.getAllElements())) { addAllElements(cc1, cc2.getAllElements(), null, true); addAllElements(cc2, cc1.getAllElements(), null, true); } bmEnd(CcBmNames.ALIGN_ELEMENTS); return new Pair<>(cc1, cc2); } else { // not inplace bmStart(CcBmNames.ALIGN_ELEMENTS); final CongruenceClosure cc1Aligned = copyNoRemInfoUnfrozen(cc1); final CongruenceClosure cc2Aligned = copyNoRemInfoUnfrozen(cc2); // addAllElements(cc1Aligned, cc2Aligned.getAllElements(), null, true); // addAllElements(cc2Aligned, cc1Aligned.getAllElements(), null, true); /* * this single call is not enough for aligning because constant arrays may introduce elements when other * elements are added based on equalities in that constraint */ while (!cc1Aligned.getAllElements().containsAll(cc2Aligned.getAllElements()) || !cc2Aligned.getAllElements().containsAll(cc1Aligned.getAllElements())) { addAllElements(cc1Aligned, cc2Aligned.getAllElements(), null, true); addAllElements(cc2Aligned, cc1Aligned.getAllElements(), null, true); } cc1Aligned.freezeAndClose(); cc2Aligned.freezeAndClose(); bmEnd(CcBmNames.ALIGN_ELEMENTS); return new Pair<>(cc1Aligned, cc2Aligned); } } private static > boolean areDisequalitiesStrongerThan(final CongruenceClosure left, final CongruenceClosure right) { for (final E rep : right.getAllRepresentatives()) { for (final E disequalRep : right.getRepresentativesUnequalTo(rep)) { if (left.getEqualityStatus(rep, disequalRep) != EqualityStatus.NOT_EQUAL) { return false; } } } return true; } private boolean assertElementsAreSuperset(final Set a, final Set b) { final Set difference = DataStructureUtils.difference(b, a); if (!difference.isEmpty()) { assert false; return false; } return true; } /** * check that elements in a are a superset of elements in b * * @param a * @param b * @return */ private boolean assertElementsAreSuperset(final CongruenceClosure a, final CongruenceClosure b) { final Set difference = DataStructureUtils.difference(b.getAllElements(), a.getAllElements()); if (!difference.isEmpty()) { assert false; return false; } return true; } /** * * @param first * @param second * @return true if first is stronger/more constraining than second */ static boolean isPartitionStronger(final ThreeValuedEquivalenceRelation first, final ThreeValuedEquivalenceRelation second) { final Collection representativesFromBoth = new ArrayList<>(first.getAllRepresentatives().size() + second.getAllRepresentatives().size()); representativesFromBoth.addAll(first.getAllRepresentatives()); representativesFromBoth.addAll(second.getAllRepresentatives()); for (final E rep : representativesFromBoth) { final Set eqInOther = second.getEquivalenceClass(rep); final Set eqInThis = first.getEquivalenceClass(rep); if (!eqInThis.containsAll(eqInOther)) { return false; } } return true; } /** * Note this will throw out all singleton constraints assuming the corresponding equalities have been reported * before! * * @param surroundingSetConstraints * @param constrainedElement * @param setConstraintsIn * @return */ public SetConstraintConjunction buildSetConstraintConjunction( final CCLiteralSetConstraints surroundingSetConstraints, final ELEM constrainedElement, final Set> setConstraintsIn) { bmStart(CcBmNames.BUILD_SET_CONSTRAINT_CONJUNCTION); final Set> setConstraintsUpdRepr = mSetConstraintManager .updateOnChangedRepresentative(setConstraintsIn, surroundingSetConstraints.getCongruenceClosure()); // assert setConstraintsUpdRepr == setConstraintsIn : "if this never fails, we can remove that updaterep // operation"; final Set> filtered1 = normalizeSetConstraintConjunction(surroundingSetConstraints, setConstraintsUpdRepr); // throw away all singletons, as they were (must have been!) reported as equalities // throw away all tautological constraints // final Set> filtered2 = filtered1.stream() // .filter(sc -> !sc.isSingleton()) // .filter(sc -> !SetConstraint.isTautological(constrainedElement, sc)) // .collect(Collectors.toSet()); final Set> filtered2 = new HashSet<>(); for (final SetConstraint sc : filtered1) { if (sc.isSingleton()) { continue; } if (SetConstraint.isTautological(constrainedElement, sc)) { continue; } filtered2.add(sc); } assert !filtered2.stream().anyMatch(sc -> sc.isInconsistent()) || filtered2 .size() == 1 : "not correctly normalized: there is a 'false' conjunct, but it is not the only conjunct"; if (filtered2.size() == 1 && filtered2.iterator().next().isInconsistent()) { bmEnd(CcBmNames.BUILD_SET_CONSTRAINT_CONJUNCTION); return new SetConstraintConjunction<>(true); } final SetConstraintConjunction result = new SetConstraintConjunction<>(surroundingSetConstraints, constrainedElement, filtered2); bmEnd(CcBmNames.BUILD_SET_CONSTRAINT_CONJUNCTION); return result; } /** * Apply normalizations, e.g., *
  • expansion of literals if possible according to surroundingSetConstraints * * note that this normalization is not wrt any constrained element! (e.g. the filtering for x in {x} must happen * outside of this method) * * @param surroundingSetConstraints * used to: expand literals, compute meet * @param setConstraintsIn * @return */ public Set> normalizeSetConstraintConjunction( final CCLiteralSetConstraints surroundingSetConstraints, final Collection> setConstraintsIn) { bmStart(CcBmNames.NORMALIZE_SET_CONSTRAINT_CONJUNCTION); if (setConstraintsIn.isEmpty()) { bmEnd(CcBmNames.NORMALIZE_SET_CONSTRAINT_CONJUNCTION); return Collections.emptySet(); } // expand non-literals if possible (in-place on SetConstraints) final Set> expanded = new HashSet<>(setConstraintsIn); { final HashRelation elemToExpansion = surroundingSetConstraints.getElemToExpansion(); for (final SetConstraint sc : setConstraintsIn) { for (final ELEM e : sc.getNonLiterals()) { final Set expansion = elemToExpansion.getImage(e); if (!expansion.isEmpty()) { expanded.add(mSetConstraintManager.expandNonLiteral(sc, e, expansion)); } } } } // //TODO: only instantiate SetConstraintComparator once?? // final PartialOrderCache> poc1 = new PartialOrderCache<>( // mSetConstraintManager.getSetConstraintComparator()); // final Set> filtered1 = poc1.getMaximalRepresentatives(expanded); // // // check for tautology // if (filtered1.isEmpty()) { // bmEnd(CcBmNames.NORMALIZE_SET_CONSTRAINT_CONJUNCTION); // return Collections.emptySet(); // } Set intersectionOfLiteralOnlyConstraints = null; // check for inconsistency for (final SetConstraint sc : expanded) { if (sc.isInconsistent()) { bmEnd(CcBmNames.NORMALIZE_SET_CONSTRAINT_CONJUNCTION); return Collections.singleton(sc); } if (sc.hasOnlyLiterals()) { if (intersectionOfLiteralOnlyConstraints == null) { intersectionOfLiteralOnlyConstraints = new HashSet<>(sc.getLiterals()); } else { intersectionOfLiteralOnlyConstraints.retainAll(sc.getLiterals()); if (intersectionOfLiteralOnlyConstraints.isEmpty()) { bmEnd(CcBmNames.NORMALIZE_SET_CONSTRAINT_CONJUNCTION); return Collections.singleton(sc); } } } // bs -- example: x in {1} /\ x in {0} // if (sc.isSingleton()) { // return Collections.singleton(sc); // } } final Set> literalsMerged; if (intersectionOfLiteralOnlyConstraints != null) { literalsMerged = new HashSet<>(); literalsMerged.add(mSetConstraintManager.buildSetConstraint(intersectionOfLiteralOnlyConstraints, Collections.emptySet())); for (final SetConstraint sc : expanded) { if (sc.hasOnlyLiterals()) { // do nothing -- we are treating those specially } else { final Set litIntersection = DataStructureUtils.intersection(intersectionOfLiteralOnlyConstraints, sc.getLiterals()); literalsMerged.add(mSetConstraintManager.buildSetConstraint(litIntersection, sc.getNonLiterals())); } } } else { literalsMerged = expanded; } // TODO: only instantiate SetConstraintComparator once?? final PartialOrderCache> poc = new PartialOrderCache<>(mSetConstraintManager.getSetConstraintComparator()); final Set> filtered = poc.getMaximalRepresentatives(literalsMerged); assert SetConstraintConjunction.sanityCheck(filtered); bmEnd(CcBmNames.NORMALIZE_SET_CONSTRAINT_CONJUNCTION); return filtered; } public BenchmarkWithCounters getBenchmark() { return mBenchmark; } private void bmStartOverall() { if (!mBenchmarkMode) { return; } mBenchmark.incrementCounter(CcBmNames.OVERALL.name()); mBenchmark.unpauseWatch(CcBmNames.OVERALL.name()); } private void bmEndOverall() { if (!mBenchmarkMode) { return; } mBenchmark.pauseWatch(CcBmNames.OVERALL.name()); } void bmStart(final CcBmNames watch) { if (!mBenchmarkMode) { return; } bmStartOverall(); mBenchmark.incrementCounter(watch.name()); mBenchmark.unpauseWatch(watch.name()); } void bmEnd(final CcBmNames watch) { if (!mBenchmarkMode) { return; } bmEndOverall(); mBenchmark.pauseWatch(watch.name()); } static enum CcBmNames { FILTERREDUNDANT, UNFREEZE, COPY, MEET, JOIN, REMOVE, IS_STRONGER_THAN_NO_CACHING, ADDNODE, REPORTCONTAINS, REPORT_EQUALITY, REPORT_DISEQUALITY, PROJECT_TO_ELEMENTS, ADD_ALL_ELEMENTS, ALIGN_ELEMENTS, OVERALL, IS_STRONGER_THAN_W_CACHING, BUILD_SET_CONSTRAINT_CONJUNCTION, NORMALIZE_SET_CONSTRAINT_CONJUNCTION, GET_EQUALITY_STATUS, MEET_IS_INCONSISTENT; 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; } } public boolean hasPartialOrderCacheBenchmark() { if (mPartialOrderCache == null) { return false; } return mPartialOrderCache.hasBenchmark(); } public BenchmarkWithCounters getPartialOrderCacheBenchmark() { return mPartialOrderCache.getBenchmark(); } public SetConstraintManager getSetConstraintManager() { return mSetConstraintManager; } /** * * @param ccWithBroaderPartition * (broader == stronger == more constraining) * @param ccWithFinerPartition * @return */ public static > HashRelation computeSplitInfo( final CongruenceClosure ccWithBroaderPartition, final CongruenceClosure ccWithFinerPartition) { assert CcManager.isPartitionStronger(ccWithBroaderPartition.mElementTVER, ccWithFinerPartition.mElementTVER) : "assuming this has been checked already"; final HashRelation result = new HashRelation<>(); for (final ELEM finerRep : ccWithFinerPartition.getAllRepresentatives()) { final ELEM broaderRep = ccWithBroaderPartition.getRepresentativeElement(finerRep); result.addPair(broaderRep, finerRep); } return result; } }