/*
* 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.congruenceclosure;
import java.util.ArrayDeque;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BiPredicate;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.stream.Collectors;
import de.uni_freiburg.informatik.ultimate.core.model.services.ILogger;
import de.uni_freiburg.informatik.ultimate.util.datastructures.CrossProducts;
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.UnionFind;
import de.uni_freiburg.informatik.ultimate.util.datastructures.congruenceclosure.CcManager.CcBmNames;
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.datastructures.relation.Triple;
/**
* Implementation of the congruence closure algorithm and data structure. Builds upon ThreeValuedEquivalenceRelation,
* and also uses a three valued logic (equal, not_equal, unknown).
*
* Provides operations for adding equality and disequality constraints both on elements and functions. Automatically
* closes under the congruence axioms with respect to all the known elements. Can propagate equalities and
* disequalities.
*
* Requires the ELEM type to implement the ICongruenceClosureElement interface. It is recommended to use a factory for
* constructing ELEM objects that extends AbstractCCElementFactory.
*
* @author Alexander Nutz (nutz@informatik.uni-freiburg.de)
*
* @param
* The element type. Elements correspond to the "nodes" or terms in standard congruence closure terminology.
* Elements can be constants or function applications.
*/
public class CongruenceClosure>
implements IEqualityReportingTarget, IElementRemovalTarget {
protected final ThreeValuedEquivalenceRelation mElementTVER;
private final CcAuxData mAuxData;
protected final CongruenceClosure.FuncAppTreeAuxData mFaAuxData;
protected final Set mAllLiterals;
protected boolean mIsFrozen = false;
boolean mConstructorInitializationPhase = false;
/**
* Store which element we are currently in the process of removing (a remove can trigger deep recursive calls, and
* some need to know this. Also sanity checks may be done more precisely when we know this)
*/
protected IRemovalInfo mElementCurrentlyBeingRemoved;
private IRemovalInfo mExternalRemovalInfo;
private final CcManager mManager;
CCLiteralSetConstraints mLiteralSetConstraints;
/**
* Constructs CongruenceClosure instance without any equalities or disequalities.
*
* @param manager
*
* @param logger
* a logger, can be null (isDebug will return false, then)
*/
CongruenceClosure(final CcManager manager) {
mElementTVER = new ThreeValuedEquivalenceRelation<>(CongruenceClosure::literalComparator);
mAuxData = new CcAuxData(this);
mFaAuxData = new FuncAppTreeAuxData();
mAllLiterals = new HashSet<>();
mManager = manager;
mLiteralSetConstraints = new CCLiteralSetConstraints<>(manager, this);
}
/**
* Constructs CongruenceClosure instance that is in an inconsistent state from the beginning.
*
* @param isInconsistent
* dummy parameter separating this constructor from the one for the empty CongruenceClosure; must always
* be "true".
*/
CongruenceClosure(final boolean isInconsistent) {
if (!isInconsistent) {
throw new AssertionError("use other constructor");
}
mElementTVER = null;
mAuxData = null;
mFaAuxData = null;
mAllLiterals = null;
mManager = null;
mLiteralSetConstraints = null;
}
/**
*
* @param logger
* a logger, can be null (isDebug will return false, then)
* @param newElemPartition
*/
CongruenceClosure(final CcManager manager, final ThreeValuedEquivalenceRelation newElemPartition) {
mManager = manager;
mElementTVER = newElemPartition;
mAuxData = new CcAuxData<>(this);
mFaAuxData = new FuncAppTreeAuxData();
mAllLiterals = new HashSet<>();
mLiteralSetConstraints = new CCLiteralSetConstraints<>(mManager, this);
mConstructorInitializationPhase = true;
// initialize the helper mappings according to mElementTVER
for (final ELEM elem : new HashSet<>(mElementTVER.getAllElements())) {
registerNewElement(elem, this);
}
mConstructorInitializationPhase = false;
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3 || sanityCheck();
}
/**
* (called from projectToElements)
*
* @param logger
* a logger, can be null (isDebug will return false, then)
* @param newElemPartition
* @param remInfo
*/
CongruenceClosure(final CcManager manager, final ThreeValuedEquivalenceRelation newElemPartition,
final CCLiteralSetConstraints literalConstraints, final IRemovalInfo remInfo) {
/*
* Note: the following two assertions do not need to hold because this may be called during element removal
* preparations
*/
// assert assertNoElementsFromRemInfoInTver(newElemPartition, remInfo);
// assert assertNoElementsFromRemInfoInLitSetConstraints(literalConstraints, remInfo);
assert newElemPartition.getAllElements()
.containsAll(literalConstraints.getAllElementsMentionedInASetConstraint());
mElementTVER = newElemPartition;
mAuxData = new CcAuxData<>(this);
mFaAuxData = new FuncAppTreeAuxData();
mAllLiterals = new HashSet<>();
mManager = manager;
mLiteralSetConstraints = Objects.requireNonNull(literalConstraints);
mLiteralSetConstraints.setCongruenceClosure(this);
mConstructorInitializationPhase = true;
// initialize the helper mappings according to mElementTVER
for (final ELEM elem : new HashSet<>(mElementTVER.getAllElements())) {
registerNewElement(elem, this, remInfo);
}
mConstructorInitializationPhase = false;
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3 || sanityCheck(remInfo);
}
/**
* copy constructor
*
* @param original
* @param externalRemovalInfo
*/
CongruenceClosure(final CongruenceClosure original, final IRemovalInfo externalRemovalInfo) {
if (original.isInconsistent()) {
throw new IllegalArgumentException("use other constructor!");
}
mElementTVER = new ThreeValuedEquivalenceRelation<>(original.mElementTVER);
mAuxData = new CcAuxData<>(this, original.getAuxData());
mFaAuxData = new FuncAppTreeAuxData(original.mFaAuxData);
mAllLiterals = new HashSet<>(original.mAllLiterals);
mExternalRemovalInfo = externalRemovalInfo;
mManager = original.mManager;
mLiteralSetConstraints =
new CCLiteralSetConstraints<>(original.mManager, this, original.getLiteralSetConstraints());
// can be violated during remove (?)
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3 || sanityCheck(externalRemovalInfo);
}
/**
* Copy constructor.
*
* @param original
* the CC to copy
*/
CongruenceClosure(final CongruenceClosure original) {
this(original, null);
}
static > Integer literalComparator(final ELEM e1, final ELEM e2) {
if (e1.isLiteral() && !e2.isLiteral()) {
return -1;
}
if (e2.isLiteral() && !e1.isLiteral()) {
return 1;
}
return 0;
}
/**
* Sets the flag for isFrozen() to true. (Propagations in CongruenceClosure are done immediately so no closure needs
* to be executed here.)
*/
public void freezeAndClose() {
assert !mIsFrozen;
mIsFrozen = true;
}
public boolean isFrozen() {
return mIsFrozen;
}
boolean reportEquality(final ELEM elem1, final ELEM elem2) {
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || sanityCheck();
final boolean result = reportEqualityRec(elem1, elem2);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || sanityCheck();
return result;
}
@Override
public boolean reportEqualityRec(final ELEM elem1, final ELEM elem2) {
// assert !elem1.isLiteral() || !elem2.isLiteral() || elem1.equals(elem2);
// assert !hasElement(elem1) || !getRepresentativeElement(elem1).isLiteral()
// || !hasElement(elem2) || !getRepresentativeElement(elem2).isLiteral()
// || getRepresentativeElement(elem1).equals(getRepresentativeElement(elem2));
assert !mIsFrozen;
if (isInconsistent()) {
throw new IllegalStateException();
}
boolean freshElem = false;
freshElem |= mManager.addElementReportChange(this, elem1, true);
freshElem |= mManager.addElementReportChange(this, elem2, true);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || assertAtMostOneLiteralPerEquivalenceClass();
if (getEqualityStatus(elem1, elem2) == EqualityStatus.EQUAL) {
// nothing to do
return freshElem;
}
if (getEqualityStatus(elem1, elem2) == EqualityStatus.NOT_EQUAL) {
// report it to tver so that it is in an inconsistent state
mElementTVER.reportEquality(elem1, elem2);
// not so nice, but needed for literals where TVER does not know they are unequal otherwise
if (!mElementTVER.isInconsistent()) {
mElementTVER.reportDisequality(elem1, elem2);
}
assert mElementTVER.isInconsistent();
return true;
}
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || assertAtMostOneLiteralPerEquivalenceClass();
final Pair, HashRelation> propInfo =
doMergeAndComputePropagations(elem1, elem2);
if (propInfo == null) {
// this became inconsistent through the merge
return true;
}
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || assertAtMostOneLiteralPerEquivalenceClass();
doFwccAndBwccPropagationsFromMerge(propInfo, this);
// assert sanityCheck();
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || assertAtMostOneLiteralPerEquivalenceClass();
return true;
}
public static > void doFwccAndBwccPropagationsFromMerge(
final Pair, HashRelation> propInfo,
final IEqualityReportingTarget icc) {
final HashRelation equalitiesToPropagate = propInfo.getFirst();
final HashRelation disequalitiesToPropagate = propInfo.getSecond();
/*
* (fwcc)
*/
for (final Entry congruentParents : equalitiesToPropagate) {
if (icc.isInconsistent(false)) {
return;
}
icc.reportEqualityRec(congruentParents.getKey(), congruentParents.getValue());
}
/*
* (bwcc1), (bwcc2) (-- they're only separate cases during reportDisequality)
*/
for (final Entry unequalNeighborIndices : disequalitiesToPropagate) {
if (icc.isInconsistent(false)) {
return;
}
icc.reportDisequalityRec(unequalNeighborIndices.getKey(), unequalNeighborIndices.getValue());
}
}
/**
* Merge the equivalence classes of the given elements and report all equality and disequality propagations that
* directly follow from that merge (according to fwcc and bwcc rules).
*
* @param elem1
* @param elem2
* @return
*/
public Pair, HashRelation> doMergeAndComputePropagations(final ELEM elem1,
final ELEM elem2) {
final ELEM e1OldRep = mElementTVER.getRepresentative(elem1);
final ELEM e2OldRep = mElementTVER.getRepresentative(elem2);
{
constantAndMixFunctionTreatmentOnAddEquality(e1OldRep, e2OldRep, mElementTVER.getEquivalenceClass(elem1),
mElementTVER.getEquivalenceClass(elem2), getAuxData(),
e -> mManager.addElement(this, e, true, true), this);
}
/*
* These sets are used for bwcc propagations, there the special case for the disequalities introduced through
* transitivity. Should save some useless propagations, and avoid some weirdness in debugging..
*/
final Set oldUnequalRepsForElem1 = getRepresentativesUnequalTo(e1OldRep);
final Set oldUnequalRepsForElem2 = getRepresentativesUnequalTo(e2OldRep);
mElementTVER.reportEquality(elem1, elem2);
if (mElementTVER.isInconsistent()) {
return null;
}
/*
* It can happen we had a disequality between an element and a literal that becomes a disequality between two
* literals through the merge. Example: before: {{x} {1} {3}}, x != 3 merge x, 1, new representative 1 --> {{x,
* 1}, {3}}, 1 != 3 We have to filter this out because we leave disequalities between literals implicit.
*/
if (e1OldRep.isLiteral() || e2OldRep.isLiteral()) {
final ELEM newRep = getRepresentativeElement(elem1);
assert newRep.isLiteral() : "if one element of an equivalence class is a literal, then it must be the "
+ "representative";
for (final ELEM unequalToMerged : mElementTVER.getRepresentativesUnequalTo(newRep)) {
if (unequalToMerged.isLiteral()) {
mElementTVER.removeDisequality(newRep, unequalToMerged);
}
}
}
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || assertNoExplicitLiteralDisequalities();
final Pair, HashRelation> propInfo =
new Pair<>(new HashRelation<>(), new HashRelation<>());
// literal constraint treatment
{
final HashRelation eqToProp =
mLiteralSetConstraints.reportEquality(e1OldRep, e2OldRep, mElementTVER.getRepresentative(elem1));
if (eqToProp != null) {
propInfo.getFirst().addAll(eqToProp);
}
if (mLiteralSetConstraints.isInconsistent()) {
return null;
}
}
final Pair, HashRelation> mergePropInfo =
getAuxData().updateAndGetPropagationsOnMerge(elem1, elem2, e1OldRep, e2OldRep, oldUnequalRepsForElem1,
oldUnequalRepsForElem2);
propInfo.getFirst().addAll(mergePropInfo.getFirst());
propInfo.getSecond().addAll(mergePropInfo.getSecond());
return propInfo;
}
public Set getRepresentativesUnequalTo(final ELEM rep) {
assert isRepresentative(rep);
final Set result = new HashSet<>(mElementTVER.getRepresentativesUnequalTo(rep));
/*
* literals are distinct from all other literals
*/
if (rep.isLiteral()) {
for (final ELEM lit : mAllLiterals) {
// don't track disequalities between different sorts -- they are always implicit
if (lit.hasSameTypeAs(rep) && !lit.equals(rep)) {
result.add(lit);
}
}
}
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3 || result.stream().allMatch(el -> el
.hasSameTypeAs(rep)) : "don't track disequalities between different sorts -- they are always implicit";
return result;
}
boolean reportDisequality(final ELEM elem1, final ELEM elem2) {
final boolean result = reportDisequalityRec(elem1, elem2);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || sanityCheck();
return result;
}
@Override
public boolean reportDisequalityRec(final ELEM elem1, final ELEM elem2) {
assert !mIsFrozen;
assert elem1.hasSameTypeAs(elem2);
if (isInconsistent()) {
throw new IllegalStateException();
}
boolean freshElem = false;
freshElem |= mManager.addElementReportChange(this, elem1, true);
freshElem |= mManager.addElementReportChange(this, elem2, true);
if (getEqualityStatus(elem1, elem2) == EqualityStatus.NOT_EQUAL) {
// No need to report existing equalities
return freshElem;
}
mElementTVER.reportDisequality(elem1, elem2);
if (mElementTVER.isInconsistent()) {
return true;
}
// Add literals equalities, if not both representatives are literals
if (!getRepresentativeElement(elem1).isLiteral() || !getRepresentativeElement(elem2).isLiteral()) {
mLiteralSetConstraints.reportDisequality(elem1, elem2);
if (mLiteralSetConstraints.isInconsistent()) {
return true;
}
}
final HashRelation propDeqs = getAuxData().getPropagationsOnReportDisequality(elem1, elem2);
for (final Entry deq : propDeqs) {
reportDisequalityRec(deq.getKey(), deq.getValue());
}
return true;
}
@Override
public boolean addElement(final ELEM elem, final boolean omitSanityCheck) {
return addElement(elem, this, omitSanityCheck);
}
/**
*
* Note: addElement makes calls to other non-trivial CongruenceClosure-manipulating methods addElement (recursively)
* and reportEquality. We sometimes want to call these methods on an ICongruenceClosure-object that is different
* from "this". (current only example: WeqCongruenceClosure.addElement which makes an addElement-call on the
* CongruenceClosure inside the WeqCc.) We call this other ICc the newEqualityTarget
*
* @param elem
* @param newEqualityTarget
* @param omitSanityCheck
* @return
*/
public boolean addElement(final ELEM elem, final IEqualityReportingTarget newEqualityTarget,
final boolean omitSanityCheck) {
final boolean result = addElementRec(elem, newEqualityTarget, null);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || omitSanityCheck || sanityCheck();
return result;
}
private boolean addElementRec(final ELEM elem, final IEqualityReportingTarget newEqualityTarget,
final IRemovalInfo remInfo) {
assert !mIsFrozen;
final boolean newlyAdded = mElementTVER.addElement(elem);
if (newlyAdded) {
registerNewElement(elem, newEqualityTarget, remInfo);
}
// assert sanityCheckOnlyCc();
return newlyAdded;
}
/**
* Updates the helper mappings for ccpars, ccchildren, and function applications. When a new element is added.
*
* Assumes that the element has been added to mElementTVER by addElement(elem), but was not present before that call
* to addElement(..).
*
* @param elem
*/
private void registerNewElement(final ELEM elem, final IEqualityReportingTarget newEqualityTarget) {
registerNewElement(elem, newEqualityTarget, null);
}
private void registerNewElement(final ELEM elem, final IEqualityReportingTarget newEqualityTarget,
final IRemovalInfo remInfo) {
if (elem.isLiteral()) {
mAllLiterals.add(elem);
}
if (elem.isDependentNonFunctionApplication()) {
for (final ELEM supp : elem.getSupportingNodes()) {
mManager.addElement(this, supp, newEqualityTarget, true, true);
mFaAuxData.addSupportingNode(supp, elem);
}
}
if (!elem.isFunctionApplication()) {
// nothing to do
assert mElementTVER.getRepresentative(elem) != null : "this method assumes that elem has been added "
+ "already";
return;
}
if (remInfo == null) {
// "fast track"
mFaAuxData.addAfParent(elem.getAppliedFunction(), elem);
mFaAuxData.addArgParent(elem.getArgument(), elem);
} else {
if (!remInfo.getAlreadyRemovedElements().contains(elem.getAppliedFunction())) {
mFaAuxData.addAfParent(elem.getAppliedFunction(), elem);
}
if (!remInfo.getAlreadyRemovedElements().contains(elem.getArgument())) {
mFaAuxData.addArgParent(elem.getArgument(), elem);
}
}
final HashRelation equalitiesToPropagate = getAuxData().registerNewElement(elem);
// add children elements
if (remInfo == null) {
mManager.addElement(this, elem.getAppliedFunction(), newEqualityTarget, true, true);
mManager.addElement(this, elem.getArgument(), newEqualityTarget, true, true);
} else {
if (!remInfo.getAlreadyRemovedElements().contains(elem.getAppliedFunction())) {
mManager.addElement(this, elem.getAppliedFunction(), newEqualityTarget, true, true);
}
if (!remInfo.getAlreadyRemovedElements().contains(elem.getArgument())) {
mManager.addElement(this, elem.getArgument(), newEqualityTarget, true, true);
}
}
{
constantFunctionTreatmentOnAddElement(elem,
// (e1, e2) -> newEqualityTarget.reportEqualityRec(e1, e2),
// e -> mManager.addElement(this, e, newEqualityTarget, true, true),
mElementTVER.getEquivalenceClass(elem.getAppliedFunction()), newEqualityTarget);
mixFunctionTreatmentOnAddElement(elem, (e, set) -> newEqualityTarget.reportContainsConstraint(e, set),
e -> mManager.addElement(this, e, newEqualityTarget, true, true),
mElementTVER.getEquivalenceClass(elem.getAppliedFunction()));
}
if (remInfo == null) {
for (final Entry eq : equalitiesToPropagate.getSetOfPairs()) {
newEqualityTarget.reportEqualityRec(eq.getKey(), eq.getValue());
// this seems nicer but does not work with the current CcManager
// mManager.reportEquality(eq.getKey(), eq.getValue(), newEqualityTarget, true);
if (isInconsistent()) {
// propagated equality made this Cc inconsistent (break or return here?)
break;
}
}
} else {
// do nothing in this case, right?..
}
}
/**
*
* @param elem
* elem that is a function application
* @param reportEquality
* @param addElement
* @param weakOrStrongEquivalenceClassOfAppliedFunction
* set of elements that are equal or weakly equal to the applied function of elem
*/
public static > void constantFunctionTreatmentOnAddElement(
final ELEM elem,
// final BiConsumer reportEquality, final Consumer addElement,
final Set weakOrStrongEquivalenceClassOfAppliedFunction,
final IEqualityReportingTarget newEqualityTarget) {
/*
* treatment for constant functions: if we are adding an element of the form f(x), where f is a constant
* function, and v is f's constant value then we add the equality "f(x) = v" if we are adding an element
* the form f(x), where f ~ g and g is a constant function, then we add the element g(x)
*/
if (elem.getAppliedFunction().isConstantFunction() && !newEqualityTarget.isInconsistent(false)) {
// reportEquality.accept(elem, elem.getAppliedFunction().getConstantFunctionValue());
newEqualityTarget.reportEqualityRec(elem, elem.getAppliedFunction().getConstantFunctionValue());
}
for (final ELEM equivalentFunction : weakOrStrongEquivalenceClassOfAppliedFunction) {
if (newEqualityTarget.isInconsistent(false)) {
return;
}
if (equivalentFunction == elem.getAppliedFunction()) {
continue;
}
if (equivalentFunction.isConstantFunction()) {
// add element g(x)
// addElement.accept(elem.replaceAppliedFunction(equivalentFunction));
newEqualityTarget.addElement(elem.replaceAppliedFunction(equivalentFunction), false);
}
}
}
/**
*
* @param elem
* elem that is a function application
* @param reportContainsConstraint
* @param addElement
* @param weakOrStrongEquivalenceClassOfAppliedFunction
* set of elements that are equal or weakly equal to the applied function of elem
*/
public static > void mixFunctionTreatmentOnAddElement(final ELEM elem,
final BiConsumer> reportContainsConstraint, final Consumer addElement,
final Set weakOrStrongEquivalenceClassOfAppliedFunction) {
if (!CcSettings.SUPPORT_MIX_FUNCTION) {
return;
}
/*
* treatment for constant functions: if we are adding an element of the form m(x), where m is a mix
* function, and a and b are m's mix functions then we add the literal set constraint "m(x) in {a(x), b(x)}"
* if we are adding an element the form f(x), where f ~ g and g is a constant function, then we add the
* element g(x)
*/
if (elem.getAppliedFunction().isMixFunction()) {
final ELEM mixArray1 = elem.getAppliedFunction().getMixFunction1();
final ELEM mixArray2 = elem.getAppliedFunction().getMixFunction2();
final HashSet set = new HashSet<>();
set.add(elem.replaceAppliedFunction(mixArray1));
set.add(elem.replaceAppliedFunction(mixArray2));
reportContainsConstraint.accept(elem, set);
}
for (final ELEM equivalentFunction : weakOrStrongEquivalenceClassOfAppliedFunction) {
if (equivalentFunction == elem.getAppliedFunction()) {
continue;
}
if (equivalentFunction.isMixFunction()) {
// add element g(x)
addElement.accept(elem.replaceAppliedFunction(equivalentFunction));
}
}
}
/**
* Constant or mix arrays can trigger addNode calls when an (possibly weak) equality is added. This method checks
* for nodes that trigger instantiation of the axiom for constant arrays and mix arrays.
*
* This method is triggered on the addition of both weak and strong equalities.
*
* background:
*
We maintain the following invariant: let f and g be (strongly or weakly) equal and let g be a constant or mix
* function. Then, for every function application f(x) that is in our set of tracked elements, we also track g(x).
* For this method, this means, we have to go through all constant or mix function equivalent to elem1 and for
* each go through the ccpar's of f to add the corresponding nodes and vice versa.
*
* @param elemRep1
* (must be a representative because we will query its afCcPars from auxdata)
* @param elemRep2
* "
* @param elem1EquivalenceClass
* set of elements that are equal or weakly equal to elemRep1
* @param elem2EquivalenceClass
* set of elements that are equal or weakly equal to elemRep2
* @param congruenceClosure
* the congruence closure that is modified by this method (which elements are added to)
*/
public static > void constantAndMixFunctionTreatmentOnAddEquality(
final ELEM elemRep1, final ELEM elemRep2, final Set elem1EquivalenceClass,
final Set elem2EquivalenceClass, final CcAuxData auxData, final Consumer addElement,
final IEqualityReportingTarget congruenceClosure) {
for (final ELEM equivalentFunction1 : new HashSet<>(elem1EquivalenceClass)) {
if ((CcSettings.SUPPORT_MIX_FUNCTION && equivalentFunction1.isMixFunction())
|| (CcSettings.SUPPORT_CONSTANT_FUNCTIONS && equivalentFunction1.isConstantFunction())) {
// ccpar is f(x), equivalentFunction1 is g
for (final ELEM ccpar : auxData.getAfCcPars(elemRep2)) {
if (congruenceClosure.isInconsistent(false)) {
return;
}
assert !equivalentFunction1.equals(ccpar.getAppliedFunction());
addElement.accept(ccpar.replaceAppliedFunction(equivalentFunction1));
}
}
}
for (final ELEM equivalentFunction2 : new HashSet<>(elem2EquivalenceClass)) {
if ((CcSettings.SUPPORT_MIX_FUNCTION && equivalentFunction2.isMixFunction())
|| (CcSettings.SUPPORT_CONSTANT_FUNCTIONS && equivalentFunction2.isConstantFunction())) {
// ccpar is f(x), equivalentFunction2 is g
for (final ELEM ccpar : auxData.getAfCcPars(elemRep1)) {
if (congruenceClosure.isInconsistent(false)) {
return;
}
assert !equivalentFunction2.equals(ccpar.getAppliedFunction());
addElement.accept(ccpar.replaceAppliedFunction(equivalentFunction2));
}
}
}
}
public ELEM getRepresentativeElement(final ELEM elem) {
final ELEM result = mElementTVER.getRepresentative(elem);
if (result == null) {
throw new IllegalArgumentException(
"Use this method only for elements that you know have been added already");
}
return result;
}
public Set collectElementsToRemove(final ELEM elem) {
final Set result = new HashSet<>();
// collect transitive parents of dependent elements
result.addAll(mFaAuxData.getDependentsOf(elem));
for (final ELEM dep : mFaAuxData.getDependentsOf(elem)) {
result.addAll(collectTransitiveParents(dep));
}
result.addAll(collectTransitiveParents(elem));
return result;
}
private Set collectTransitiveParents(final ELEM elem) {
final Set result = new HashSet<>();
final Deque worklist = new ArrayDeque<>();
worklist.add(elem);
while (!worklist.isEmpty()) {
final ELEM current = worklist.pop();
result.add(current);
worklist.addAll(mFaAuxData.getAfParents(current));
worklist.addAll(mFaAuxData.getArgParents(current));
}
return result;
}
/**
*
* @param elementsToRemove
* @param nodeToReplacementNode
*
*/
public void removeElements(final Set elementsToRemove, final Map nodeToReplacementNode) {
assert !mIsFrozen;
for (final ELEM etr : elementsToRemove) {
mFaAuxData.removeFromNodeToDependents(etr);
}
// remove from this cc
for (final ELEM etr : elementsToRemove) {
if (!hasElement(etr)) {
continue;
}
updateElementTverAndAuxDataOnRemoveElement(etr, nodeToReplacementNode.get(etr));
}
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_2 || sanityCheck();
}
/**
*
* @param elemToRemove
* @param elemToRemoveIsAppliedFunctionNotArgument
* serves as info which elements are currently being removed -- we don't want to schedule any of these
* for adding
* @param elemToRemoveToReplacement
* this method may schedule elements for adding that can replace elements being removed -- it should
* update this map accordingly
* @return
*/
@Override
public Set getNodesToIntroduceBeforeRemoval(final ELEM elemToRemove, final Set elementsToRemove,
final Map elemToRemoveToReplacement) {
assert elementsToRemove.contains(elemToRemove);
// assert elemToRemoveToReplacement.keySet().equals(mElementCurrentlyBeingRemoved.getRemovedElements());
/*
* say elemToRemove = a[i] elemToRemove does not have an equivalence class member to replace it with
* (assumed by this method) isConstrained(a[i]) (assumed by this method) (case1) a[i] is removed
* because a is removed, and exists b ~ a, then return b[i] (to be added later) (case2) a[i] is removed
* because i is removed, and exists i ~ j, then return a[j] (to be added later)
*/
assert elemToRemove.isFunctionApplication();
/*
* it is tempting to make the following assertion, but not what we want: assume we have {x, y}, then we have a
* replacement for x for all other purposes, but not for the purpose of keeping the "y equals something"
* constraint
*/
// assert getOtherEquivalenceClassMember(elemToRemove, elemToRemoveToReplacement.keySet()) == null;
/*
* case split on which child of elemToRemove is a reason for elemToRemove being scheduled for removal three
* cases: appliedfunction, argument, both
*/
final boolean etrIsRemovedBecauseOfAf = elementsToRemove.contains(elemToRemove.getAppliedFunction());
final boolean etrIsRemovedBecauseOfArg = elementsToRemove.contains(elemToRemove.getArgument());
if (etrIsRemovedBecauseOfAf && etrIsRemovedBecauseOfArg) {
// look for b with a ~ b, and j with i ~ j
final ELEM afReplacement =
getOtherEquivalenceClassMember(elemToRemove.getAppliedFunction(), elementsToRemove);
final ELEM argReplacement = getOtherEquivalenceClassMember(elemToRemove.getArgument(), elementsToRemove);
if (afReplacement != null && argReplacement != null) {
final ELEM afReplaced = elemToRemove.replaceAppliedFunction(afReplacement);
final ELEM afAndArgReplaced = afReplaced.replaceArgument(argReplacement);
assert !mElementCurrentlyBeingRemoved.getRemovedElements().contains(afAndArgReplaced);
elemToRemoveToReplacement.put(elemToRemove, afAndArgReplaced);
if (!hasElement(afAndArgReplaced)) {
assert nodeToAddIsEquivalentToOriginal(afAndArgReplaced, elemToRemove);
return Collections.singleton(afAndArgReplaced);
} else {
return Collections.emptySet();
}
}
} else if (etrIsRemovedBecauseOfAf) {
// look for b with a ~ b
final ELEM afReplacement =
getOtherEquivalenceClassMember(elemToRemove.getAppliedFunction(), elementsToRemove);
if (afReplacement != null) {
final ELEM afReplaced = elemToRemove.replaceAppliedFunction(afReplacement);
assert !mElementCurrentlyBeingRemoved.getRemovedElements().contains(afReplaced);
elemToRemoveToReplacement.put(elemToRemove, afReplaced);
if (!hasElement(afReplaced)) {
assert nodeToAddIsEquivalentToOriginal(afReplaced, elemToRemove);
return Collections.singleton(afReplaced);
} else {
return Collections.emptySet();
}
}
} else {
// look for j with i ~ j
final ELEM argReplacement = getOtherEquivalenceClassMember(elemToRemove.getArgument(), elementsToRemove);
if (argReplacement != null) {
final ELEM argReplaced = elemToRemove.replaceArgument(argReplacement);
assert !mElementCurrentlyBeingRemoved.getRemovedElements().contains(argReplaced);
elemToRemoveToReplacement.put(elemToRemove, argReplaced);
if (!hasElement(argReplaced)) {
assert nodeToAddIsEquivalentToOriginal(argReplaced, elemToRemove);
return Collections.singleton(argReplaced);
} else {
return Collections.emptySet();
}
}
}
return Collections.emptySet();
}
private boolean nodeToAddIsEquivalentToOriginal(final ELEM afAndArgReplaced, final ELEM elemToRemove) {
final CongruenceClosure copy = mManager.copyNoRemInfoUnfrozen(this);
mManager.addElement(copy, afAndArgReplaced, true, false);
if (copy.getEqualityStatus(afAndArgReplaced, elemToRemove) != EqualityStatus.EQUAL) {
assert false;
return false;
}
return true;
}
/**
*
* If elem is alone in its equivalence class, return null. Otherwise return any element from elem's equivalence
* class that is not elem.
*
*
* @param argument
* @param forbiddenSet
* optional, a set whose members we don't want returned, so only look for an elemen in
* equivalenceClassOf(argument) \ forbiddenSet
* @return
*/
public ELEM getOtherEquivalenceClassMember(final ELEM eqmember, final Set forbiddenSet) {
assert hasElement(eqmember);
final Set eqc = mElementTVER.getEquivalenceClass(eqmember);
if (eqc.size() == 1) {
return null;
}
for (final ELEM e : eqc) {
if (!e.equals(eqmember) && (forbiddenSet == null || !forbiddenSet.contains(e))) {
return e;
}
}
return null;
}
private ELEM updateElementTverAndAuxDataOnRemoveElement(final ELEM elem, final ELEM newRepChoice) {
final boolean elemWasRepresentative = mElementTVER.isRepresentative(elem);
final ELEM newRep = mElementTVER.removeElement(elem, newRepChoice);
assert mElementTVER.getRepresentative(newRep) == newRep;
assert !elemWasRepresentative || newRepChoice == null || newRep == newRepChoice;
getAuxData().removeElement(elem, elemWasRepresentative, newRep);
if (elem.isFunctionApplication()) {
mFaAuxData.removeAfParent(elem.getAppliedFunction(), elem);
mFaAuxData.removeArgParent(elem.getArgument(), elem);
}
if (elemWasRepresentative) {
if (newRep == null) {
mLiteralSetConstraints.projectAway(elem);
} else {
mLiteralSetConstraints.replaceRepresentative(elem, newRep);
}
}
return newRep;
}
CongruenceClosure merge(final CongruenceClosure other,
final BinaryOperator>> literalMergeOperator) {
assert !this.isInconsistent() && !other.isInconsistent() && !this.isTautological() && !other.isTautological();
final Pair, CongruenceClosure> aligned =
mManager.alignElements(this, other, CcSettings.ALIGN_INPLACE && !this.isFrozen() && !other.isFrozen());
final CongruenceClosure thisAligned = aligned.getFirst();
final CongruenceClosure otherAligned = aligned.getSecond();
final Triple, HashRelation, HashRelation> joinRes =
thisAligned.mElementTVER.joinPartitions(otherAligned.mElementTVER);
final UnionFind newPartition = joinRes.getFirst();
final HashRelation thisSplitInfo = joinRes.getSecond();
final HashRelation otherSplitInfo = joinRes.getThird();
final HashRelation newDisequalities =
intersectOrUnionDisequalities(thisAligned, otherAligned, newPartition, true);
final ThreeValuedEquivalenceRelation newElemTver =
new ThreeValuedEquivalenceRelation<>(newPartition, newDisequalities);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3
|| CcManager.isPartitionStronger(thisAligned.mElementTVER, newElemTver);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3
|| CcManager.isPartitionStronger(otherAligned.mElementTVER, newElemTver);
final CongruenceClosure newCc = mManager.getCongruenceClosureFromTver(newElemTver, true);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3
|| CcManager.isPartitionStronger(thisAligned.mElementTVER, newCc.mElementTVER);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_3
|| CcManager.isPartitionStronger(otherAligned.mElementTVER, newCc.mElementTVER);
final CCLiteralSetConstraints newLiteralSetConstraints = mLiteralSetConstraints.merge(newCc,
thisSplitInfo, otherSplitInfo, other.mLiteralSetConstraints, literalMergeOperator);
newCc.resetCcLiteralSetConstraints(newLiteralSetConstraints);
return newCc;
}
private void resetCcLiteralSetConstraints(final CCLiteralSetConstraints newLiteralSetConstraints) {
assert newLiteralSetConstraints.getCongruenceClosure() == this;
mLiteralSetConstraints = newLiteralSetConstraints;
}
/**
* Conjoin or disjoin two disequality relations.
*
* @param tver1
* @param tver2
* @param newElemPartition
* @param intersect
* conjoin or disjoin?
* @return
*/
private static > HashRelation intersectOrUnionDisequalities(
final CongruenceClosure tver1, final CongruenceClosure tver2, final UnionFind newElemPartition,
final boolean intersect) {
final HashRelation result = new HashRelation<>();
final BiPredicate filterForCrossProduct =
(e1, e2) -> (e1 != e2 && (!e1.isLiteral() || !e2.isLiteral()) && e1.hasSameTypeAs(e2));
for (final Entry pair : CrossProducts
.binarySelectiveCrossProduct(newElemPartition.getAllRepresentatives(), false, filterForCrossProduct)) {
assert !(pair.getKey().isLiteral() && pair.getValue().isLiteral()) : "disequalities between literals are "
+ "implicit, the selective cross product should have filtered these cases";
final boolean addDisequality;
if (intersect) {
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 CongruenceClosure meetRec(final CongruenceClosure other, final IRemovalInfo remInfo,
final boolean inplace) {
assert !this.isInconsistent();
/*
* if we are meeting in place, we make this inconsistent by adding enough constraints from other (might be
* optimized..)
*/
assert !other.isInconsistent() || inplace;
CongruenceClosure thisAligned = mManager.addAllElements(this, other.getAllElements(), remInfo, inplace);
for (final Entry eq : other.getSupportingElementEqualities().entrySet()) {
if (thisAligned.isInconsistent()) {
if (inplace) {
return thisAligned;
} else {
return mManager.getInconsistentCc();
}
}
thisAligned = mManager.reportEquality(eq.getKey(), eq.getValue(), thisAligned, inplace);
}
for (final Entry deq : other.getElementDisequalities()) {
if (thisAligned.isInconsistent()) {
if (inplace) {
return thisAligned;
} else {
return mManager.getInconsistentCc();
}
}
thisAligned = mManager.reportDisequality(deq.getKey(), deq.getValue(), thisAligned, inplace);
}
for (final Entry> literalConstraint : other.getLiteralSetConstraints()
.getConstraints().entrySet()) {
if (thisAligned.isInconsistent()) {
if (inplace) {
return thisAligned;
} else {
return mManager.getInconsistentCc();
}
}
thisAligned = mManager.reportContainsConstraint(literalConstraint.getKey(),
literalConstraint.getValue().getSetConstraints(), thisAligned, inplace);
}
return thisAligned;
}
/**
* Returns a new CongruenceClosure instance that is the meet of "this" and "other".
*
* @param other
* @return
*/
public CongruenceClosure meetRec(final CongruenceClosure other, final boolean inplace) {
return meetRec(other, null, inplace);
}
public boolean isStrongerThanNoCaching(final CongruenceClosure other) {
if (isInconsistent()) {
// mManager may be null in this case, so catch it here..
return true;
}
return mManager.isStrongerThanNoCaching(this, other);
}
public EqualityStatus getEqualityStatus(final ELEM elem1, final ELEM elem2) {
assert hasElement(elem1) && hasElement(elem2);
assert !isInconsistent() : "catch this outside!";
mManager.bmStart(CcBmNames.GET_EQUALITY_STATUS);
if (!elem1.hasSameTypeAs(elem2)) {
mManager.bmEnd(CcBmNames.GET_EQUALITY_STATUS);
return EqualityStatus.NOT_EQUAL;
}
final ELEM rep1 = getRepresentativeElement(elem1);
final ELEM rep2 = getRepresentativeElement(elem2);
if (rep1.equals(rep2)) {
mManager.bmEnd(CcBmNames.GET_EQUALITY_STATUS);
return EqualityStatus.EQUAL;
}
if (rep1.isLiteral() && rep2.isLiteral()) {
mManager.bmEnd(CcBmNames.GET_EQUALITY_STATUS);
return EqualityStatus.NOT_EQUAL;
}
final Set> litConstraint1 = mLiteralSetConstraints.getConstraint(rep1);
final Set> litConstraint2 = mLiteralSetConstraints.getConstraint(rep2);
// /* if elem1 equals a literal l and litConstraint2 constrains to a literal set disjoint from l: not equal
// * (and vice versa) */
// {
// if (litConstraint2 != null) {
// final Set litSet2 = mManager.getSetConstraintManager().getLiteralSet(litConstraint2);
// if (rep1.isLiteral() && litSet2 != null && !litSet2.contains(rep1)) {
// return EqualityStatus.NOT_EQUAL;
// }
// }
// if (litConstraint1 != null) {
// final Set litSet1 = mManager.getSetConstraintManager().getLiteralSet(litConstraint1);
// if (rep2.isLiteral() && litSet1 != null && !litSet1.contains(rep2)) {
// return EqualityStatus.NOT_EQUAL;
// }
// }
// }
if (mManager.getSetConstraintManager().meetIsInconsistent(getLiteralSetConstraints(), litConstraint1,
litConstraint2)) {
mManager.bmEnd(CcBmNames.GET_EQUALITY_STATUS);
return EqualityStatus.NOT_EQUAL;
}
final EqualityStatus result = mElementTVER.getEqualityStatus(elem1, elem2);
mManager.bmEnd(CcBmNames.GET_EQUALITY_STATUS);
return result;
}
public Set getAllElements() {
return Collections.unmodifiableSet(mElementTVER.getAllElements());
}
public CCLiteralSetConstraints getLiteralSetConstraints() {
return mLiteralSetConstraints;
}
@Override
public boolean isInconsistent() {
return mElementTVER == null || mElementTVER.isInconsistent() || mLiteralSetConstraints.isInconsistent();
}
@Override
public boolean isInconsistent(final boolean close) {
// CongruenceClosure is always closed immediately..
return isInconsistent();
}
private boolean assertNoElementsFromRemInfoInLitSetConstraints(
final CCLiteralSetConstraints literalConstraints, final IRemovalInfo remInfo) {
if (remInfo == null) {
return true;
}
for (final Entry> en : literalConstraints.getConstraints().entrySet()) {
if (dependsOnAny(en.getKey(), remInfo.getRemovedElements())) {
return false;
}
if (dependsOnAny(en.getValue().getConstrainedElement(), remInfo.getRemovedElements())) {
return false;
}
for (final ELEM el : en.getValue().getAllRhsElements()) {
if (dependsOnAny(el, remInfo.getRemovedElements())) {
return false;
}
}
}
return true;
}
private boolean assertNoElementsFromRemInfoInTver(final ThreeValuedEquivalenceRelation newElemPartition,
final IRemovalInfo remInfo) {
if (remInfo == null) {
return true;
}
for (final ELEM elem : newElemPartition.getAllElements()) {
if (dependsOnAny(elem, remInfo.getRemovedElements())) {
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
*/
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;
}
public boolean assertAtMostOneLiteralPerEquivalenceClass() {
if (isInconsistent()) {
return true;
}
for (final Set eqc : mElementTVER.getAllEquivalenceClasses()) {
assert eqc.stream().filter(e -> e.isLiteral()).collect(Collectors.toList()).size() < 2;
}
return true;
}
public boolean assertSimpleElementIsFullyRemoved(final ELEM elem) {
// not ideal as this method is used during the removal, too..
final Set transitiveParents = collectElementsToRemove(elem);
for (final ELEM e : getAllElements()) {
if (transitiveParents.contains(e)) {
assert false;
return false;
}
}
return true;
}
public boolean assertSingleElementIsFullyRemoved(final ELEM elem) {
for (final Entry en : mFaAuxData.getNodeToDependentPairs()) {
if (en.getKey().equals(elem) || en.getValue().equals(elem)) {
assert false;
return false;
}
}
return assertElementIsFullyRemovedOnlyCc(elem);
}
/**
* Checks for any remainig entries of elem, does not look for subterms.
*
* @param elem
* @return
*/
protected boolean assertElementIsFullyRemovedOnlyCc(final ELEM elem) {
if (mElementTVER.getRepresentative(elem) != null) {
assert false;
return false;
}
return true;
}
public boolean assertHasOnlyWeqVarConstraints(final Set allWeqNodes) {
if (isTautological()) {
return true;
}
if (allWeqNodes.isEmpty()) {
return true;
}
final Set elemsAppearingInADisequality = new HashSet<>();
for (final Entry deq : mElementTVER.getDisequalities().getSetOfPairs()) {
elemsAppearingInADisequality.add(deq.getKey());
elemsAppearingInADisequality.add(deq.getValue());
}
for (final Set eqc : mElementTVER.getAllEquivalenceClasses()) {
if (eqc.size() == 1) {// &&
// (!mFaAuxData.getAfParents(eqc.iterator().next()).isEmpty() ||
// !mFaAuxData.getArgParents(eqc.iterator().next()).isEmpty())) {
continue;
}
final Set intersection1 =
eqc.stream().filter(eqcelem -> dependsOnAny(eqcelem, allWeqNodes)).collect(Collectors.toSet());
final Set intersection2 = DataStructureUtils.intersection(eqc, elemsAppearingInADisequality);
if (intersection1.isEmpty() && intersection2.isEmpty()) {
assert false;
return false;
}
}
return true;
}
public boolean assertHasExternalRemInfo() {
return mExternalRemovalInfo != null;
}
@Override
public boolean sanityCheck() {
return sanityCheck(null);
}
public boolean sanityCheck(final IRemovalInfo remInfo) {
return sanityCheckOnlyCc(remInfo);
}
public boolean sanityCheckOnlyCc() {
return sanityCheckOnlyCc(null);
}
/**
* Check for some class invariants.
*
* @return
*/
@SuppressWarnings("unused")
public boolean sanityCheckOnlyCc(final IRemovalInfo remInfo) {
if (mConstructorInitializationPhase) {
return true;
}
if (this.isInconsistent()) {
if (mElementTVER != null) {
// transitory CClosure instance which will later be replaced by the "bottom" variant
if (!mElementTVER.isInconsistent() && !mLiteralSetConstraints.isInconsistent()) {
assert false : "cc reports as inconsistent, but why?..";
return false;
}
}
return true;
}
if (!mElementTVER.sanityCheck()) {
assert false;
return false;
}
if (mElementTVER.isInconsistent()) {
assert false : "Cc is inconsistent but fields are not null";
return false;
}
/*
* check that each element in ccpars is a function application
*/
for (final ELEM elem : getAllRepresentatives()) {
for (final ELEM ccp : this.getAuxData().getAfCcPars(elem)) {
if (!ccp.isFunctionApplication()) {
assert false : "ccpar is not a funcapp";
return false;
}
}
for (final ELEM ccp : this.getAuxData().getArgCcPars(elem)) {
if (!ccp.isFunctionApplication()) {
assert false : "ccpar is not a funcapp";
return false;
}
}
}
/*
* check that for each element that is a function application, its children are present, too However, take
* removalInfo into account.
*/
for (final ELEM elem : getAllElements()) {
if (!elem.isFunctionApplication()) {
continue;
}
if (!hasElement(elem.getAppliedFunction())
&& (remInfo == null || !remInfo.getRemovedElements().contains(elem.getAppliedFunction()))
&& (mElementCurrentlyBeingRemoved == null
|| !mElementCurrentlyBeingRemoved.getRemovedElements().contains(elem.getAppliedFunction()))
&& (mExternalRemovalInfo == null
|| !mExternalRemovalInfo.getRemovedElements().contains(elem.getAppliedFunction()))) {
assert false;
return false;
}
if (!hasElement(elem.getArgument())
&& (remInfo == null || !remInfo.getRemovedElements().contains(elem.getArgument()))
&& (mElementCurrentlyBeingRemoved == null
|| !mElementCurrentlyBeingRemoved.getRemovedElements().contains(elem.getArgument()))
&& (mExternalRemovalInfo == null
|| !mExternalRemovalInfo.getRemovedElements().contains(elem.getArgument()))) {
assert false;
return false;
}
}
/*
* check that for each function application a[i], its representative's ccchild contains the corresponding
* af/arg-pair (a,i)
*/
for (final ELEM elem : getAllElements()) {
if (!elem.isFunctionApplication()) {
continue;
}
final ELEM rep = getRepresentativeElement(elem);
if (!getAuxData().getCcChildren(rep).containsPair(elem.getAppliedFunction(), elem.getArgument())) {
assert false : "ccchild store incomplete";
return false;
}
}
/*
* check that all elements with isLiteral() = true are in mAllLiterals an that every element of mAllLiterals is
* a literal
*/
for (final ELEM elem : getAllElements()) {
if (elem.isLiteral() && !mAllLiterals.contains(elem)) {
assert false : "all literals store incomplete";
return false;
}
}
for (final ELEM lit : mAllLiterals) {
if (!lit.isLiteral()) {
assert false : "non-literal in all literals store";
return false;
}
}
/*
* check for each equivalence class that if there is a literal in the equivalence class, it is the
* representative
*/
for (final Set eqc : mElementTVER.getAllEquivalenceClasses()) {
for (final ELEM elem : eqc) {
if (!elem.isLiteral()) {
continue;
}
// elem is literal
if (!isRepresentative(elem)) {
// elem is a
assert false : "literal is not the representative of its eq class";
return false;
}
}
}
/*
* check that for each element, its parents in funcAppTreeAuxData and ccAuxData agree
*/
for (final ELEM elem : getAllElements()) {
final Set afCcparFromDirectParents = new HashSet<>();
final Set argCcparFromDirectParents = new HashSet<>();
for (final ELEM eqcMember : mElementTVER.getEquivalenceClass(elem)) {
afCcparFromDirectParents.addAll(mFaAuxData.getAfParents(eqcMember));
argCcparFromDirectParents.addAll(mFaAuxData.getArgParents(eqcMember));
}
final ELEM rep = getRepresentativeElement(elem);
if (!afCcparFromDirectParents.equals(getAuxData().getAfCcPars(rep))) {
// d1 and d2 are not used by the program, but nice to have precomputed when the assertion fails
final Set d1 = DataStructureUtils.difference(afCcparFromDirectParents,
new HashSet<>(getAuxData().getAfCcPars(rep)));
final Set d2 = DataStructureUtils.difference(new HashSet<>(getAuxData().getAfCcPars(rep)),
afCcparFromDirectParents);
assert false : "funcAppTreeAuxData and ccAuxData don't agree (Af case)";
return false;
}
if (!argCcparFromDirectParents.equals(getAuxData().getArgCcPars(rep))) {
// d1 and d2 are not used by the program, but nice to have precomputed when the assertion fails
final Set d1 = DataStructureUtils.difference(argCcparFromDirectParents,
new HashSet<>(getAuxData().getArgCcPars(rep)));
final Set d2 = DataStructureUtils.difference(new HashSet<>(getAuxData().getArgCcPars(rep)),
argCcparFromDirectParents);
assert false : "funcAppTreeAuxData and ccAuxData don't agree (Arg case)";
return false;
}
}
/*
* check that all elements that are related are of the same type while same type means here: - for funcApps:
* same number of arguments -
*/
for (final ELEM e1 : getAllElements()) {
for (final ELEM e2 : mElementTVER.getEquivalenceClass(e1)) {
if (!e1.hasSameTypeAs(e2)) {
assert false : "elements of incompatible type are in same eq class";
return false;
}
}
}
for (final Entry deq : mElementTVER.getDisequalities()) {
if (!deq.getKey().hasSameTypeAs(deq.getValue())) {
assert false : "stored disequality between elements of incompatible type";
return false;
}
}
if (!assertNoExplicitLiteralDisequalities()) {
assert false;
return false;
}
if (!mLiteralSetConstraints.sanityCheck()) {
assert false;
return false;
}
return true;
}
private boolean assertNoExplicitLiteralDisequalities() {
/*
* disequalities between literals must remain implicit
*/
for (final Entry deq : mElementTVER.getDisequalities()) {
if (deq.getKey().isLiteral() && deq.getValue().isLiteral()) {
assert false : "explicit disequality between literals";
return false;
}
}
return true;
}
public Map getSupportingElementEqualities() {
return mElementTVER.getSupportingEqualities();
}
public HashRelation getElementDisequalities() {
return mElementTVER.getDisequalities();
}
Map summarize() {
final Map result = new HashMap<>();
result.put("#Elements", getAllElements().size());
result.put("#EquivalenceClasses", getAllRepresentatives().size());
result.put("#SupportingEqualties", getSupportingElementEqualities().size());
result.put("#SupportingDisequalties", getElementDisequalities().size());
return result;
}
@Override
public String toString() {
if (isTautological()) {
return "True";
}
if (isInconsistent()) {
return "False";
}
if (getAllElements().size() < CcSettings.MAX_NO_ELEMENTS_FOR_VERBOSE_TO_STRING) {
return toLogString();
}
final StringBuilder sb = new StringBuilder();
for (final Entry en : summarize().entrySet()) {
sb.append(String.format("%s : %d\n", en.getKey(), en.getValue()));
}
return sb.toString();
}
@Override
public String toLogString() {
if (isTautological()) {
return "True";
}
if (isInconsistent()) {
return "False";
}
final StringBuilder sb = new StringBuilder();
sb.append("--CC(begin):--\n");
sb.append("Equivalences:\n");
for (final Set eqc : mElementTVER.getAllEquivalenceClasses()) {
sb.append(eqc);
if (eqc.size() > 1) {
sb.append(" --- rep: ");
sb.append(mElementTVER.getRepresentative(eqc.iterator().next()));
}
sb.append("\n");
}
sb.append("Disequalities:\n");
for (final Entry deq : mElementTVER.getDisequalities()) {
sb.append(deq.getKey());
sb.append(" != ");
sb.append(deq.getValue());
sb.append("\n");
}
sb.append(mLiteralSetConstraints.toString());
sb.append("--CC(end):--\n");
return sb.toString();
}
static boolean haveSameElements(final ThreeValuedEquivalenceRelation tver1,
final ThreeValuedEquivalenceRelation tver2) {
return tver1.getAllElements().equals(tver2.getAllElements());
}
public boolean isTautological() {
if (isInconsistent()) {
return false;
}
return mElementTVER.isTautological() && mLiteralSetConstraints.isEmpty();
}
/**
* Replaces all ELEMs and ELEMs with transformed versions in place. The transformation is done by the given
* Functions.
*
* @param elemTransformer
* @param functionTransformer
* @return
*/
public void transformElementsAndFunctions(final Function elemTransformer) {
assert !mIsFrozen;
// assert sanitizeTransformer(elemTransformer, getAllElements()) : "we assume that the transformer does not "
// + "produce elements that were in the relation before!";
mElementTVER.transformElements(elemTransformer);
getAuxData().transformElements(elemTransformer);
mFaAuxData.transformElements(elemTransformer);
mLiteralSetConstraints.transformElements(elemTransformer);
assert CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_1 || sanityCheck();
}
/**
* We demand that if our transformer changes an element, it may not be in the original set of elements
*
* @param elemTransformer
* @param transformedSet
* @return
*/
private static boolean sanitizeTransformer(final Function elemTransformer, final Set inputSet) {
for (final E el : inputSet) {
final E transformed = elemTransformer.apply(el);
if (el.equals(transformed)) {
// nothing to check
continue;
}
if (inputSet.contains(transformed)) {
assert false;
return false;
}
}
return true;
}
public boolean hasElements(final ELEM... elems) {
for (final ELEM e : elems) {
if (!hasElement(e)) {
return false;
}
}
return true;
}
@Override
public boolean hasElement(final ELEM elem) {
return getAllElements().contains(elem);
}
/**
* We call a node constrained iff this CongruenceClosure puts any non-tautological constraint on it. In particular,
* the node elem is constrained if both of the following conditions hold
* elem is the only member of its equivalence class
* elem does not appear in a disequality
*
* @param elem
* @return true
*/
@Override
public boolean isConstrained(final ELEM elem) {
if (!hasElement(elem)) {
return false;
}
if (isConstrainedDirectly(elem)) {
return true;
}
if (elem.isDependentNonFunctionApplication()) {
for (final ELEM supp : elem.getSupportingNodes()) {
if (isConstrained(supp)) {
return true;
}
}
}
for (final ELEM afpar : mFaAuxData.getAfParents(elem)) {
if (isConstrained(afpar)) {
return true;
}
}
for (final ELEM argpar : mFaAuxData.getArgParents(elem)) {
if (isConstrained(argpar)) {
return true;
}
}
return false;
}
@Override
public boolean isConstrainedDirectly(final ELEM elem) {
if (!hasElement(elem)) {
return false;
}
if (mElementTVER.isConstrained(elem)) {
return true;
}
if (mLiteralSetConstraints.isConstrained(elem)) {
return true;
}
return false;
}
/**
* Returns a new CongruenceClosure which contains only those constraints in this CongruenceClosure that constrain at
* least one of the given elements.
*
* Say elemsToKeep contains a variable q, and we have {q, i} {a[i], j}, then we make explicit that the second
* conjunct constrains q, too, by adding the node a[q], we get {q, i} {a[q], a[i], j}.
* we may call this during a remove-operation (where this Cc labels a weak equivalence edge in a weqCc, and in
* the weqCc we are in the process of removing elements). That means that we may not introduce elements that are
* currently being removed, in particular, it may be the case that this Cc has a parent a[i], but not the child
* element i. We may not do anything that adds i back. (sanity checks have to account for this by taking into
* account the removeElementInfo, that we are passing around).
*
*
* @param elemsToKeep
* @param removeElement
* @return
*/
CongruenceClosure projectToElements(final Set elemsToKeep, final IRemovalInfo removeElementInfo) {
assert !mIsFrozen;
if (isInconsistent()) {
return this;
}
/*
* we need to augment the set such that all equivalent elements are contained, too. example: we project to {q}
* current partition: {q, i} {a[i], 0} then the second block implicitly puts a constraint on q, too, thus we
* need to keep it. --> this principle applies transitively, i.e., say we have {a[q], x} {b[x], y}...
*/
final CongruenceClosure copy = mManager.getCopyWithRemovalInfo(this, removeElementInfo);
final Set worklist = new HashSet<>(elemsToKeep);
/*
* the elements constraints over which we need to keep in our result (they do not need to be representatives in
* the UnionFind instance but to add one element per equivalence class here suffices)
*/
final Set elemsInConstraintsToKeep = new HashSet<>();
final Set visitedEquivalenceClassElements = new HashSet<>();
while (!worklist.isEmpty()) {
// assert copy.sanityCheck(removeElementInfo);
if (!CcSettings.OMIT_SANITYCHECK_FINE_GRAINED_1) {
assert copy.sanityCheck();
}
final ELEM current = worklist.iterator().next();
worklist.remove(current);
if (!copy.hasElement(current)) {
continue;
}
visitedEquivalenceClassElements.addAll(copy.mElementTVER.getEquivalenceClass(current));
// assert copy.mElementTVER.getEquivalenceClass(current).stream()
// .anyMatch(e -> dependsOnAny(e, elemsToKeep));
elemsInConstraintsToKeep.add(current);
/*
* for each ccpar f(x) (afccpar as well as argccpar) of the current element q (which is related to an
* element in elemsToKeep through constraints), add an element f(q) (or q(x) respectively) to the worklist,
* so its equivalence class will be kept.
*/
for (final ELEM afccpar : new HashSet<>(
copy.getAuxData().getAfCcPars(copy.getRepresentativeElement(current)))) {
if (visitedEquivalenceClassElements.contains(afccpar)) {
continue;
}
final ELEM substituted = afccpar.replaceAppliedFunction(current);
if (elemsInConstraintsToKeep.contains(substituted)) {
continue;
}
if (removeElementInfo != null && dependsOnAny(substituted, removeElementInfo.getRemovedElements())) {
// don't add anything that is currently being removed or depends on it
continue;
}
assert removeElementInfo == null || !dependsOnAny(substituted, removeElementInfo.getRemovedElements());
/*
* dropped this assertion since because of set constraints we also need elements that do not depend on a
* weq var
*/
// assert dependsOnAny(substituted, elemsToKeep);
mManager.addElement(copy, substituted, true, false);
worklist.add(substituted);
}
for (final ELEM argccpar : new HashSet<>(
copy.getAuxData().getArgCcPars(copy.getRepresentativeElement(current)))) {
if (visitedEquivalenceClassElements.contains(argccpar)) {
continue;
}
final ELEM substituted = argccpar.replaceArgument(current);
if (elemsInConstraintsToKeep.contains(substituted)) {
continue;
}
if (removeElementInfo != null && dependsOnAny(substituted, removeElementInfo.getRemovedElements())) {
// don't add anything that is currently being removed or depends on it
continue;
}
assert removeElementInfo == null || !dependsOnAny(substituted, removeElementInfo.getRemovedElements());
/*
* dropped this assertion since because of set constraints we also need elements that do not depend on a
* weq var
*/
// assert dependsOnAny(substituted, elemsToKeep);
mManager.addElement(copy, substituted, true, false);
worklist.add(substituted);
}
/*
* check literal constraints on current element, elements related to an elemToKeep this way must also be
* kept
*/
for (final ELEM relEl : copy.getLiteralSetConstraints()
.getRelatedElements(copy.getRepresentativeElement(current))) {
if (visitedEquivalenceClassElements.contains(relEl)) {
continue;
}
if (elemsInConstraintsToKeep.contains(relEl)) {
continue;
}
if (removeElementInfo != null && dependsOnAny(relEl, removeElementInfo.getRemovedElements())) {
// don't add anything that is currently being removed or depends on it
continue;
}
assert removeElementInfo == null || !dependsOnAny(relEl, removeElementInfo.getRemovedElements());
// assert dependsOnAny(relEl, elemsToKeep);
mManager.addElement(copy, relEl, true, false);
worklist.add(relEl);
}
}
// TVER does not know about parent/child relationship of nodes, so it is safe
final ThreeValuedEquivalenceRelation newTver =
copy.mElementTVER.filterAndKeepOnlyConstraintsThatIntersectWith(elemsInConstraintsToKeep);
assert assertNoNewElementsIntroduced(this.getAllElements(), newTver.getAllElements(),
elemsToKeep) : "no elements may have been introduced that were not present before this operation";
final CCLiteralSetConstraints newLiteralSetConstraints =
copy.mLiteralSetConstraints.filterAndKeepOnlyConstraintsThatIntersectWith(elemsInConstraintsToKeep);
/*
* Set constraints that are kept due to elemsInConstraintsToKeep may refer to elements that are not kept due to
* equality or disequality constraints. Thus we must add them to newTver in order to ensure the invariant that
* all elements in a set constraint are known to the enclosing congruenceClosure.
*/
for (final ELEM elem : newLiteralSetConstraints.getAllElementsMentionedInASetConstraint()) {
newTver.addElement(elem);
}
/*
* (former BUG!!!) this constructor may not add all child elements for all remaining elements, therefore we
* either need a special constructor or do something else..
*/
final CongruenceClosure result =
mManager.getCongruenceClosureFromTver(newTver, removeElementInfo, newLiteralSetConstraints, true);
assert assertNoNewElementsIntroduced(this.getAllElements(), result.getAllElements(),
elemsToKeep) : "no elements may have been introduced that were not present before this operation";
assert !result.isInconsistent() : "cannot go from a consistent input to an inconsisten output during projectTo";
return result;
}
/**
* projectToElements may only introduce fresh elements that depend on one of the elemsToKeep
*
* @param result
* @param elemsToKeep
* @return
*/
public boolean assertNoNewElementsIntroduced(final Set oldElems, final Set newElems,
final Set elemsToKeep) {
final Set diff = DataStructureUtils.difference(newElems, oldElems);
for (final ELEM d : diff) {
if (!dependsOnAny(d, elemsToKeep)) {
assert false;
return false;
}
}
// if (!diff.isEmpty()) {
// assert false;
// return false;
// }
return true;
}
public Collection getAllRepresentatives() {
return mElementTVER.getAllRepresentatives();
}
/**
* Determines if one of the descendants of given element elem is a member of the given element set sub.
*
* @param elem
* @param sub
* @return
*/
public static > boolean dependsOnAny(final ELEM elem,
final Set sub) {
if (sub.contains(elem)) {
return true;
}
if (elem.isDependentNonFunctionApplication()) {
if (DataStructureUtils.haveNonEmptyIntersection(elem.getSupportingNodes(), sub)) {
return true;
}
}
if (elem.isFunctionApplication()) {
return dependsOnAny(elem.getAppliedFunction(), sub) || dependsOnAny(elem.getArgument(), sub);
}
return false;
}
public IRemovalInfo getElementCurrentlyBeingRemoved() {
return mElementCurrentlyBeingRemoved;
}
public void setExternalRemInfo(final IRemovalInfo remInfo) {
assert mExternalRemovalInfo == null || mExternalRemovalInfo == remInfo;
mExternalRemovalInfo = remInfo;
}
public boolean isRepresentative(final ELEM elem) {
return mElementTVER.isRepresentative(elem);
}
public CcAuxData getAuxData() {
return mAuxData;
}
/**
* auxiliary data related to the tree where an edge a -> b means that b is an argument to function a (this is
* mostly/only needed for element removal)
*/
protected class FuncAppTreeAuxData {
// these cannot be managed within the nodes because the nodes are shared between CongruenceClosure instances!
private final HashRelation mDirectAfPars;
private final HashRelation mDirectArgPars;
private final HashRelation mNodeToDependents;
FuncAppTreeAuxData() {
mDirectAfPars = new HashRelation<>();
mDirectArgPars = new HashRelation<>();
mNodeToDependents = new HashRelation<>();
}
FuncAppTreeAuxData(final CongruenceClosure.FuncAppTreeAuxData faAuxData) {
mDirectAfPars = new HashRelation<>(faAuxData.mDirectAfPars);
mDirectArgPars = new HashRelation<>(faAuxData.mDirectArgPars);
mNodeToDependents = new HashRelation<>(faAuxData.mNodeToDependents);
}
public void addSupportingNode(final ELEM supp, final ELEM elem) {
mNodeToDependents.addPair(supp, elem);
}
public void addAfParent(final ELEM elem, final ELEM parent) {
mDirectAfPars.addPair(elem, parent);
}
public void addArgParent(final ELEM elem, final ELEM parent) {
mDirectArgPars.addPair(elem, parent);
}
public Set getAfParents(final ELEM elem) {
return Collections.unmodifiableSet(mDirectAfPars.getImage(elem));
}
public Set getArgParents(final ELEM elem) {
return Collections.unmodifiableSet(mDirectArgPars.getImage(elem));
}
public void removeAfParent(final ELEM elem, final ELEM parent) {
mDirectAfPars.removePair(elem, parent);
}
public void removeArgParent(final ELEM elem, final ELEM parent) {
mDirectArgPars.removePair(elem, parent);
}
public void transformElements(final Function elemTransformer) {
mDirectAfPars.transformElements(elemTransformer, elemTransformer);
mDirectArgPars.transformElements(elemTransformer, elemTransformer);
for (final Entry en : new HashRelation<>(mNodeToDependents).getSetOfPairs()) {
mNodeToDependents.removePair(en.getKey(), en.getValue());
mNodeToDependents.addPair(elemTransformer.apply(en.getKey()), elemTransformer.apply(en.getValue()));
}
}
public Set> getNodeToDependentPairs() {
return mNodeToDependents.getSetOfPairs();
}
public Set getDependentsOf(final ELEM elem) {
return mNodeToDependents.getImage(elem);
}
public void removeFromNodeToDependents(final ELEM etr) {
if (etr.isDependentNonFunctionApplication()) {
mNodeToDependents.removeRangeElement(etr);
}
mNodeToDependents.removeDomainElement(etr);
}
}
public void reportEqualityToElementTVER(final ELEM node1, final ELEM node2) {
mElementTVER.reportEquality(node1, node2);
}
public boolean isElementTverInconsistent() {
return mElementTVER.isInconsistent();
}
/**
* only used for technical reasons, to make mElementTVER inconsistent, don't use for anything else!
*
* @param node1
* @param node2
*/
public void reportDisequalityToElementTver(final ELEM node1, final ELEM node2) {
mElementTVER.reportDisequality(node1, node2);
}
public Collection getArgCcPars(final ELEM elem) {
return mAuxData.getArgCcPars(elem);
}
public Collection getFuncAppsWithFunc(final ELEM func) {
return mFaAuxData.getAfParents(func);
}
public void setElementCurrentlyBeingRemoved(final IRemovalInfo re) {
assert re == null || mElementCurrentlyBeingRemoved == null;
mElementCurrentlyBeingRemoved = re;
}
@Override
public boolean isDebugMode() {
return mManager.isDebugMode();
}
@Override
public ILogger getLogger() {
return mManager.getLogger();
}
public Set getEquivalenceClass(final ELEM elem) {
return Collections.unmodifiableSet(mElementTVER.getEquivalenceClass(elem));
}
public Set getAllLiterals() {
return Collections.unmodifiableSet(mAllLiterals);
}
public CcManager getManager() {
return mManager;
}
@Override
public void reportContainsConstraint(final ELEM elem, final Set literalSet) {
final HashRelation eqToProp = mLiteralSetConstraints.reportContains(elem, literalSet);
if (eqToProp != null) {
for (final Entry en : eqToProp) {
mManager.reportEquality(en.getKey(), en.getValue(), this, true);
}
}
}
public void reportContainsConstraint(final ELEM elem, final Collection> setCc) {
final HashRelation eqToProp = mLiteralSetConstraints.reportContains(elem, new HashSet<>(setCc));
if (eqToProp != null) {
for (final Entry en : eqToProp) {
mManager.reportEquality(en.getKey(), en.getValue(), this, true);
}
}
}
public Set> getContainsConstraintForElement(final ELEM elem) {
final Set> constraint = mLiteralSetConstraints.getConstraint(elem);
if (SetConstraintManager.isTautologicalWrtElement(elem, constraint)) {
// normalization: if the constraint is tautological for the given element, return null instead
return null;
}
return constraint;
}
}