/**
* Author: Georg Hofferek <georg.hofferek@iaik.tugraz.at>
*/
package at.iaik.suraq.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import at.iaik.suraq.proof.VeritProofNode;
import at.iaik.suraq.smtlib.formula.DomainEq;
import at.iaik.suraq.smtlib.formula.DomainTerm;
import at.iaik.suraq.smtlib.formula.EqualityFormula;
import at.iaik.suraq.smtlib.formula.Formula;
import at.iaik.suraq.smtlib.formula.PropositionalVariable;
import at.iaik.suraq.smtlib.formula.UninterpretedFunctionInstance;
import at.iaik.suraq.smtlib.formula.UninterpretedPredicateInstance;
/**
* Helper Class to perform congruence closure algorithm.
*
* @author Georg Hofferek <georg.hofferek@iaik.tugraz.at>
*
*/
public class CongruenceClosure {
/**
* Counts calls to checkTheoryLemma
*/
private static long checkTheoryLemmaCounter = 0;
/**
* Stores timing for checkTheoryLemma calls
*/
private static final Timer checkTheoryLemmaTimer = new Timer();
/**
* The list of equivalence classes
*/
private final List<Set<DomainTerm>> equivClasses = new LinkedList<Set<DomainTerm>>();
public CongruenceClosure() {
// nothing to do here
}
/**
* Adds the given equality and updates internal data structures (merging).
*
* @param formula
* an equality formula with exactly two terms.
*/
public void addEquality(DomainEq formula) {
assert (formula.getTerms().size() == 2);
assert (formula.isEqual());
DomainTerm term1 = (DomainTerm) formula.getTerms().get(0);
DomainTerm term2 = (DomainTerm) formula.getTerms().get(1);
if (term1 instanceof UninterpretedFunctionInstance) {
this.addTerm(term1);
}
if (term2 instanceof UninterpretedFunctionInstance) {
this.addTerm(term2);
}
for (Set<DomainTerm> equivClass : equivClasses) {
if (equivClass.contains(term1)) {
equivClass.add(term2);
merge();
return;
}
if (equivClass.contains(term2)) {
equivClass.add(term1);
merge();
return;
}
}
Set<DomainTerm> newClass = new HashSet<DomainTerm>();
newClass.add(term1);
newClass.add(term2);
equivClasses.add(newClass);
merge();
return;
}
/**
* Adds the given term to the equivalences classes. (Also adds all
* subterms.)
*
* @param term
*/
public void addTerm(DomainTerm term) {
if (term instanceof UninterpretedFunctionInstance) {
for (DomainTerm subterm : ((UninterpretedFunctionInstance) term)
.getSubTerms())
this.addTerm(subterm);
}
for (Set<DomainTerm> equivClass : equivClasses) {
if (equivClass.contains(term))
return;
}
Set<DomainTerm> singleton = new HashSet<DomainTerm>();
singleton.add(term);
equivClasses.add(singleton);
}
/**
* Checks whether the given formula is implied by this congruence closure.
* I.e., checks whether its two terms occur in the same equivalence class.
*
* @param formula
* @return <code>true</code> if the given formula is implied by this
* congruence closure.
*/
public boolean checkImplied(DomainEq formula) {
assert (formula.getTerms().size() == 2);
assert (formula.isEqual());
DomainTerm term1 = (DomainTerm) formula.getTerms().get(0);
DomainTerm term2 = (DomainTerm) formula.getTerms().get(1);
this.addTerm(term1);
this.addTerm(term2);
this.merge();
for (Set<DomainTerm> equivClass : equivClasses) {
if (equivClass.contains(term1) && equivClass.contains(term2))
return true;
}
return false;
}
/**
*
* @return the number of equivalence classes
*/
public int getNumEquivClasses() {
return this.equivClasses.size();
}
/**
* Performs merging. Probably not very efficient.
*/
private void merge() {
while (mergeInternal()) {
// Nothing; mergeInternal does everything
}
}
/**
* Performs one merge and reports back that it happened.
*
* @return <code>true</code> if a merge was performed.
*/
private boolean mergeInternal() {
// Merging based on common terms
for (Set<DomainTerm> equivClass1 : equivClasses) {
assert (equivClasses.contains(equivClass1));
for (DomainTerm term : equivClass1) {
for (Set<DomainTerm> equivClass2 : equivClasses) {
assert (equivClasses.contains(equivClass2));
if (equivClass1 == equivClass2)
continue;
if (equivClass2.contains(term)) {
equivClass1.addAll(equivClass2);
boolean removed = equivClasses.remove(equivClass2);
assert (removed);
return true;
}
}
}
}
// Merging based on congruence
Map<UninterpretedFunctionInstance, Set<DomainTerm>> functionInstancesEquivClasses = getAllUninterpretedFunctionInstances();
for (UninterpretedFunctionInstance instance1 : functionInstancesEquivClasses
.keySet()) {
for (UninterpretedFunctionInstance instance2 : functionInstancesEquivClasses
.keySet()) {
if (instance1 == instance2)
continue;
if (functionInstancesEquivClasses.get(instance1) == functionInstancesEquivClasses
.get(instance2))
continue;
if (!instance1.getFunction().equals(instance2.getFunction()))
continue;
assert (instance1.getParameters().size() == instance2
.getParameters().size());
int numOk = 0;
for (int count = 0; count < instance1.getParameters().size(); count++) {
DomainTerm param1 = instance1.getParameters().get(count);
DomainTerm param2 = instance2.getParameters().get(count);
if (findClassContainingBoth(param1, param2) != null)
numOk++;
}
if (numOk == instance1.getParameters().size()) {
Set<DomainTerm> class1 = functionInstancesEquivClasses
.get(instance1);
Set<DomainTerm> class2 = functionInstancesEquivClasses
.get(instance2);
class1.addAll(class2);
boolean removed = this.equivClasses.remove(class2);
assert (removed);
return true;
}
}
}
return false;
// for (Set<DomainTerm> equivClass1 : equivClasses) {
// for (Term term1 : equivClass1) {
// List<DomainTerm> terms1 = null;
// List<DomainTerm> terms2 = null;
// Set<DomainTerm> equivClass2 = null;
// if (term1 instanceof UninterpretedFunctionInstance) {
// terms1 = ((UninterpretedFunctionInstance) term1)
// .getParameters();
// for (Set<DomainTerm> equivClassTmp : equivClasses) {
// if (equivClass1 == equivClassTmp)
// continue;
// for (Term term2 : equivClassTmp) {
// if (term2 instanceof UninterpretedFunctionInstance) {
// if (((UninterpretedFunctionInstance) term2)
// .getFunction()
// .equals(((UninterpretedFunctionInstance) term1)
// .getFunction())) {
// terms2 = ((UninterpretedFunctionInstance) term2)
// .getParameters();
// equivClass2 = equivClassTmp;
// break;
// }
// }
// }
// if (terms2 != null)
// break;
// }
// } else if (term1 instanceof UninterpretedPredicateInstance) {
// terms1 = ((UninterpretedPredicateInstance) term1)
// .getParameters();
// for (Set<DomainTerm> equivClassTmp : equivClasses) {
// if (equivClass1 == equivClassTmp)
// continue;
// for (Term term2 : equivClassTmp) {
// if (term2 instanceof UninterpretedPredicateInstance) {
// if (((UninterpretedFunctionInstance) term2)
// .getFunction()
// .equals(((UninterpretedPredicateInstance) term1)
// .getFunction())) {
// terms2 = ((UninterpretedPredicateInstance) term2)
// .getParameters();
// equivClass2 = equivClassTmp;
// break;
// }
// }
// }
// if (terms2 != null)
// break;
// }
// } else {
// continue;
// }
// if (terms1 != null && terms2 != null) {
// assert (equivClass2 != null);
// assert (terms1.size() == terms2.size());
// int numOk = 0;
// for (int count = 0; count < terms1.size(); count++) {
// for (Set<DomainTerm> equivClassTmp : equivClasses) {
// if (equivClassTmp.contains(terms1.get(count))
// && equivClassTmp
// .contains(terms2.get(count))) {
// numOk++;
// }
// }
// }
// if (numOk == terms1.size()) {
// equivClass1.addAll(equivClass2);
// equivClasses.remove(equivClass2);
// return true;
// }
// }
// }
// }
// return false;
}
/**
*
* @param term1
* @param term2
* @return the equivalence class that holds both <code>term1</code> and
* <code>term2</code>, or <code>null</code> if no such class exists
*/
private Set<DomainTerm> findClassContainingBoth(DomainTerm term1,
DomainTerm term2) {
for (Set<DomainTerm> equivClass : this.equivClasses) {
if (equivClass.contains(term1) && equivClass.contains(term2))
return equivClass;
}
return null;
}
/**
* Returns a map with all uninterpreted function instances as keys, mapping
* to the equivalence classes they are currently in.
*
* @return a map from uninterpreted function instances to containing
* equivalence classes.
*/
private Map<UninterpretedFunctionInstance, Set<DomainTerm>> getAllUninterpretedFunctionInstances() {
Map<UninterpretedFunctionInstance, Set<DomainTerm>> result = new HashMap<UninterpretedFunctionInstance, Set<DomainTerm>>();
for (Set<DomainTerm> equivClass : this.equivClasses) {
for (DomainTerm term : equivClass) {
if (term instanceof UninterpretedFunctionInstance)
result.put((UninterpretedFunctionInstance) term, equivClass);
}
}
return result;
}
/**
* This check assumes that only one positive literal is present in the
* <code>literals</code>. If the given literals contains anything that
* should not occur in a theory lemma (e.g. Tseitin variables)
* <code>false</code> is returned.
*
* @param literals
* a collection of literals (only one positive)
* @return <code>true</code> if the given <code>literals</code> are a theory
* lemma
*/
public static boolean checkTheoryLemma(
Collection<? extends Formula> literals) {
CongruenceClosure.checkTheoryLemmaTimer.start();
CongruenceClosure.checkTheoryLemmaCounter++;
assert (literals != null);
CongruenceClosure cc = new CongruenceClosure();
Formula impliedLiteral = null;
for (Formula literal : literals) {
if (literal instanceof PropositionalVariable) {
// e.g. Tseitin variable, or something from the input
CongruenceClosure.checkTheoryLemmaTimer.stop();
return false;
}
if (Util.isNegativeLiteral(literal)) {
Formula positiveLiteral = Util.makeLiteralPositive(literal);
if (positiveLiteral instanceof DomainEq) {
cc.addEquality((DomainEq) positiveLiteral);
}
} else {
if (impliedLiteral != null) {
// More than one implied literal means this is not a theory
// lemma
CongruenceClosure.checkTheoryLemmaTimer.stop();
return false;
}
assert (Util.isAtom(literal));
impliedLiteral = literal;
}
}
if (impliedLiteral == null) {
// No implied literal means this is not a theory lemma
CongruenceClosure.checkTheoryLemmaTimer.stop();
return false;
}
assert (Util.isLiteral(impliedLiteral));
assert (!Util.isNegativeLiteral(impliedLiteral));
if (impliedLiteral instanceof DomainEq) {
assert (((DomainEq) impliedLiteral).getTerms().size() == 2);
cc.addTerm((DomainTerm) ((DomainEq) impliedLiteral).getTerms().get(
0));
cc.addTerm((DomainTerm) ((DomainEq) impliedLiteral).getTerms().get(
1));
cc.merge();
final boolean result = cc.checkImplied((DomainEq) impliedLiteral);
CongruenceClosure.checkTheoryLemmaTimer.stop();
return result;
} else {
assert (impliedLiteral instanceof UninterpretedPredicateInstance);
UninterpretedPredicateInstance positiveInstance = (UninterpretedPredicateInstance) impliedLiteral;
UninterpretedPredicateInstance negativeInstance = null;
for (Formula literal : literals) {
if (!Util.isNegativeLiteral(literal))
continue;
Formula positiveLiteral = Util.makeLiteralPositive(literal);
if (!(positiveLiteral instanceof UninterpretedPredicateInstance))
continue;
UninterpretedPredicateInstance predicateInstance = (UninterpretedPredicateInstance) positiveLiteral;
if (!predicateInstance.getFunction().equals(
positiveInstance.getFunction()))
continue;
if (negativeInstance != null) {
// Only one matching instance should exist in a theory lemma
CongruenceClosure.checkTheoryLemmaTimer.stop();
return false;
}
negativeInstance = predicateInstance;
}
if (negativeInstance == null) {
// No matching instance found
// Not a theory lemma
CongruenceClosure.checkTheoryLemmaTimer.stop();
return false;
}
assert (positiveInstance.getFunction().equals(negativeInstance
.getFunction()));
assert (positiveInstance.getFunction().getNumParams() == negativeInstance
.getFunction().getNumParams());
assert (positiveInstance.getParameters().size() == negativeInstance
.getParameters().size());
for (int count = 0; count < positiveInstance.getFunction()
.getNumParams(); count++) {
DomainTerm term1 = positiveInstance.getParameters().get(count);
DomainTerm term2 = negativeInstance.getParameters().get(count);
List<DomainTerm> domainTerms = new ArrayList<DomainTerm>(2);
domainTerms.add(term1);
domainTerms.add(term2);
DomainEq equalityToCheck = DomainEq.create(domainTerms, true);
if (!cc.checkImplied(equalityToCheck)) {
CongruenceClosure.checkTheoryLemmaTimer.stop();
return false;
}
}
CongruenceClosure.checkTheoryLemmaTimer.stop();
return true;
}
}
/**
* This check assumes that only one positive literal is present in the
* conclusion. This should hold for veriT proofs, because every (original)
* theory-lemma leaf has only one positive literal and every resolution
* eliminates one positive literal.
*
* @param node
* @return <code>true</code> if the given <code>node</code>'s conclusion is
* a theory lemma.
*/
public static boolean checkVeritProofNode(VeritProofNode node) {
return CongruenceClosure.checkTheoryLemma(node.getLiteralConclusions());
}
/**
*
* @param literals
* a collection of literals which are checked for whether they
* imply the given <code>impliedLiteral</code>. All these must be
* positive.
* @param impliedLiteral
* the literal which should be implied by the given
* <code>literals</code>. Must be positive.
* @return <code>true</code> if the given <code>literals</code> imply the
* given <code>impliedLiteral</code>.
*/
public static boolean checkLiteralImplication(
Collection<? extends EqualityFormula> literals,
DomainEq impliedLiteral) {
CongruenceClosure cc = new CongruenceClosure();
for (Formula literal : literals) {
assert (Util.isLiteral(literal));
assert (!Util.isNegativeLiteral(literal));
cc.addEquality((DomainEq) literal);
}
assert (Util.isLiteral(impliedLiteral));
assert (!Util.isNegativeLiteral(impliedLiteral));
return cc.checkImplied(impliedLiteral);
}
/**
*
* @param literals
* a collection of literals which are checked for whether they
* imply the given <code>impliedLiteral</code>. All these must be
* positive.
* @param term1
* the first term of the literal which should be implied by the
* given <code>literals</code>.
* @param term2
* the second term of the literal which should be implied by the
* given <code>literals</code>.
* @return <code>true</code> if the given <code>literals</code> imply the
* literal <code>term1=term2</code>.
*/
public static boolean checkLiteralImplication(
Collection<? extends EqualityFormula> literals, DomainTerm term1,
DomainTerm term2) {
List<DomainTerm> domainTerms = new ArrayList<DomainTerm>(2);
domainTerms.add(term1);
domainTerms.add(term2);
DomainEq impliedLiteral = DomainEq.create(domainTerms, true);
CongruenceClosure cc = new CongruenceClosure();
cc.addTerm(term1);
cc.addTerm(term2);
for (Formula literal : literals) {
assert (Util.isLiteral(literal));
assert (!Util.isNegativeLiteral(literal));
assert (literal instanceof DomainEq);
cc.addEquality((DomainEq) literal);
}
assert (Util.isLiteral(impliedLiteral));
assert (!Util.isNegativeLiteral(impliedLiteral));
return cc.checkImplied(impliedLiteral);
}
/**
*
* @return the counter of calls to <code>checkTheoryLemma</code>.
*/
public static long getCheckTheoryLemmaCounter() {
return CongruenceClosure.checkTheoryLemmaCounter;
}
/**
*
* @return the value of the timer of the calls to
* <code>checkTheoryLemma</code>.
*/
public static String getCheckTheoryLemmaTimer() {
return CongruenceClosure.checkTheoryLemmaTimer.toString();
}
}