/** * Author: Christoph Hillebold <c.hillebold@student.tugraz.at> */ package at.iaik.suraq.proof; import java.io.BufferedWriter; import java.io.IOException; import java.io.Serializable; 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.exceptions.IncomparableTermsException; import at.iaik.suraq.exceptions.SuraqException; import at.iaik.suraq.main.SuraqOptions; import at.iaik.suraq.sexp.Token; import at.iaik.suraq.smtlib.formula.AndFormula; 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.ImpliesFormula; import at.iaik.suraq.smtlib.formula.NotFormula; import at.iaik.suraq.smtlib.formula.OrFormula; import at.iaik.suraq.smtlib.formula.PropositionalConstant; import at.iaik.suraq.smtlib.formula.Term; import at.iaik.suraq.smtlib.formula.UninterpretedFunctionInstance; import at.iaik.suraq.smtlib.formula.UninterpretedPredicateInstance; import at.iaik.suraq.util.CongruenceClosure; import at.iaik.suraq.util.HashTagContainer; import at.iaik.suraq.util.ImmutableArrayList; import at.iaik.suraq.util.ImmutableSet; import at.iaik.suraq.util.MutableInteger; import at.iaik.suraq.util.Timer; import at.iaik.suraq.util.Util; import at.iaik.suraq.util.chain.TransitivityCongruenceChain; import at.iaik.suraq.util.graph.Graph; /** * VeritProofSet is a single set-line in the proof. You shall not use this class * to create or modify the proof. Use VeritProof instead. To prevent you from * using certain functions, they are marked as deprecated. * * @author chillebold * */ public class VeritProofNode implements Serializable { private static final long serialVersionUID = 1L; /** * This field can be used to quickly turn off checks of proof nodes. */ private static boolean checkProofNodesEnabled = true; /** * Records time spent in the checkProofNode method */ private static final Timer checkTimer = new Timer(); /** * Records time spent in the checkTransCongr method */ private static final Timer checkTransCongrTimer = new Timer(); /** * Records time spent in the checkResolution method */ private static final Timer checkResolutionTimer = new Timer(); /** * Counts calls to the checkProofNode method */ private static long checkCounter = 0; /** * Counts calls to the checkTransCongr method */ private static long checkTransCongrCounter = 0; /** * Counts calls to the checkResolution method */ private static long checkResolutionCounter = 0; private final int hashCode; /** * The name of the VeritProofNode. E.g. ".c11" */ private final String name; /** * The type of the VeritProofNode. It should be one of the Types defined in * VeriTToken. E.g. VeriTToken.EQ_TRANSITIVE */ private final Token type; /** * A list of literalConclusions (Formulas). The contents of this list should * not be changed! */ private final ImmutableArrayList<Formula> literalConclusions; /** * iargs (Integer) */ private final Integer iargs; /** * A list of SubProofs/Conclusions */ private final List<VeritProofNode> subProofs; /** * A list of Parents */ private final Set<VeritProofNode> parents; /** * <code>true</code> iff at least one descendant of this node is an input * node. I.e., this node is not purely derived from theory lemmas. */ private final boolean inputDerived; /** * The proof this node belongs to. */ private final VeritProof proof; /** * Used to disable printing of failure message while checking TRANS_CONGR * nodes. */ private boolean suppressFailureMessage = false; /** * Stores suppressed messages */ @SuppressWarnings("unused") private String suppressedMessage = null; /** * Prints information about check counters and timers to stdout. */ public static void printCheckCountersAndTimers() { System.out .println("--------------------------------------------------------------------------------"); Util.printToSystemOutWithWallClockTimePrefix("Check timers and counters:"); System.out .println("Check counter: " + Util.largeNumberFormatter .format(VeritProofNode.checkCounter)); System.out.println("Check timer: " + VeritProofNode.checkTimer.toString()); System.out.println("CheckTransCongr counter: " + Util.largeNumberFormatter .format(VeritProofNode.checkTransCongrCounter)); System.out.println("CheckTransCongr timer: " + VeritProofNode.checkTransCongrTimer.toString()); System.out.println("CheckResolution counter: " + Util.largeNumberFormatter .format(VeritProofNode.checkResolutionCounter)); System.out.println("CheckResolution timer: " + VeritProofNode.checkResolutionTimer.toString()); System.out.println("CheckTheoryLemma counter: " + Util.largeNumberFormatter.format(CongruenceClosure .getCheckTheoryLemmaCounter())); System.out.println("CheckTheoryLemma timer: " + CongruenceClosure.getCheckTheoryLemmaTimer()); System.out .println("--------------------------------------------------------------------------------"); } /** * Do not call this constructor by yourself. Use VeritProof to create this * class. * * @param name * @param type * @param conclusions * @param clauses * @param iargs * @param proof * @param removeSubproofsOfTheoryLemmas */ protected VeritProofNode(String name, Token type, List<Formula> conclusions, List<VeritProofNode> clauses, Integer iargs, VeritProof proof, boolean removeSubproofsOfTheoryLemmas) { assert (name != null); boolean tmpInputDerived = type.equals(VeriTToken.INPUT); if (!tmpInputDerived && clauses != null) { for (VeritProofNode child : clauses) { if (child.inputDerived) { tmpInputDerived = true; break; } } } this.inputDerived = tmpInputDerived; final boolean isTheoryLemma = this.inputDerived ? false : true; // CongruenceClosure.checkTheoryLemma(conclusions); if (!isTheoryLemma) { assert (this.inputDerived); if (clauses != null) { for (VeritProofNode child : clauses) { if (!child.inputDerived) { // Perform the delayed check of this node final boolean checkResult = child.checkProofNode(); assert (checkResult); if (!checkResult) throw new RuntimeException( "A node that should have been a theory lemma failed the check.\n" + child.toString()); if (child.containsBadLiteral()) throw new RuntimeException( "Node unexpectedly contains bad literal.\n" + child.toString()); } } } else assert (type.equals(VeriTToken.INPUT)); } List<Formula> reducedConclusions = new ArrayList<Formula>(); for (Formula literal : conclusions) { if (!reducedConclusions.contains(literal)) { if (Util.isLiteral(literal)) { if (Util.isUnitClause(literal)) literal = Util.getSingleLiteral(literal); // Negated reflexivities might sometimes be needed for // resolution // // if (!Util.isNegatedReflexivity(literal)) { if (literal instanceof EqualityFormula) { if (!reducedConclusions.contains(Util .reverseEquality((EqualityFormula) literal))) { reducedConclusions.add(literal); } } else { reducedConclusions.add(literal); } // } } else { assert (type.equals(VeriTToken.INPUT)); reducedConclusions.add(literal); } } } assert ((new HashSet<Formula>(reducedConclusions)).size() == reducedConclusions .size()); List<VeritProofNode> tmpSubProofs = new ArrayList<VeritProofNode>(2); ArrayList<Formula> tmpLiteralConclusions = new ArrayList<Formula>(); if (type.equals(VeriTToken.RESOLUTION) && (clauses == null ? false : clauses.size() > 2) && (!isTheoryLemma || !removeSubproofsOfTheoryLemmas)) { // Clauses are left-associative List<VeritProofNode> remainingNodes = new LinkedList<VeritProofNode>( clauses); int count = 0; while (true) { assert (remainingNodes.size() > 2); List<VeritProofNode> currentNodes = new ArrayList<VeritProofNode>( remainingNodes.subList(0, 2)); List<Formula> currentConclusions = conclusionsOfResolution( currentNodes.get(0).getLiteralConclusions(), currentNodes.get(1).getLiteralConclusions()); VeritProofNode intermediateNode = new VeritProofNode(name + "i" + (++count), VeriTToken.RESOLUTION, currentConclusions, currentNodes, null, proof, removeSubproofsOfTheoryLemmas); remainingNodes.remove(1); remainingNodes.remove(0); remainingNodes.add(0, intermediateNode); if (remainingNodes.size() == 2) { tmpSubProofs = remainingNodes; tmpLiteralConclusions = new ArrayList<Formula>( conclusionsOfResolution(tmpSubProofs.get(0) .getLiteralConclusions(), tmpSubProofs.get(1).getLiteralConclusions())); break; } } assert (tmpSubProofs.size() == 2 || !type .equals(VeriTToken.RESOLUTION)); } else { tmpLiteralConclusions = reducedConclusions == null ? new ArrayList<Formula>() : new ArrayList<Formula>(reducedConclusions); tmpSubProofs = clauses == null ? new ArrayList<VeritProofNode>() : new ArrayList<VeritProofNode>(clauses); } assert (tmpLiteralConclusions != null); assert (tmpSubProofs != null); if (isTheoryLemma && removeSubproofsOfTheoryLemmas) { this.subProofs = new ArrayList<VeritProofNode>(0); this.type = VeriTToken.TRANS_CONGR; } else { this.subProofs = tmpSubProofs; this.type = type; } this.name = name; this.parents = new HashSet<VeritProofNode>(2); this.iargs = iargs == null ? null : new Integer(iargs); this.proof = proof; this.literalConclusions = new ImmutableArrayList<Formula>( tmpLiteralConclusions); // Compute hashCode before passing the this-pointer to the outside world this.hashCode = literalConclusions.hashCode() + 32 * name.hashCode() + 64 * type.hashCode(); // If this is a lemma that will potentially be removed, delay the check // until later if (!isTheoryLemma || !removeSubproofsOfTheoryLemmas) assert (this.checkProofNode()); assert (proof != null); proof.addProofNode(this); for (VeritProofNode child : this.subProofs) child.addParent(this); } /** * Returns the literal that resolved the two given clauses, as it occurs in * <code>clause1</code>. A sanity check (that no other literals would be * resolving literals is <em>not</em> done. * * @param clause1 * @param clause2 * @return the resolving literal (as it occurs in <code>clause1</code> * polarity), or <code>null</code> if none exists */ private Formula findResolvingLiteral(Collection<? extends Formula> clause1, Collection<? extends Formula> clause2) { for (Formula literal : clause1) { assert (Util.isLiteral(literal)); Formula invertedLiteral = Util.invertLiteral(literal); if (clause2.contains(invertedLiteral)) return literal; } return null; } /** * Returns the conclusion resulting from resolution of the given literals. * If the given literals have no resolving literal (or multiple resolving * literals), the method will fail. The result will not contain any literal * twice. * * @param literals1 * @param literals2 * @return the conclusion of resolution of <code>literals1</code> and * <code>literals2</code>. */ private List<Formula> conclusionsOfResolution( Collection<? extends Formula> literals1, Collection<? extends Formula> literals2) { List<Formula> conclusions = new LinkedList<Formula>(); Formula resolvingLiteral = findResolvingLiteral(literals1, literals2); assert (resolvingLiteral != null); for (Formula literal : literals1) { if (!literal.equals(resolvingLiteral) && !conclusions.contains(literal)) { assert (VeritProofNode.checkProofNodesEnabled ? !conclusions .contains(Util.invertLiteral(literal)) : true); conclusions.add(literal); } } for (Formula literal : literals2) { if (!literal.equals(Util.invertLiteral(resolvingLiteral)) && !conclusions.contains(literal)) { assert (VeritProofNode.checkProofNodesEnabled ? !conclusions .contains(Util.invertLiteral(literal)) : true); conclusions.add(literal); } } assert ((new HashSet<Formula>(conclusions).size() == conclusions.size())); return conclusions; } /** * Picks a fitting proof node from <code>remainingClauses</code> to resolve * on the clause given in <code>tmpSubProofs</code>. The node is removed * from <code>remainingClauses</code> and added to <code>tmpSubProofs</code> * . * * @param remainingClauses * list of nodes to be searched for a resolving node. * <em>Will be modified!</em> * @param tmpSubProofs * list to add the found node to. Must have exactly one element * first! * @param conclusions * the literals that occur in the final conclusion, after all * intermediate steps * @return the literal on which resolution is done, in arbitrary polarity. */ @SuppressWarnings("unused") private Formula pickAndUseFittingClause( List<VeritProofNode> remainingClauses, List<VeritProofNode> tmpSubProofs, Collection<Formula> conclusions) { assert (remainingClauses != null); assert (tmpSubProofs != null); assert (conclusions != null); assert (!remainingClauses.isEmpty()); assert (tmpSubProofs.size() == 1); List<Formula> literals = tmpSubProofs.get(0).getLiteralConclusions() .editableCopy(); literals.removeAll(conclusions); assert (!literals.isEmpty()); for (Formula literal : literals) { for (int clauseCount = 0; clauseCount < remainingClauses.size(); clauseCount++) { Formula inverseLiteral = Util.invertLiteral(literal); if (remainingClauses.get(clauseCount).getLiteralConclusions() .contains(inverseLiteral)) { tmpSubProofs.add(remainingClauses.remove(clauseCount)); return literal; } } } // If we reach here, we found no clause to resolve with. // This should not happen. assert (false); return null; } public String getName() { return name; } public Token getType() { return type; } /** * This method returns the inner conclusions-List. * * @return the list of literals in the conclusions. */ public ImmutableArrayList<Formula> getLiteralConclusions() { return literalConclusions; } /** * This method returns the inner conclusions-List as a set. * * @return the list of literals in the conclusions in set representation. */ public ImmutableSet<Formula> getLiteralConclusionsAsSet() { return ImmutableSet.create(literalConclusions); } /** * This method returns an OR-Formula of the literal conclusions * * @return a copied ArrayList of the conclusions */ public OrFormula getConclusionsAsOrFormula() { if (literalConclusions.isEmpty()) { List<Formula> list = new ArrayList<Formula>(1); list.add(PropositionalConstant.create(false)); return OrFormula.generate(list); } return OrFormula.generate(literalConclusions); } /** * returns an immutable copy of clauses. You cannot modify the list * directly. Use the VeritProof-Class instead! * * @return an immutable copy of the subproofs (might be empty) */ public ImmutableArrayList<VeritProofNode> getSubProofs() { assert (subProofs != null); return new ImmutableArrayList<VeritProofNode>(subProofs); } /** * * @return the <code>iargs</code> property, or <code>null</code> if not * present. */ public Integer getIargs() { return iargs; } /** * returns an immutable copy of parents. You cannot modify the list * directly. Use the VeritProof-Class instead! * * @return an immutable copy of parents. Will be empty if this is a/the root * node. */ public ImmutableSet<VeritProofNode> getParents() { assert (parents != null); return ImmutableSet.create(parents); } /** * Adds a node to the list of parents of <code>this</code> node. * <code>this</code> node has to be in the <code>subProofs</code> of the * given <code>parent</code>. * * @param parent * a parent of this node */ protected void addParent(VeritProofNode parent) { assert (parent.subProofs.contains(this)); parents.add(parent); } /** * Removes the given <code>parent</code> from the list of parents. If the * given <code>parent</code> is not present in the current list of parents, * nothing happens. * * @param parent * the parent to remove */ protected void removeParent(VeritProofNode parent) { Set<VeritProofNode> newParents = new HashSet<VeritProofNode>(); // Workaround because removal seems to be broken for (VeritProofNode otherParent : parents) { if (!parent.equals(otherParent)) newParents.add(otherParent); } parents.clear(); parents.addAll(newParents); // end workaround assert (!parents.contains(parent)); if (parents.isEmpty()) this.kill(); } /** * Clears the subproofs, removes <code>this</code> from the parents of all * subproofs, and removes this dangling node from its proof. <strong>Only * call on nodes without parents!</strong> */ private void kill() { assert (parents.isEmpty()); this.proof.removeDanglingProofNode(this); List<VeritProofNode> tmpSubProofs = new ArrayList<VeritProofNode>( subProofs); subProofs.clear(); for (VeritProofNode subProof : tmpSubProofs) { subProof.removeParent(this); } } @Deprecated protected void addSubProof(VeritProofNode subProof) { subProofs.add(subProof); assert (this.checkProofNode()); } @Deprecated protected void removeSubProof(VeritProofNode subProof) { subProofs.remove(subProof); assert (this.checkProofNode()); } /** * Replaces <code>oldSubProof</code> with <code>newSubProof</code> in the * subproofs of <code>this</code>. The conclusion of * <code>oldSubProof</code> and <code>newSubProof</code> have to be the * same. None of the parameters may be <code>null</code>. * * @param oldSubProof * the subproof to remove * @param newSubProof * the subproof to put it instead */ protected boolean updateProofNode(VeritProofNode oldSubProof, VeritProofNode newSubProof) { assert (oldSubProof != null); assert (newSubProof != null); // assert (oldSubProof.getParents().contains(this)); // Parent is // removed before update assert (this.subProofs.size() == 2); assert (this.type.equals(VeriTToken.RESOLUTION)); if (!this.subProofs.contains(oldSubProof)) return false; VeritProofNode otherSubProof = this.subProofs.get(0) == oldSubProof ? this.subProofs .get(1) : this.subProofs.get(0); for (Formula literal : oldSubProof.literalConclusions) { if (!newSubProof.literalConclusions.contains(literal) && !otherSubProof.literalConclusions.contains(literal)) return false; } subProofs.set(subProofs.indexOf(oldSubProof), newSubProof); assert (checkProofNode()); oldSubProof.removeParent(this); newSubProof.addParent(this); return true; } /** * Updates the subproofs of <code>this</code> node to the given ones. The * node is checked after replacement. If the check fails, changes are * reverted and <code>false</code> is returned. Parent relations are also * updated accordingly, if update is successful. * * @param newSubProofs * the new subproofs to set. * @return <code>true</code> if replacement was successful and actually * done; <code>false</code> if replacement failed and changes have * been reverted. */ @Deprecated protected boolean updateProofNode(List<VeritProofNode> newSubProofs) { List<VeritProofNode> tmpSubProofs = new ArrayList<VeritProofNode>( subProofs); subProofs.clear(); subProofs.addAll(newSubProofs); if (!checkProofNode()) { subProofs.clear(); subProofs.addAll(tmpSubProofs); return false; } for (VeritProofNode oldSubProof : tmpSubProofs) oldSubProof.removeParent(this); for (VeritProofNode newSubProof : subProofs) newSubProof.addParent(this); return true; } /** * Creates a stronger (or equal) new node, and uses it instead of this one. * * @param weakerSubProof * the subproof for which a stronger counterpart can be used * instead * @param strongerSubProof * the stronger counterpart * @return the number of nodes updated */ protected int makeStronger(VeritProofNode weakerSubProof, VeritProofNode strongerSubProof) { if (weakerSubProof == strongerSubProof) return 0; assert (weakerSubProof != strongerSubProof); assert (weakerSubProof != null); assert (strongerSubProof != null); assert (this.subProofs.size() == 2); assert (this.type.equals(VeriTToken.RESOLUTION)); assert (this.subProofs.contains(weakerSubProof)); assert (!this.subProofs.contains(strongerSubProof)); assert (weakerSubProof.getLiteralConclusions() .containsAll(strongerSubProof.getLiteralConclusions())); assert (weakerSubProof.getLiteralConclusions().size() >= strongerSubProof .getLiteralConclusions().size()); assert (this.parents.size() > 0 || this == this.proof.getRoot()); if (strongerSubProof.literalConclusions.size() == 0) { boolean rootSet = this.proof.setNewRoot(strongerSubProof); assert (rootSet); Util.printToSystemOutWithWallClockTimePrefix("Set a new root."); return 0; } if (strongerSubProof.literalConclusions .containsAll(weakerSubProof.literalConclusions) && weakerSubProof.literalConclusions .containsAll(strongerSubProof.literalConclusions)) { boolean updated = this.updateProofNode(weakerSubProof, strongerSubProof); assert (updated); return 1; } Formula resolvingLiteral = this.findResolvingLiteral(); if (!strongerSubProof.literalConclusions.contains(resolvingLiteral) && !strongerSubProof.literalConclusions.contains(Util .invertLiteral(resolvingLiteral))) { assert (this.literalConclusions .containsAll(strongerSubProof.literalConclusions)); assert (this != this.proof.getRoot()); // This resolution step is unnecessary. Make all parents stronger. Util.printToSystemOutWithWallClockTimePrefix("Removing an unnecessary resolution step"); Set<VeritProofNode> parentsCopy = new HashSet<VeritProofNode>( this.parents); int count = 0; for (VeritProofNode parent : parentsCopy) { if (!this.parents.contains(parent)) continue; count += parent.makeStronger(this, strongerSubProof); } return count; } List<VeritProofNode> clauses = new ArrayList<VeritProofNode>(2); List<Formula> conclusions = new ArrayList<Formula>(); for (VeritProofNode clause : this.subProofs) { if (clause.equals(weakerSubProof)) { clauses.add(strongerSubProof); for (Formula literal : strongerSubProof.literalConclusions) { if (!conclusions.contains(literal)) conclusions.add(literal); } } else { clauses.add(clause); for (Formula literal : clause.literalConclusions) { if (!conclusions.contains(literal)) conclusions.add(literal); } } } assert (clauses.size() == 2); conclusions.remove(resolvingLiteral); conclusions.remove(Util.invertLiteral(resolvingLiteral)); if (this.literalConclusions.containsAll(conclusions) && conclusions.containsAll(this.literalConclusions)) { boolean updated = this.updateProofNode(weakerSubProof, strongerSubProof); assert (updated); return 1; } assert (conclusions.size() < this.literalConclusions.size()); assert (this.literalConclusions.containsAll(conclusions)); VeritProofNode strongerNode = new VeritProofNode( this.proof.freshNodeName("str_", this.name), this.type, conclusions, clauses, null, this.proof, false); assert (strongerNode != null); Set<VeritProofNode> parentsCopy = new HashSet<VeritProofNode>( this.parents); int count = 1; for (VeritProofNode parent : parentsCopy) { if (!this.parents.contains(parent)) continue; count += parent.makeStronger(this, strongerNode); } return count; } /** * * @see java.lang.Object#toString() */ @Override public String toString() { String str = "(set " + name + " (" + type; if (subProofs != null) { str += " :clauses ("; for (VeritProofNode clause : subProofs) str += clause.getName() + " "; str += ")"; } if (iargs != null) { str += " :iargs (" + iargs + ")"; } if (literalConclusions != null) { str += " :conclusions ("; for (Formula conclusion : literalConclusions) str += " " + conclusion.toString(); str += ")"; } str += "))"; str = str.replaceAll("\\s{2,}", " ").replace("\n", ""); if (subProofs != null) { if (subProofs.size() > 0) { str += "\nConclusions of subproofs:"; for (VeritProofNode subproof : subProofs) { str += "\n" + subproof.name + ": "; for (Formula conclusion : subproof.literalConclusions) { str += " " + conclusion.toString() .replaceAll("\\s{2,}", " ") .replace("\n", ""); } } } } return str; } /** * Checks if the current proof node's conclusion is correctly derived from * all its sub-proofs. * * @return <code>true</code> if this node is a valid deduction, * <code>false</code> otherwise. */ public boolean checkProofNode() { if (!VeritProofNode.checkProofNodesEnabled) return true; VeritProofNode.checkTimer.start(); VeritProofNode.checkCounter++; // Type specific tests if (this.type.equals(VeriTToken.INPUT)) { if (subProofs.size() != 0) { VeritProofNode.checkTimer.stop(); return failOnMessage("INPUT node with parents"); } VeritProofNode.checkTimer.stop(); return true; } if (this.type.equals(VeriTToken.AND)) { // This type will be removed after parsing. // --> no detailed checks VeritProofNode.checkTimer.stop(); return true; } if (this.type.equals(VeriTToken.OR)) { // This type will be removed after parsing. // --> no detailed checks VeritProofNode.checkTimer.stop(); return true; } // Remaining types should have only literals in their conclusions for (Formula literal : literalConclusions) { if (!Util.isLiteral(literal)) { VeritProofNode.checkTimer.stop(); return failOnMessage("Non-literal in conclusion: " + literal.toString()); } } if (this.containsReversedLiterals()) { return failOnMessage("Contains reversed literals."); } if (this.containsDuplicateLiteral()) { return failOnMessage("Contains duplicate literal."); } if (this.type.equals(VeriTToken.EQ_REFLEXIVE)) { if (subProofs.size() != 0) { VeritProofNode.checkTimer.stop(); return failOnMessage("Reflexivity node with parents!"); } if (literalConclusions.size() != 1) { VeritProofNode.checkTimer.stop(); return failOnMessage("Reflexivity node with more than one literal in conclusions!"); } assert (literalConclusions.size() == 1); Formula literal = literalConclusions.get(0); if (!Util.isReflexivity(literal)) { VeritProofNode.checkTimer.stop(); return failOnMessage("Not a correct reflexivity!"); } else { VeritProofNode.checkTimer.stop(); return true; } } if (this.type.equals(VeriTToken.EQ_CONGRUENT)) { final boolean result = checkCongruence(); VeritProofNode.checkTimer.stop(); return result; } if (this.type.equals(VeriTToken.EQ_CONGRUENT_PRED)) { final boolean result = checkCongruencePred(); VeritProofNode.checkTimer.stop(); return result; } if (this.type.equals(VeriTToken.EQ_TRANSITIVE)) { final boolean result = checkTransitive(); VeritProofNode.checkTimer.stop(); return result; } if (this.type.equals(VeriTToken.RESOLUTION)) { final boolean result = checkResolution(); VeritProofNode.checkTimer.stop(); return result; } if (this.type.equals(VeriTToken.TRANS_CONGR)) { // this.suppressFailureMessage = true; // boolean result = checkTransitive() || checkCongruence() // || checkCongruencePred(); // this.suppressFailureMessage = false; // if (!result) { // assert (suppressedMessage != null); // System.out.println(suppressedMessage); // suppressedMessage = null; // } final boolean result = checkTransCongr(); VeritProofNode.checkTimer.stop(); return result; } // unknown node type failOnMessage("Unknown node type!"); assert (false); VeritProofNode.checkTimer.stop(); return false; } /** * @return <code>true</code> if the conclusions contain a literal and its * symmetric counterpart (regardless of phase). */ private boolean containsReversedLiterals() { for (Formula literal1 : this.literalConclusions) { if (Util.isReflexivity(Util.makeLiteralPositive(literal1))) continue; for (Formula literal2 : this.literalConclusions) { if (literal1 == literal2) { continue; } if (Util.areReversedLiterals(literal1, literal2)) return true; } } return false; } private boolean containsDuplicateLiteral() { for (int count1 = 0; count1 < literalConclusions.size(); count1++) { Formula literal1 = literalConclusions.get(count1); for (int count2 = count1 + 1; count2 < literalConclusions.size(); count2++) { Formula literal2 = literalConclusions.get(count2); if (literal1.equals(literal2)) { return true; } } } return false; } /** * Returns <code>false</code> and prints the given <code>message</code>, * unless it is <code>null</code>, in which case <code>true</code> is * returned and nothing is printed. * * @param message * the failure message to print. * @return <code>false</code> if <code>message != null</code>, * <code>true</code> otherwise. */ private boolean failOnMessage(String message) { if (message == null) return true; assert (message != null); StringBuffer messageBuffer = new StringBuffer(); messageBuffer.append("ERROR: Check of node '" + this.name + "' failed.\n"); messageBuffer.append(message); messageBuffer.append("\nNode data follows:\n"); messageBuffer.append(this.toString()); messageBuffer.append("\n"); if (this.suppressFailureMessage) { suppressedMessage += messageBuffer.toString(); } else System.out.println(messageBuffer.toString()); return false; } /** * Call only on nodes with type <code>RESOLUTION</code>. * * @return <code>true</code> iff this is a valid resolution step */ private boolean checkResolution() { VeritProofNode.checkResolutionTimer.start(); VeritProofNode.checkResolutionCounter++; assert (this.type.equals(VeriTToken.RESOLUTION)); // Taking the assumption that only resolution with two parents occurs. if (subProofs.size() != 2) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Resolution with number of subproofs !=2. Number: " + subProofs.size()); } // Special case if one of the two parents is a unit clause if (subProofs.get(0).literalConclusions.size() == 1) { Formula unitLiteral = subProofs.get(0).literalConclusions.get(0); Formula inverseUnitLiteral = Util.invertLiteral(unitLiteral); if (!subProofs.get(1).literalConclusions .contains(inverseUnitLiteral)) return failOnMessage("Unit literal not found in opposite polarity in other subproof."); List<Formula> expectedConclusions = new ArrayList<Formula>( subProofs.get(1).literalConclusions); expectedConclusions.remove(inverseUnitLiteral); if (!literalConclusions.containsAll(expectedConclusions)) return failOnMessage("Missing a literal in conclusion."); if (!expectedConclusions.containsAll(literalConclusions)) return failOnMessage("Too much literals in conclusion."); return true; } if (subProofs.get(1).literalConclusions.size() == 1) { Formula unitLiteral = subProofs.get(1).literalConclusions.get(0); Formula inverseUnitLiteral = Util.invertLiteral(unitLiteral); if (!subProofs.get(0).literalConclusions .contains(inverseUnitLiteral)) return failOnMessage("Unit literal not found in opposite polarity in other subproof."); List<Formula> expectedConclusions = new ArrayList<Formula>( subProofs.get(0).literalConclusions); expectedConclusions.remove(inverseUnitLiteral); if (!literalConclusions.containsAll(expectedConclusions)) return failOnMessage("Missing a literal in conclusion."); if (!expectedConclusions.containsAll(literalConclusions)) return failOnMessage("Too much literals in conclusion."); return true; } // Special case if one of the two parents is a "LEM instance" (a \/ ~a) Formula lemLiteral0 = subProofs.get(0).isLEM(); if (lemLiteral0 != null) { Formula resolvingLiteral = Util.findResolvingLiteral(subProofs); if (lemLiteral0.equals(resolvingLiteral)) { if (!subProofs.get(1).literalConclusions.contains(lemLiteral0) && !subProofs.get(1).literalConclusions.contains(Util .invertLiteral(lemLiteral0))) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("LEM literal not found in other subproof!"); } if (!literalConclusions .containsAll(subProofs.get(1).literalConclusions)) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Missing a literal after LEM resolution."); } List<Formula> nonLemLiterals = new ArrayList<Formula>( subProofs.get(0).literalConclusions); nonLemLiterals.remove(lemLiteral0); nonLemLiterals.remove(Util.invertLiteral(lemLiteral0)); List<Formula> expectedInOtherSubProof = new ArrayList<Formula>( literalConclusions); expectedInOtherSubProof.removeAll(nonLemLiterals); if (!subProofs.get(1).literalConclusions .containsAll(expectedInOtherSubProof)) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Too much literals after LEM resolution."); } VeritProofNode.checkResolutionTimer.stop(); return true; } } Formula lemLiteral1 = subProofs.get(1).isLEM(); if (lemLiteral1 != null) { Formula resolvingLiteral = Util.findResolvingLiteral(subProofs); if (lemLiteral1.equals(resolvingLiteral)) { if (!subProofs.get(0).literalConclusions.contains(lemLiteral1) && !subProofs.get(0).literalConclusions.contains(Util .invertLiteral(lemLiteral1))) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("LEM literal not found in other subproof!"); } if (!literalConclusions .containsAll(subProofs.get(0).literalConclusions)) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Missing a literal after LEM resolution."); } List<Formula> nonLemLiterals = new ArrayList<Formula>( subProofs.get(1).literalConclusions); nonLemLiterals.remove(lemLiteral1); nonLemLiterals.remove(Util.invertLiteral(lemLiteral1)); List<Formula> expectedInOtherSubProof = new ArrayList<Formula>( literalConclusions); expectedInOtherSubProof.removeAll(nonLemLiterals); if (!subProofs.get(0).literalConclusions .containsAll(expectedInOtherSubProof)) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Too much literals after LEM resolution."); } VeritProofNode.checkResolutionTimer.stop(); return true; } } boolean resolvingLiteralFound = false; for (Formula literal : subProofs.get(0).literalConclusions) { if (!literalConclusions.contains(literal) && !literalConclusions.contains(Util .reverseTermsInLiteral(literal))) { if (resolvingLiteralFound) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Found more than one resolving literal!"); } Formula invertedLiteral = Util.invertLiteral(literal); if (!subProofs.get(1).literalConclusions .contains(invertedLiteral)) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Resolving literal " + literal.toString().replaceAll("\\s{2,}", " ") .replace("\n", "") + " from first subproof not found in inverse polarity in other subproof!"); } else resolvingLiteralFound = true; } } if (!resolvingLiteralFound) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("No resolving literal found!"); } resolvingLiteralFound = false; for (Formula literal : subProofs.get(1).literalConclusions) { if (!literalConclusions.contains(literal) && !literalConclusions.contains(Util .reverseTermsInLiteral(literal))) { if (resolvingLiteralFound) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Found more than one resolving literal!"); } Formula invertedLiteral = Util.invertLiteral(literal); if (!subProofs.get(0).literalConclusions .contains(invertedLiteral)) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Resolving literal " + literal.toString().replaceAll("\\s{2,}", " ") .replace("\n", "") + " from second subproof not found in inverse polarity in other subproof!"); } else resolvingLiteralFound = true; } } if (!resolvingLiteralFound) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("No resolving literal found!"); } for (Formula literal : literalConclusions) { if (!subProofs.get(0).literalConclusions.contains(literal) && !subProofs.get(1).literalConclusions.contains(literal)) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Literal not originating from one of the subproofs found! Literal: " + literal.toString()); } } Set<Formula> literal1 = new HashSet<Formula>(subProofs.get(0) .getLiteralConclusions()); literal1.removeAll(literalConclusions); Set<Formula> literal2 = new HashSet<Formula>(subProofs.get(1) .getLiteralConclusions()); literal2.removeAll(literalConclusions); if (literal1.size() != 1 || literal2.size() != 1) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Conclusion misses at least one literal from subproofs!"); } if (!Util.makeLiteralPositive(literal1.iterator().next()).equals( Util.makeLiteralPositive(literal2.iterator().next()))) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Mismatch in resolving literal!"); } if (!(Util.isNegativeLiteral(literal1.iterator().next()) ^ Util .isNegativeLiteral(literal2.iterator().next()))) { VeritProofNode.checkResolutionTimer.stop(); return failOnMessage("Mismatch in resolving literal polarity!"); } VeritProofNode.checkResolutionTimer.stop(); return true; } /** * @return the (positive) LEM literal iff this is an instance of the law of * excluded middle (LEM), plus potentially some reflexivities. * Otherwise <code>null</code>. */ private Formula isLEM() { Formula lemLiteral1 = null; Formula lemLiteral2 = null; if (literalConclusions.size() > 2) { int numReflexivities = 0; for (Formula literal : literalConclusions) { assert (Util.isLiteral(literal)); if (Util.isReflexivity(Util.makeLiteralPositive(literal))) numReflexivities++; else { if (lemLiteral1 == null) lemLiteral1 = literal; else { if (lemLiteral2 == null) lemLiteral2 = literal; else return null; } } } if (lemLiteral1 == null || lemLiteral2 == null) return null; assert (literalConclusions.size() == 2 + numReflexivities); } else { lemLiteral1 = literalConclusions.get(0); lemLiteral2 = literalConclusions.get(1); } assert (lemLiteral1 != null); assert (lemLiteral2 != null); if (Util.makeLiteralPositive(lemLiteral1).equals( Util.makeLiteralPositive(lemLiteral2)) && (Util.getSignValue(lemLiteral1) ^ Util .getSignValue(lemLiteral2))) return Util.makeLiteralPositive(lemLiteral1); return null; } /** * Call only on nodes with type <code>EQ_TRANSITIVE</code>. * * @return <code>true</code> iff this is a valid transitivity axiom * instantiation. */ private boolean checkTransitive() { assert (this.type.equals(VeriTToken.EQ_TRANSITIVE) || this.type .equals(VeriTToken.TRANS_CONGR)); if (literalConclusions.size() == 2) { if (this.isLEM() == null) return failOnMessage("Transitivity with size two that is not a LEM."); else return true; } if (literalConclusions.size() < 3) return failOnMessage("Transitivity axiom with less than 3 literals! Number: " + literalConclusions.size()); if (subProofs.size() != 0) return failOnMessage("Transitivity axiom with subproofs! Number: " + subProofs.size()); // Taking the assumption that the implied literal is the last one Formula impliedLiteral = literalConclusions.get(literalConclusions .size() - 1); if (Util.isNegativeLiteral(impliedLiteral)) return failOnMessage("Implied literal is negative!"); if (!(impliedLiteral instanceof EqualityFormula)) return failOnMessage("Implied literal is not an equality!"); if (!((EqualityFormula) impliedLiteral).isEqual()) return failOnMessage("Implied literal is a disequality!"); Term[] terms = ((EqualityFormula) impliedLiteral).getTerms().toArray( new Term[0]); if (terms.length != 2) return failOnMessage("Implied literal is equality with number of terms !=2."); Graph<Term, Formula> equalityGraph = new Graph<Term, Formula>(); for (Formula literal : literalConclusions.subList(0, literalConclusions.size() - 1)) { if (!Util.isLiteral(literal)) return failOnMessage("Found non-literal!"); if (!Util.isNegativeLiteral(literal)) return failOnMessage("Found unexpected positive literal!"); if (!(Util.makeLiteralPositive(literal) instanceof EqualityFormula)) return failOnMessage("Found unexpected non-equality literal!"); if (!((EqualityFormula) Util.makeLiteralPositive(literal)) .isEqual()) return failOnMessage("Found unexpected disequality!"); if (((EqualityFormula) Util.makeLiteralPositive(literal)) .getTerms().size() != 2) return failOnMessage("Found equality literal with number of terms !=2."); Term[] currentTerms = ((EqualityFormula) Util .makeLiteralPositive(literal)).getTerms().toArray( new Term[0]); assert (currentTerms.length == 2); equalityGraph.addNode(currentTerms[0]); equalityGraph.addNode(currentTerms[1]); equalityGraph.addEdge(currentTerms[0], currentTerms[1], literal); } List<Formula> path = equalityGraph.findPath(terms[0], terms[1]); if (path == null) { if (!CongruenceClosure.checkVeritProofNode(this)) return failOnMessage("Could not prove implied literal with congruence closure!"); } else { assert (literalConclusions.containsAll(path)); } return true; } /** * Call only on nodes with type <code>EQ_CONGRUENT_PRED</code> * * @return <code>true</code>, iff this is a valid predicate congruence axiom * instantiation. */ private boolean checkCongruencePred() { assert (this.type.equals(VeriTToken.EQ_CONGRUENT_PRED) || this.type .equals(VeriTToken.TRANS_CONGR)); if (literalConclusions.size() < 3) return failOnMessage("Less than 3 literals in predicate congruence axiom! Number: " + literalConclusions.size()); if (subProofs.size() != 0) return failOnMessage("Predicate congruence axiom with subproofs! Number: " + subProofs.size()); // Taking the assumption that the "implied literal" is the last one. Formula impliedLiteral = literalConclusions.get(literalConclusions .size() - 1); // Taking the assumption that the second-to-last literal is also // a predicate instance, with inverse polarity Formula inversePredicateLiteral = literalConclusions .get(literalConclusions.size() - 2); if (!(Util.makeLiteralPositive(impliedLiteral) instanceof UninterpretedPredicateInstance)) return failOnMessage("Implied literal not a predicate instance!"); if (!(Util.makeLiteralPositive(inversePredicateLiteral) instanceof UninterpretedPredicateInstance)) return failOnMessage("The literal expected to be the inverse predicate instance is not a predicate instance!"); if (Util.getSignValue(impliedLiteral) == Util .getSignValue(inversePredicateLiteral)) return failOnMessage("Implied literal and inverse predicate literal have the same polarity!"); UninterpretedPredicateInstance instance1 = (UninterpretedPredicateInstance) Util .makeLiteralPositive(impliedLiteral); UninterpretedPredicateInstance instance2 = (UninterpretedPredicateInstance) Util .makeLiteralPositive(inversePredicateLiteral); if (!instance1.getFunction().equals(instance2.getFunction())) return failOnMessage("Predicate mismatch!"); List<DomainTerm> terms1 = instance1.getParameters(); List<DomainTerm> terms2 = instance2.getParameters(); assert (terms1.size() == terms2.size()); // Check that there is an equality-chain for each parameter for (int count = 0; count < terms1.size(); count++) { // Try via transitivity chain Graph<Term, Formula> equalityGraph = new Graph<Term, Formula>(); for (Formula currentLiteral : literalConclusions.subList(0, literalConclusions.size() - 2)) { if (!Util.isLiteral(currentLiteral)) return failOnMessage("Found a non-literal!"); if (!Util.isNegativeLiteral(currentLiteral)) return failOnMessage("Found an unexpected non-negative literal!"); if (!(Util.makeLiteralPositive(currentLiteral) instanceof EqualityFormula)) return failOnMessage("Found an unexpected non-equality literal!"); if (!((EqualityFormula) Util .makeLiteralPositive(currentLiteral)).isEqual()) return failOnMessage("Found an unexpected disequality!"); if (((EqualityFormula) Util.makeLiteralPositive(currentLiteral)) .getTerms().size() != 2) return failOnMessage("Found an equality with number of terms !=2."); Term[] currentTerms = ((EqualityFormula) Util .makeLiteralPositive(currentLiteral)).getTerms() .toArray(new Term[0]); assert (currentTerms.length == 2); equalityGraph.addNode(currentTerms[0]); equalityGraph.addNode(currentTerms[1]); equalityGraph.addEdge(currentTerms[0], currentTerms[1], currentLiteral); } List<Formula> path = equalityGraph.findPath(terms1.get(count), terms2.get(count)); if (path != null) { assert (literalConclusions.containsAll(path)); continue; } // Not found any matching equality. // Try finding something with Congruence Closure List<EqualityFormula> ccLiterals = new ArrayList<EqualityFormula>(); for (Formula literal : literalConclusions.subList(0, literalConclusions.size() - 2)) { assert (Util.makeLiteralPositive(literal) instanceof EqualityFormula); ccLiterals.add((EqualityFormula) Util .makeLiteralPositive(literal)); } if (CongruenceClosure.checkLiteralImplication(ccLiterals, terms1.get(count), terms2.get(count))) continue; // Congruence Closure did not work either return failOnMessage("Did not find an equality path for parameter number " + count); } return true; } /** * Call only on nodes with type <code>EQ_CONGRUENT</code> * * @return <code>true</code>, iff this is a valid congruence axiom * instantiation. */ private boolean checkCongruence() { assert (this.type.equals(VeriTToken.EQ_CONGRUENT) || this.type .equals(VeriTToken.TRANS_CONGR)); if (literalConclusions.size() < 2) return failOnMessage("Too few conclusion literals: " + literalConclusions.size()); if (subProofs.size() != 0) return failOnMessage("Subproofs non-empty! Size: " + subProofs.size()); // Taking the assumption that the "implied literal" is the last one. Formula impliedLiteral = literalConclusions.get(literalConclusions .size() - 1); if (Util.isNegativeLiteral(impliedLiteral)) return failOnMessage("Implied literal is negative!"); if (!(impliedLiteral instanceof EqualityFormula)) return failOnMessage("Implied literal in congruence axiom is not an equality!"); if (!((EqualityFormula) impliedLiteral).isEqual()) return failOnMessage("Implied literal in congruence axiom is a disequality!"); Term[] terms = ((EqualityFormula) impliedLiteral).getTerms().toArray( new Term[0]); if (terms.length != 2) return false; List<DomainTerm> terms1 = null; List<DomainTerm> terms2 = null; if (terms[0] instanceof UninterpretedFunctionInstance) { if (!(terms[1] instanceof UninterpretedFunctionInstance)) return failOnMessage("Second equality term is not an uninterpreted function instance!"); terms1 = ((UninterpretedFunctionInstance) terms[0]).getParameters(); terms2 = ((UninterpretedFunctionInstance) terms[1]).getParameters(); assert (terms1 != null); assert (terms2 != null); if (terms1.size() != terms2.size()) return failOnMessage("Implied literal has uninterpreted function instances with different number of parameters"); if (!((UninterpretedFunctionInstance) terms[0]).getFunction() .equals(((UninterpretedFunctionInstance) terms[1]) .getFunction())) return failOnMessage("Implied literal has non-matching uninterpreted function instances!"); } else { return failOnMessage("First equality term is not an uninterpreted function instance!"); } assert (terms1 != null); assert (terms2 != null); assert (terms1.size() == terms2.size()); // Taking the assumption that equalities in the axiom instantiation // occur in the same order as they occur as parameters to the // uninterpreted function boolean allOk = true; for (int count = 0; count < terms1.size(); count++) { // For each parameter (-pair), search for negated equality. List<DomainTerm> eqTerms = new ArrayList<DomainTerm>(2); eqTerms.add(terms1.get(count)); eqTerms.add(terms2.get(count)); Formula literal; try { literal = EqualityFormula.create(eqTerms, true); } catch (IncomparableTermsException exc) { throw new RuntimeException(exc); } literal = NotFormula.create(literal); if (literalConclusions.contains(literal)) continue; // Try other order of terms eqTerms.clear(); eqTerms.add(terms2.get(count)); eqTerms.add(terms1.get(count)); try { literal = EqualityFormula.create(eqTerms, true); } catch (IncomparableTermsException exc) { throw new RuntimeException(exc); } literal = NotFormula.create(literal); if (literalConclusions.contains(literal)) continue; // Try via transitivity chain Graph<Term, Formula> equalityGraph = new Graph<Term, Formula>(); allOk = true; for (Formula currentLiteral : literalConclusions.subList(0, literalConclusions.size() - 1)) { if (!Util.isLiteral(currentLiteral)) return failOnMessage("Non-Literal found!"); if (!Util.isNegativeLiteral(currentLiteral)) return failOnMessage("Unexpected non-negative literal found!"); if (!(Util.makeLiteralPositive(currentLiteral) instanceof EqualityFormula)) return failOnMessage("Non-equality literal found!"); if (!((EqualityFormula) Util .makeLiteralPositive(currentLiteral)).isEqual()) return failOnMessage("Disequality-literal found!"); if (((EqualityFormula) Util.makeLiteralPositive(currentLiteral)) .getTerms().size() != 2) return failOnMessage("Equality with number of terms !=2 found!"); Term[] currentTerms = ((EqualityFormula) Util .makeLiteralPositive(currentLiteral)).getTerms() .toArray(new Term[0]); assert (currentTerms.length == 2); equalityGraph.addNode(currentTerms[0]); equalityGraph.addNode(currentTerms[1]); equalityGraph.addEdge(currentTerms[0], currentTerms[1], currentLiteral); } List<Formula> path = equalityGraph.findPath(terms1.get(count), terms2.get(count)); if (path != null) { assert (literalConclusions.containsAll(path)); continue; } // Not found any matching equality. Try with Congruence Closure // instead, as the graph does not merge congruences automatically allOk = false; break; } if (!allOk) { // check with Congruence Closure List<EqualityFormula> positiveLiterals = new ArrayList<EqualityFormula>( literalConclusions.size() - 1); for (Formula literal : literalConclusions.subList(0, literalConclusions.size() - 1)) { assert (Util.makeLiteralPositive(literal) instanceof EqualityFormula); positiveLiterals.add((EqualityFormula) Util .makeLiteralPositive(literal)); } assert (impliedLiteral instanceof DomainEq); if (!CongruenceClosure.checkLiteralImplication(positiveLiterals, (DomainEq) impliedLiteral)) return failOnMessage("Could not prove congruence axiom with congruence closure."); } return true; } /** * Call only on nodes with types <code>TRANS_CONGR</code>. * * @return */ private boolean checkTransCongr() { VeritProofNode.checkTransCongrTimer.start(); VeritProofNode.checkTransCongrCounter++; assert (this.type.equals(VeriTToken.TRANS_CONGR)); if (!this.subProofs.isEmpty()) { VeritProofNode.checkTransCongrTimer.stop(); return failOnMessage("TRANS_CONGR with non-empty subproofs."); } if (!CongruenceClosure.checkVeritProofNode(this)) { VeritProofNode.checkTransCongrTimer.stop(); return failOnMessage("Congruence closure failed."); } VeritProofNode.checkTransCongrTimer.stop(); return true; } /** * * @return <code>true</code>, if the conclusion of this node contains a bad * literal. */ public boolean containsBadLiteral() { for (Formula literal : literalConclusions) { if (Util.isBadLiteral(literal)) return true; } return false; } /** * Checks if the conclusion of this node is an axiom that introduces a new * bad literal based solely on good literals. * * @return <code>true</code> if this node is a leaf with a conclusion that * has one positive bad literal and only negative good literals. */ public boolean isGoodDefinitionOfBadLiteral() { if (subProofs.size() > 0) return false; // Not a leaf if (literalConclusions.size() < 2) return false; // Not a definition if there is just one literal boolean badLiteralFound = false; for (Formula literal : literalConclusions) { if (Util.isNegativeLiteral(literal)) { if (Util.isBadLiteral(literal)) return false; // bad literal should occur positive } else if (Util.isLiteral(literal)) { if (!Util.isBadLiteral(literal)) return false; // positive literal should be bad } else { // This means we have something that is not a literal. // That should not happen. assert (false); } if (Util.isBadLiteral(literal)) { if (badLiteralFound) return false; // more than one bad literal found badLiteralFound = true; } } return badLiteralFound; } /** * @return <code>true</code> if this is a leaf. */ public boolean isLeaf() { assert (subProofs != null); return subProofs.size() == 0; } /** * * @return the defined bad literal, or <code>null</code> if this node does * not define a bad literal by good ones. */ public Formula getDefinedBadLiteral() { if (!isGoodDefinitionOfBadLiteral()) return null; for (Formula literal : literalConclusions) { assert (Util.isLiteral(literal)); if (Util.isBadLiteral(literal)) return literal; } assert (false); return null; } /** * * @return the list of good literals defining a bad one, or * <code>null</code> if this node is not a good definition of a bad * literal */ public List<Formula> getDefiningGoodLiterals() { if (!isGoodDefinitionOfBadLiteral()) return null; ArrayList<Formula> tmp = new ArrayList<Formula>(); for (Formula literal : literalConclusions) { assert (Util.isLiteral(literal)); if (!Util.isBadLiteral(literal)) tmp.add(literal); } assert (!tmp.isEmpty()); return tmp; } /** * @return the <code>proof</code> */ public VeritProof getProof() { return proof; } /** * Checks whether this node resolves on the given <code>literal</code>. * * @param literal * must be a literal! * @return <code>true</code> if this node resolves on the given * <code>literal</code>. <code>false</code> otherwise; in particular * if this is not a resolution node. */ public boolean resolvesOn(Formula literal) { assert (Util.isLiteral(literal)); if (!this.type.equals(VeriTToken.RESOLUTION)) return false; Formula resolvingLiteral = this.findResolvingLiteral(); assert (Util.isLiteral(resolvingLiteral)); assert (!Util.isNegativeLiteral(resolvingLiteral)); return Util.makeLiteralPositive(literal).equals(resolvingLiteral); } /** * Only call on resolution nodes! * * @return the resolving literal (in positive form) */ public Formula findResolvingLiteral() { assert (checkResolution()); // OLD CODE // Set<Formula> literal1 = new HashSet<Formula>(subProofs.get(0) // .getLiteralConclusionsAsSet()); // literal1.removeAll(literalConclusions); // return Util.makeLiteralPositive(literal1.iterator().next()); Formula result = Util.findResolvingLiteral(this.subProofs); assert (result != null); return result; } /** * If this node resolves on the given <code>literal</code>, returns the * child that has the literal in the opposite polarity as the given one. * * @param literal * @return the child of this node which has the given literal in opposite * polarity. */ public VeritProofNode getChildWithLiteralInOppositePolarity(Formula literal) { assert (this.resolvesOn(literal)); assert (subProofs.size() == 2); if (subProofs.get(0).getLiteralConclusions().contains(literal)) { assert (subProofs.get(1).getLiteralConclusions().contains(Util .invertLiteral(literal))); return subProofs.get(1); } if (subProofs.get(1).getLiteralConclusions().contains(literal)) { assert (subProofs.get(0).getLiteralConclusions().contains(Util .invertLiteral(literal))); return subProofs.get(0); } assert (false); return null; } /** * Checks whether this is a theory axiom, based solely on the type of the * node. * * @return <code>true</code> if this is an axiom. */ public boolean isAxiom() { if (type.equals(VeriTToken.EQ_CONGRUENT)) return true; if (type.equals(VeriTToken.EQ_CONGRUENT_PRED)) return true; if (type.equals(VeriTToken.EQ_REFLEXIVE)) return true; if (type.equals(VeriTToken.EQ_TRANSITIVE)) return true; if (type.equals(VeriTToken.TRANS_CONGR)) return true; return false; } /** * @return the partitions of the literals of this node. */ public Set<Integer> getPartitionsFromSymbols() { Set<Integer> result = new HashSet<Integer>(); for (Formula literal : literalConclusions) result.addAll(literal.getPartitionsFromSymbols()); return result; } /** * @param path * the path so far * @param notPartOfCycle * a set of nodes which are not part of a cycle. * @return <code>true</code> if no cycles are found */ public boolean isAcyclic(List<VeritProofNode> path, Set<VeritProofNode> notPartOfCycle, MutableInteger lastReportedPercentage) { int percentage = (int) Math .floor(((double) notPartOfCycle.size() / proof.size()) * 100); if (percentage >= lastReportedPercentage.intValue() + 10) { Util.printToSystemOutWithWallClockTimePrefix("Done " + percentage + "% of nodes during acyclicity check."); lastReportedPercentage.add(10); } for (VeritProofNode child : subProofs) { if (path.contains(child)) return false; if (notPartOfCycle.contains(child)) continue; path.add(child); if (!child.isAcyclic(path, notPartOfCycle, lastReportedPercentage)) return false; path.remove(path.size() - 1); assert (!path.contains(child)); } notPartOfCycle.add(this); return true; } /** * Reimplementation of {@link VeritProofNode#splitPredicateLeaf()} with a * more modular structure, for n-ary predicates, and using some new * knowledge on VeriT proofs. (I.e., uncolorable literals do not occur after * removing subproofs of theory lemmas.) * * @return a replacement of this leaf, based on only colorable leaves. */ public VeritProofNode splitPredicateLeafNew() { assert (this.type.equals(VeriTToken.EQ_CONGRUENT_PRED) || this.type .equals(VeriTToken.TRANS_CONGR)); assert (this.type.equals(VeriTToken.EQ_CONGRUENT_PRED) ? this .checkCongruencePred() : this.checkTransCongr()); // Compute implied literal Formula positiveLiteral = Util.getImpliedLiteral(literalConclusions); assert (positiveLiteral instanceof UninterpretedPredicateInstance); UninterpretedPredicateInstance impliedLiteral = (UninterpretedPredicateInstance) positiveLiteral; UninterpretedPredicateInstance inversePredicateLiteral = Util .findInversePredicateLiteral(impliedLiteral, literalConclusions); assert (inversePredicateLiteral != null); assert (impliedLiteral.getParameters().size() == inversePredicateLiteral .getParameters().size()); Set<Integer> partitions = impliedLiteral.getPartitionsFromSymbols(); partitions.remove(-1); assert (partitions.size() <= 1); // Compute other literals List<DomainEq> otherLiterals = new ArrayList<DomainEq>( literalConclusions.size() - 2); for (Formula literal : literalConclusions) { if (literal.equals(impliedLiteral) || Util.makeLiteralPositive(literal).equals( inversePredicateLiteral)) continue; assert (Util.isLiteral(literal)); assert (Util.isNegativeLiteral(literal)); assert (Util.makeLiteralPositive(literal) instanceof DomainEq); otherLiterals.add((DomainEq) Util.makeLiteralPositive(literal)); } List<Formula> leftParameterEqualities = new ArrayList<Formula>( impliedLiteral.getParameters().size()); List<Formula> rightParameterEqualities = new ArrayList<Formula>( impliedLiteral.getParameters().size()); Map<Formula, VeritProofNode> proofsForParameterEqualities = new HashMap<Formula, VeritProofNode>(); List<DomainTerm> globalParameters = new ArrayList<DomainTerm>( impliedLiteral.getParameters().size()); // Compute proofs for each parameter equality for (int count = 0; count < impliedLiteral.getParameters().size(); count++) { List<DomainTerm> terms = new ArrayList<DomainTerm>(2); terms.add(inversePredicateLiteral.getParameters().get(count)); terms.add(impliedLiteral.getParameters().get(count)); DomainEq parameterEquality = DomainEq.create(terms, true); TransitivityCongruenceChain leftChain = TransitivityCongruenceChain .create(parameterEquality, otherLiterals, this); assert (leftChain.isComplete()); if (leftChain.allTermsSameColor()) { // Special case that the uncolorable literals are actually not // needed for proving parameter equality // FIXME This part wrongly assumes that the predicate is unary! // Thus, in case it's not, we fail fast. if (impliedLiteral.getParameters().size() != 1) { throw new RuntimeException( "Only unary predicates supported"); } VeritProofNode result = this .createColorablePredicateCongruenceProof(leftChain, inversePredicateLiteral, impliedLiteral); assert (this.literalConclusions .containsAll(result.literalConclusions)); return result; } TransitivityCongruenceChain rightChain = leftChain .splitAtGlobalTerm(); assert (leftChain.isComplete()); assert (rightChain.isComplete()); assert (leftChain.getEndTerm().equals(rightChain.getStart() .getTerm())); globalParameters.add(rightChain.getStart().getTerm()); VeritProofNode leftParameterEqualityProof = leftChain .toColorableProofNew(); VeritProofNode rightParameterEqualityProof = rightChain .toColorableProofNew(); assert (leftParameterEqualityProof.hasOnlyColorableLeaves()); assert (rightParameterEqualityProof.hasOnlyColorableLeaves()); leftParameterEqualities.add(leftChain.getLiteral()); rightParameterEqualities.add(rightChain.getLiteral()); proofsForParameterEqualities.put(leftChain.getLiteral(), leftParameterEqualityProof); proofsForParameterEqualities.put(rightChain.getLiteral(), rightParameterEqualityProof); } // Create global predicate instance UninterpretedPredicateInstance globalPredicateInstance = null; try { globalPredicateInstance = UninterpretedPredicateInstance.create( impliedLiteral.getFunction(), globalParameters); } catch (SuraqException exc) { throw new RuntimeException( "Unexpected Exception while creating global predicate instance.", exc); } assert (globalPredicateInstance != null); // Construct left predicate congruence List<Formula> leftConclusions = Util .invertAllLiterals(leftParameterEqualities); leftConclusions.add(NotFormula.create(inversePredicateLiteral)); leftConclusions.add(globalPredicateInstance); VeritProofNode leftProof = this.proof.addProofNodeWithFreshName( "leftPredCongr", "", VeriTToken.EQ_CONGRUENT_PRED, leftConclusions, null, null, false); assert (leftProof.isColorable()); // Construct right predicate congruence List<Formula> rightConclusions = Util .invertAllLiterals(rightParameterEqualities); rightConclusions.add(NotFormula.create(globalPredicateInstance)); rightConclusions.add(impliedLiteral); VeritProofNode rightProof = this.proof.addProofNodeWithFreshName( "rightPredCongr", "", VeriTToken.EQ_CONGRUENT_PRED, rightConclusions, null, null, false); assert (rightProof.isColorable()); VeritProofNode result = leftProof.resolveWith(rightProof, false); // Resolve the parameter equalities for (Formula parameterEquality : leftParameterEqualities) { VeritProofNode other = proofsForParameterEqualities .get(parameterEquality); assert (other != null); result = result.resolveWith(other, false); } for (Formula parameterEquality : rightParameterEqualities) { VeritProofNode other = proofsForParameterEqualities .get(parameterEquality); assert (other != null); result = result.resolveWith(other, false); } assert (this.literalConclusions.containsAll(result.literalConclusions)); return result; } /** * Used when the resulting chain in predicate leaf splitting turns out to be * colorable. * * @param chain * a colorable chain * @param inversePredicateLiteral * @param impliedLiteral * @return a colorable proof for the congruence */ private VeritProofNode createColorablePredicateCongruenceProof( TransitivityCongruenceChain chain, UninterpretedPredicateInstance inversePredicateLiteral, UninterpretedPredicateInstance impliedLiteral) { assert (chain.allTermsSameColor()); Formula parameterEquality = chain.getLiteral(); List<Formula> conclusions = new ArrayList<Formula>(3); conclusions.add(NotFormula.create(parameterEquality)); conclusions.add(NotFormula.create(inversePredicateLiteral)); conclusions.add(impliedLiteral); VeritProofNode congruenceNode = this.proof.addProofNodeWithFreshName( "predCongr", "", VeriTToken.EQ_CONGRUENT_PRED, conclusions, null, null, false); assert (congruenceNode.isColorable()); VeritProofNode parameterEqualityNode = chain.toColorableProofNew(); VeritProofNode result = parameterEqualityNode.resolveWith( congruenceNode, false); return result; } /** * Splits a predicate congruence that belongs to more than one partition. * This method (presently) only support unary predicates! * * @return a replacement for this leaf. */ @Deprecated public VeritProofNode splitPredicateLeaf() { assert (this.type.equals(VeriTToken.EQ_CONGRUENT_PRED) || this.type .equals(VeriTToken.TRANS_CONGR)); assert (this.checkCongruencePred() || this.checkTransCongr()); // Here, the last literal can also be negative. // assert (Util // .isAtom(literalConclusions.get(literalConclusions.size() - 1)) ^ Util // .isAtom(literalConclusions.get(literalConclusions.size() - 2))); // int posIndex = Util.isAtom(literalConclusions.get(literalConclusions // .size() - 1)) ? literalConclusions.size() - 1 // : literalConclusions.size() - 2; // int negIndex = posIndex == literalConclusions.size() - 1 ? posIndex - // 1 // : posIndex + 1; // UninterpretedPredicateInstance impliedLiteral = // (UninterpretedPredicateInstance) literalConclusions // .get(posIndex); // UninterpretedPredicateInstance inversePredicateLiteral = // (UninterpretedPredicateInstance) Util // .makeLiteralPositive(literalConclusions.get(negIndex)); Formula positiveLiteral = Util.findPositiveLiteral(literalConclusions); assert (positiveLiteral instanceof UninterpretedPredicateInstance); UninterpretedPredicateInstance impliedLiteral = (UninterpretedPredicateInstance) positiveLiteral; UninterpretedPredicateInstance inversePredicateLiteral = Util .findInversePredicateLiteral(impliedLiteral, literalConclusions); assert (inversePredicateLiteral != null); // FIXME this method presently only support unary predicates! if (impliedLiteral.getParameters().size() != 1 || inversePredicateLiteral.getParameters().size() != 1) { Util.printToSystemOutWithWallClockTimePrefix("ERROR: Cannot split the following leaf:"); System.out.println(this.toString()); Util.printToSystemOutWithWallClockTimePrefix("Only unary predicates supported by current implementation!"); throw new RuntimeException( "Non-unary predicate congruence in need of splitting detected."); } DomainTerm term1 = inversePredicateLiteral.getParameters().get(0); DomainTerm term2 = impliedLiteral.getParameters().get(0); List<DomainTerm> terms = new ArrayList<DomainTerm>(2); terms.add(term1); terms.add(term2); DomainEq equality = DomainEq.create(terms, true); List<DomainEq> otherLiterals = new ArrayList<DomainEq>( literalConclusions.size() - 2); for (Formula literal : literalConclusions) { if (literal.equals(impliedLiteral) || Util.makeLiteralPositive(literal).equals( inversePredicateLiteral)) continue; assert (Util.isLiteral(literal)); assert (Util.isNegativeLiteral(literal)); assert (Util.makeLiteralPositive(literal) instanceof DomainEq); otherLiterals.add((DomainEq) Util.makeLiteralPositive(literal)); } TransitivityCongruenceChain chain1 = TransitivityCongruenceChain .create(equality, otherLiterals, this); if (chain1.isColorable()) { List<Formula> conclusions = new ArrayList<Formula>(); conclusions.addAll(Util.invertAllLiterals(chain1.usedLiterals())); conclusions.add(NotFormula.create(inversePredicateLiteral)); conclusions.add(impliedLiteral); VeritProofNode result = proof.addProofNode( proof.freshNodeName("col_", ""), VeriTToken.TRANS_CONGR, conclusions, null, null, false); return result; } TransitivityCongruenceChain chain2 = chain1.splitAtGlobalTerm(); DomainTerm globalTerm = chain2.getStart().getTerm(); assert (globalTerm.getPartitionsFromSymbols().size() == 1); assert (globalTerm.getPartitionsFromSymbols().iterator().next() .equals(-1)); UninterpretedPredicateInstance globalInstancePositive; try { globalInstancePositive = UninterpretedPredicateInstance.create( impliedLiteral.getFunction(), globalTerm); } catch (SuraqException exc) { throw new RuntimeException( "Unexpected exception while constructing UninterpretedPredicateInstance.", exc); } NotFormula globalInstanceNegative = NotFormula .create(globalInstancePositive); List<Formula> literals1 = new ArrayList<Formula>(); List<DomainTerm> terms1 = new ArrayList<DomainTerm>(2); terms1.add(term1); terms1.add(globalTerm); DomainEq equality1 = DomainEq.create(terms1, true); NotFormula disequality1 = NotFormula.create(equality1); literals1.add(disequality1); literals1.add(NotFormula.create(inversePredicateLiteral)); literals1.add(globalInstancePositive); VeritProofNode predicateNode1 = new VeritProofNode( "pred1_" + this.name, VeriTToken.EQ_CONGRUENT_PRED, literals1, null, null, this.proof, false); VeritProofNode chain1Node = chain1.toColorableProofNew(); List<VeritProofNode> leftSubProofs = new ArrayList<VeritProofNode>(2); leftSubProofs.add(chain1Node); leftSubProofs.add(predicateNode1); List<Formula> literalsLeft = new ArrayList<Formula>( chain1Node.getLiteralConclusions()); literalsLeft.remove(literalsLeft.size() - 1); literalsLeft.add(NotFormula.create(inversePredicateLiteral)); literalsLeft.add(globalInstancePositive); VeritProofNode left = new VeritProofNode("predLeft_" + this.name, VeriTToken.RESOLUTION, literalsLeft, leftSubProofs, null, this.proof, false); List<Formula> literals2 = new ArrayList<Formula>(); List<DomainTerm> terms2 = new ArrayList<DomainTerm>(2); terms2.add(globalTerm); terms2.add(term2); DomainEq equality2 = DomainEq.create(terms2, true); NotFormula disequality2 = NotFormula.create(equality2); literals2.add(disequality2); literals2.add(globalInstanceNegative); literals2.add(impliedLiteral); VeritProofNode predicateNode2 = new VeritProofNode( "pred2_" + this.name, VeriTToken.EQ_CONGRUENT_PRED, literals2, null, null, this.proof, false); VeritProofNode chain2Node = chain2.toColorableProofNew(); List<VeritProofNode> rightSubProofs = new ArrayList<VeritProofNode>(2); rightSubProofs.add(chain2Node); rightSubProofs.add(predicateNode2); List<Formula> literalsRight = new ArrayList<Formula>( chain2Node.getLiteralConclusions()); literalsRight.remove(literalsRight.size() - 1); literalsRight.add(globalInstanceNegative); literalsRight.add(impliedLiteral); VeritProofNode right = new VeritProofNode("predRight_" + this.name, VeriTToken.RESOLUTION, literalsRight, rightSubProofs, null, this.proof, false); List<Formula> resultLiterals = new ArrayList<Formula>(); resultLiterals.addAll(left.getLiteralConclusions()); resultLiterals.addAll(right.getLiteralConclusions()); resultLiterals.remove(globalInstanceNegative); resultLiterals.remove(globalInstancePositive); List<VeritProofNode> resultSubProofs = new ArrayList<VeritProofNode>(2); resultSubProofs.add(left); resultSubProofs.add(right); VeritProofNode result = new VeritProofNode("predResult_" + this.name, VeriTToken.RESOLUTION, resultLiterals, resultSubProofs, null, this.proof, false); return result; } /** * @return <code>true</code> iff this node has symbols from no more than one * partition. */ public boolean isColorable() { Set<Integer> partitions = this.getPartitionsFromSymbols(); partitions.remove(-1); return partitions.size() <= 1; } /** * * @return <code>true</code> iff the leaves reachable from <code>this</code> * are all colorable. */ public boolean hasOnlyColorableLeaves() { Set<VeritProofNode> leaves = this.getLeaves(); for (VeritProofNode leaf : leaves) { if (!leaf.isColorable()) return false; } return true; } /** * Recursively replaces the given <code>inverseBadLiteral</code> in this * subtree and returns new nodes where it has been replaced by the * <code>definingLiterals</code> instead. * * @param inverseBadLiteral * the literal to replace * @param definingLiterals * the defining literals to put in instead * @param dagOperationCache * a cache for not unwinding the DAG. * @return a new node in whose subtree <code>inverseBadLiteral</code> does * not occur any more */ @Deprecated protected VeritProofNode replaceInverseBadLiteral( Formula inverseBadLiteral, List<Formula> definingLiterals, Map<VeritProofNode, VeritProofNode> dagOperationCache) { assert (inverseBadLiteral != null); assert (definingLiterals != null); assert (dagOperationCache != null); VeritProofNode result = dagOperationCache.get(this); if (result != null) return result; assert (this.literalConclusions.contains(inverseBadLiteral) || this.subProofs .size() == 2); // Recursive Calls List<VeritProofNode> newSubProofs = null; if (this.subProofs.size() != 0) { assert (this.subProofs.size() == 2); assert (this.type.equals(VeriTToken.RESOLUTION)); newSubProofs = new ArrayList<VeritProofNode>(2); for (VeritProofNode subProof : this.subProofs) { if (subProof.literalConclusions.contains(inverseBadLiteral)) newSubProofs.add(subProof.replaceInverseBadLiteral( inverseBadLiteral, definingLiterals, dagOperationCache)); else newSubProofs.add(subProof); } // If this node resolves one of the defining literals, this literal // should not be reintroduced into any nodes that are below (i.e., // closer to the root as) this node. Formula resolvingLiteral = Util.findResolvingLiteral(newSubProofs); assert (resolvingLiteral != null); definingLiterals.remove(resolvingLiteral); definingLiterals.remove(Util.invertLiteral(resolvingLiteral)); } // Prepare new node contents String newName = proof.freshNodeName("repl", this.name); List<Formula> newConclusions = new ArrayList<Formula>( this.literalConclusions.size()); for (Formula literal : this.literalConclusions) { if (literal.equals(inverseBadLiteral)) { for (Formula definingLiteral : definingLiterals) { if (!newConclusions.contains(definingLiteral)) newConclusions.add(definingLiteral); } } else { if (!newConclusions.contains(literal)) newConclusions.add(literal); } } assert (!newConclusions.contains(inverseBadLiteral)); result = this.proof.addProofNode(newName, this.type, newConclusions, newSubProofs, null, false); dagOperationCache.put(this, result); return result; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return hashCode; } /** * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (!(obj instanceof VeritProofNode)) return false; final VeritProofNode other = (VeritProofNode) obj; if (this.hashCode != other.hashCode) return false; if (this.proof != other.proof) return false; if (!this.name.equals(other.name)) return false; if (!this.type.equals(other.type)) return false; if (!(this.iargs == null ? other.iargs == null : this.iargs .equals(other.iargs))) return false; if (!(this.subProofs == null ? other.subProofs == null : this.subProofs .equals(other.subProofs))) return false; if (!(this.parents == null ? other.parents == null : this.parents .equals(other.parents))) return false; return true; } /** * @param <code>checkProofEnabled</code> the new value for * <code>checkProofEnabled</code> */ public static void setCheckProofNodesEnabled(boolean checkProofNodesEnabled) { if (checkProofNodesEnabled) Util.printToSystemOutWithWallClockTimePrefix("Activating proof nodes checks."); else Util.printToSystemOutWithWallClockTimePrefix("Deactivating proof nodes checks."); VeritProofNode.checkProofNodesEnabled = checkProofNodesEnabled; } /** * @param other * @param removeSubproofsOfTheoryLemmas * @return the resolution of <code>this</code> with <code>other</code>. */ public VeritProofNode resolveWith(VeritProofNode other, boolean removeSubproofsOfTheoryLemmas) { assert (this != other); assert (this.proof == other.proof); List<VeritProofNode> subproofs = new ArrayList<VeritProofNode>(2); subproofs.add(this); subproofs.add(other); List<Formula> conlusions = Util.findConclusionsOfResolution( this.literalConclusions, other.literalConclusions); VeritProofNode result = this.proof.addProofNodeWithFreshName("res", "", VeriTToken.RESOLUTION, conlusions, subproofs, null, removeSubproofsOfTheoryLemmas); return result; } /** * Computes the set of leaves reachable from <code>this</code>. Computation * is recursive and DAG-aware. * * @return the set of leaves reachable from this node */ public Set<VeritProofNode> getLeaves() { Set<VeritProofNode> reachableNodes = this.getReachableNodes(); Set<VeritProofNode> result = new HashSet<VeritProofNode>(); for (VeritProofNode node : reachableNodes) { if (node.isLeaf()) result.add(node); } return result; } /** * Computes the set of nodes reachable from <code>this</code>. Computation * is recursive and DAG-aware. * * @return the set of nodes reachable from this node */ public Set<VeritProofNode> getReachableNodes() { Set<VeritProofNode> result = new HashSet<VeritProofNode>(); this.getReachableNodesInternal(result); return result; } /** * * @param result * reachable nodes will be added to this set. */ private void getReachableNodesInternal(Set<VeritProofNode> result) { assert (result != null); result.add(this); for (VeritProofNode child : this.subProofs) { if (!result.contains(child)) child.getReachableNodesInternal(result); } } /** * Searches for negated reflexivities in this node, creates corresponding * reflexivity leaves, and resolves them. * * @return a node with all reflexivities resolved. */ public VeritProofNode resolveNegatedReflexivities() { List<Formula> reflexivities = new ArrayList<Formula>( this.literalConclusions.size()); for (Formula literal : this.literalConclusions) { if (!Util.isNegatedReflexivity(literal)) continue; reflexivities.add(Util.makeLiteralPositive(literal)); } VeritProofNode result = this; for (Formula reflexivity : reflexivities) { VeritProofNode reflexivityNode = this .createReflexivity(reflexivity); result = result.resolveWith(reflexivityNode, false); } return result; } /** * Creates a reflexivity proof for the given reflexivity. Adds it to the * proof of <code>this</code> node. * * @param reflexivity * @return a reflexivity leaf */ public VeritProofNode createReflexivity(Formula reflexivity) { assert (Util.isReflexivity(reflexivity)); List<Formula> conclusions = new ArrayList<Formula>(1); conclusions.add(reflexivity); VeritProofNode result = this.proof.addProofNodeWithFreshName("reflex", "", VeriTToken.EQ_REFLEXIVE, conclusions, null, null, false); return result; } /** * @param writer * @param alreadyWritten * @param tagContainer * @throws IOException */ protected void writeOut(BufferedWriter writer, Set<VeritProofNode> alreadyWritten, HashTagContainer tagContainer) throws IOException { assert (alreadyWritten != null); assert (tagContainer != null); if (alreadyWritten.contains(this)) return; // First write parents for (VeritProofNode subproof : subProofs) subproof.writeOut(writer, alreadyWritten, tagContainer); // Now write out this node writer.append("(set ").append(this.name).append(" (") .append(this.type.toString()).append(' '); if (subProofs.size() > 0) { assert (this.type.equals(VeriTToken.RESOLUTION)); writer.append(":clauses ("); for (VeritProofNode subproof : subProofs) { assert (alreadyWritten.contains(subproof)); writer.append(subproof.name).append(' '); } writer.append(')'); } writer.append(" :conclusion ("); for (Formula literal : literalConclusions) literal.writeOut(writer, tagContainer, true); writer.append(")))\n"); alreadyWritten.add(this); } /** * @return the partition of this leaf based on its proof's * <code>partitionsOfLeaves</code> map */ public Integer getLeafPartition() { assert (this.isLeaf()); return this.proof.getPartitionOfLeaf(this); } /** * Computes the partial interpolant for this node, stores it to the given * map and returns it. * * Since partitions in the symbols and leaf nodes are counted from 1 to 2^n, * we will have to subtract 1 every time before we determine whether it's * even or odd. That is because the values of control signals iterate from 0 * to (2^n)-1. After subtracting 1, even partitions represent "A" (i.e., * where the control signal is 0), and odd partitions represent "B" (i.e., * where the control signal is 1). * * @param partialInterpolants * map of partial interpolants to query before making recursive * calls * @return the partial interpolant for this node */ public Formula interpolateEvenVsOddPartitions( Map<VeritProofNode, Formula> partialInterpolants) { Formula result = partialInterpolants.get(this); if (result != null) return result; if (this.subProofs.isEmpty()) { result = leafInterpolant(); } else { result = resolutionInterpolant(partialInterpolants); assert (this.proof.partitions != null && SuraqOptions.getInstance() .getCheckResolutionInterpolants() ? Util .checkPartialInterpolant(result, getConclusionsAsOrFormula(), this.proof.partitions) : true); } assert (result != null); partialInterpolants.put(this, result); return result; } /** * Computes the partial interpolant for a predicate leaf. <strong> Does not * work correctly!</strong> * * @return a partial interpolant for this leaf. */ @SuppressWarnings("unused") @Deprecated private Formula predicateLeafInterpolant() { // Compute implied literal and inverse predicate literal Formula positiveLiteral = Util.getImpliedLiteral(literalConclusions); assert (positiveLiteral instanceof UninterpretedPredicateInstance); UninterpretedPredicateInstance impliedLiteral = (UninterpretedPredicateInstance) positiveLiteral; UninterpretedPredicateInstance inversePredicateLiteral = Util .findInversePredicateLiteral(impliedLiteral, literalConclusions); assert (inversePredicateLiteral != null); assert (impliedLiteral.getParameters().size() == inversePredicateLiteral .getParameters().size()); Set<Integer> partitions = impliedLiteral.getPartitionsFromSymbols(); partitions.remove(-1); assert (partitions.size() <= 1); // Compute other literals List<DomainEq> otherLiterals = new ArrayList<DomainEq>( literalConclusions.size() - 2); for (Formula literal : literalConclusions) { if (literal.equals(impliedLiteral) || Util.makeLiteralPositive(literal).equals( inversePredicateLiteral)) continue; assert (Util.isLiteral(literal)); assert (Util.isNegativeLiteral(literal)); assert (Util.makeLiteralPositive(literal) instanceof DomainEq); otherLiterals.add((DomainEq) Util.makeLiteralPositive(literal)); } List<Formula> leftParameterEqualities = new ArrayList<Formula>( impliedLiteral.getParameters().size()); List<Formula> rightParameterEqualities = new ArrayList<Formula>( impliedLiteral.getParameters().size()); Map<Formula, Formula> interpolantsForParameterEqualities = new HashMap<Formula, Formula>( 2 * impliedLiteral.getParameters().size()); List<DomainTerm> globalParameters = new ArrayList<DomainTerm>( impliedLiteral.getParameters().size()); // Compute interpolants for each parameter equality for (int count = 0; count < impliedLiteral.getParameters().size(); count++) { List<DomainTerm> terms = new ArrayList<DomainTerm>(2); terms.add(inversePredicateLiteral.getParameters().get(count)); terms.add(impliedLiteral.getParameters().get(count)); DomainEq parameterEquality = DomainEq.create(terms, true); if (Util.isReflexivity(parameterEquality)) { Set<Integer> parts = parameterEquality .getPartitionsFromSymbols(); parts.remove(-1); assert (parts.size() <= 1); if (parts.size() == 1) { int part = parts.iterator().next(); if ((part - 1) % 2 == 0) { // A-literal interpolantsForParameterEqualities.put( parameterEquality, PropositionalConstant.create(false)); } else { // B-literal interpolantsForParameterEqualities.put( parameterEquality, PropositionalConstant.create(true)); } } else { // global literal assert (parts.isEmpty()); // Arbitrary choice interpolantsForParameterEqualities.put(parameterEquality, PropositionalConstant.create(false)); } leftParameterEqualities.add(parameterEquality); rightParameterEqualities.add(parameterEquality); globalParameters.add(terms.get(0)); continue; } TransitivityCongruenceChain leftChain = TransitivityCongruenceChain .create(parameterEquality, otherLiterals, this); assert (leftChain.isComplete()); if (!leftChain.isColorable()) { TransitivityCongruenceChain rightChain = leftChain .splitAtGlobalTerm(); assert (leftChain.isComplete()); assert (rightChain.isComplete()); assert (leftChain.getEndTerm().equals(rightChain.getStart() .getTerm())); globalParameters.add(rightChain.getStart().getTerm()); Formula leftParameterEqualityInterpolant = leftChain .fuchsEtAlInterpolant(); Formula rightParameterEqualityInterpolant = rightChain .fuchsEtAlInterpolant(); leftParameterEqualities.add(leftChain.getLiteral()); rightParameterEqualities.add(rightChain.getLiteral()); interpolantsForParameterEqualities.put(leftChain.getLiteral(), leftParameterEqualityInterpolant); interpolantsForParameterEqualities.put(rightChain.getLiteral(), rightParameterEqualityInterpolant); } else { DomainTerm start = leftChain.getStart().getTerm(); if (Util.isGlobal(start)) { globalParameters.add(start); Formula leftEquality = Util.createReflexivity(start); leftParameterEqualities.add(leftEquality); // Arbitrarily choose false as interpolant interpolantsForParameterEqualities.put(leftEquality, PropositionalConstant.create(false)); Formula rightEquality = leftChain.getLiteral(); rightParameterEqualities.add(rightEquality); interpolantsForParameterEqualities.put(rightEquality, leftChain.fuchsEtAlInterpolant()); } else { DomainTerm end = leftChain.getEndTerm(); if (Util.isGlobal(end)) { globalParameters.add(end); Formula rightEquality = Util.createReflexivity(start); rightParameterEqualities.add(rightEquality); // Arbitrarily choose false as interpolant interpolantsForParameterEqualities.put(rightEquality, PropositionalConstant.create(false)); Formula leftEquality = leftChain.getLiteral(); leftParameterEqualities.add(leftEquality); interpolantsForParameterEqualities.put(leftEquality, leftChain.fuchsEtAlInterpolant()); } else { DomainTerm globalTerm = leftChain.findGlobalTerm(); TransitivityCongruenceChain rightChain = leftChain .split(globalTerm); assert (leftChain.isComplete()); assert (rightChain.isComplete()); assert (leftChain.getEndTerm().equals(rightChain .getStart().getTerm())); globalParameters.add(rightChain.getStart().getTerm()); Formula leftParameterEqualityInterpolant = leftChain .fuchsEtAlInterpolant(); Formula rightParameterEqualityInterpolant = rightChain .fuchsEtAlInterpolant(); leftParameterEqualities.add(leftChain.getLiteral()); rightParameterEqualities.add(rightChain.getLiteral()); interpolantsForParameterEqualities.put( leftChain.getLiteral(), leftParameterEqualityInterpolant); interpolantsForParameterEqualities.put( rightChain.getLiteral(), rightParameterEqualityInterpolant); } } } } // Create global predicate instance UninterpretedPredicateInstance globalPredicateInstance = null; try { globalPredicateInstance = UninterpretedPredicateInstance.create( impliedLiteral.getFunction(), globalParameters); } catch (SuraqException exc) { throw new RuntimeException( "Unexpected Exception while creating global predicate instance.", exc); } assert (globalPredicateInstance != null); // Left side interpolants Formula leftInterpolant = NotFormula.create(globalPredicateInstance); for (Formula literal : leftParameterEqualities) { Formula pos = interpolantsForParameterEqualities.get(literal); assert (pos != null); leftInterpolant = resolutionInterpolant(literal, pos, leftInterpolant); } // Right side of interpolants Formula rightInterpolant = globalPredicateInstance; for (Formula literal : rightParameterEqualities) { Formula pos = interpolantsForParameterEqualities.get(literal); assert (pos != null); rightInterpolant = resolutionInterpolant(literal, pos, rightInterpolant); } Formula result = resolutionInterpolant(globalPredicateInstance, leftInterpolant, rightInterpolant); return result; } /** * Computes the partial interpolant for a predicate leaf. * * @return a partial interpolant for this leaf. */ private Formula predicateLeafInterpolantNew() { // Compute implied literal and inverse predicate literal Formula positiveLiteral = Util.getImpliedLiteral(literalConclusions); assert (positiveLiteral instanceof UninterpretedPredicateInstance); UninterpretedPredicateInstance impliedLiteral = (UninterpretedPredicateInstance) positiveLiteral; UninterpretedPredicateInstance inversePredicateLiteral = Util .findInversePredicateLiteral(impliedLiteral, literalConclusions); assert (inversePredicateLiteral != null); assert (impliedLiteral.getParameters().size() == inversePredicateLiteral .getParameters().size()); Set<Integer> partitions = impliedLiteral.getPartitionsFromSymbols(); partitions.remove(-1); assert (partitions.size() <= 1); // Compute other literals List<DomainEq> otherLiterals = new ArrayList<DomainEq>( literalConclusions.size() - 2); for (Formula literal : literalConclusions) { if (literal.equals(impliedLiteral) || Util.makeLiteralPositive(literal).equals( inversePredicateLiteral)) continue; assert (Util.isLiteral(literal)); assert (Util.isNegativeLiteral(literal)); assert (Util.makeLiteralPositive(literal) instanceof DomainEq); otherLiterals.add((DomainEq) Util.makeLiteralPositive(literal)); } // Compute coloring of predicate instances (as they would occur in the // unsat cube) // A=0, B=1, G=-1 int positiveColor; partitions.clear(); partitions = inversePredicateLiteral.getPartitionsFromSymbols(); partitions.remove(-1); assert (partitions.size() <= 1); if (partitions.isEmpty()) positiveColor = -1; else positiveColor = (partitions.iterator().next() - 1) % 2; int negativeColor; partitions.clear(); partitions = impliedLiteral.getPartitionsFromSymbols(); partitions.remove(-1); assert (partitions.size() <= 1); if (partitions.isEmpty()) negativeColor = -1; else negativeColor = (partitions.iterator().next() - 1) % 2; if (positiveColor >= 0 && negativeColor >= 0 && positiveColor != negativeColor) { // A-B or B-A predicate congruence boolean polarityA = positiveColor == 0; // in the cube, "negative" // is "positive" and vice // versa! List<Formula> conjuncts = new ArrayList<Formula>(impliedLiteral .getParameters().size() + 1); Set<TransitivityCongruenceChain> bPremises = new HashSet<TransitivityCongruenceChain>(); List<DomainTerm> globalParameters = new ArrayList<DomainTerm>( impliedLiteral.getParameters().size()); for (int count = 0; count < impliedLiteral.getParameters().size(); count++) { DomainTerm paramA = (positiveColor == 0 ? inversePredicateLiteral : impliedLiteral).getParameters().get(count); DomainTerm paramB = (positiveColor == 1 ? inversePredicateLiteral : impliedLiteral).getParameters().get(count); TransitivityCongruenceChain chain = TransitivityCongruenceChain .create(paramA, paramB, otherLiterals, this); // We need the right half for interpolation, the left half for // computing B-premises TransitivityCongruenceChain leftHalfChain = chain; TransitivityCongruenceChain rightHalfChain = chain .splitAtGlobalTerm(); DomainTerm globalParameter = rightHalfChain.getStart() .getTerm(); assert (globalParameter.equals(leftHalfChain.getEndTerm())); globalParameters.add(globalParameter); Formula chainInterpolant = rightHalfChain .fuchsEtAlInterpolant(); conjuncts.add(chainInterpolant); bPremises.addAll(leftHalfChain.getBPremises()); } List<Formula> bPremiseEqualities = new ArrayList<Formula>( bPremises.size()); for (TransitivityCongruenceChain bPremise : bPremises) bPremiseEqualities.add(bPremise.getLiteral()); AndFormula bPremiseEqualitiesFormula = AndFormula .generate(bPremiseEqualities); UninterpretedPredicateInstance globalInstance; try { globalInstance = UninterpretedPredicateInstance.create( impliedLiteral.getFunction(), globalParameters); } catch (SuraqException exc) { throw new RuntimeException(exc); } Formula rightSide = polarityA ? globalInstance : NotFormula .create(globalInstance); ImpliesFormula lastConjunct = ImpliesFormula.create( bPremiseEqualitiesFormula, rightSide); conjuncts.add(lastConjunct); AndFormula result = AndFormula.generate(conjuncts); return result; } assert (positiveColor == negativeColor || positiveColor == -1 || negativeColor == -1); if (positiveColor == 0 || negativeColor == 0) { // A-G, G-A, or A-A congruence assert (positiveColor != 1 && negativeColor != 1); Set<TransitivityCongruenceChain> bPremises = new HashSet<TransitivityCongruenceChain>(); for (int count = 0; count < impliedLiteral.getParameters().size(); count++) { DomainTerm param1 = inversePredicateLiteral.getParameters() .get(count); DomainTerm param2 = impliedLiteral.getParameters().get(count); TransitivityCongruenceChain chain = TransitivityCongruenceChain .create(param1, param2, otherLiterals, this); bPremises.addAll(chain.getBPremises()); } List<Formula> conjuncts = new ArrayList<Formula>( bPremises.size() + 1); List<Formula> bPremiseEqualities = new ArrayList<Formula>( bPremises.size()); for (TransitivityCongruenceChain bPremise : bPremises) { Formula interpolant = bPremise.fuchsEtAlInterpolant(); conjuncts.add(interpolant); bPremiseEqualities.add(bPremise.getLiteral()); } AndFormula bPremiseEqualiesFormula = AndFormula .generate(bPremiseEqualities); NotFormula lastConjunct = NotFormula .create(bPremiseEqualiesFormula); conjuncts.add(lastConjunct); AndFormula result = AndFormula.generate(conjuncts); return result; } assert (positiveColor == 1 || negativeColor == 1); assert (positiveColor != 0 && negativeColor != 0); // B-G, G-B, or B-B congruence List<Formula> conjuncts = new ArrayList<Formula>(impliedLiteral .getParameters().size()); for (int count = 0; count < impliedLiteral.getParameters().size(); count++) { DomainTerm param1 = inversePredicateLiteral.getParameters().get( count); DomainTerm param2 = impliedLiteral.getParameters().get(count); TransitivityCongruenceChain chain = TransitivityCongruenceChain .create(param1, param2, otherLiterals, this); Formula interpolant = chain.fuchsEtAlInterpolant(); conjuncts.add(interpolant); } AndFormula result = AndFormula.generate(conjuncts); return result; } /** * Computes the partial interpolant for a leaf. Follows Pudlak's * interpolation system for input clauses and theory lemmas with just one * color. Follows Fuchs et al. for theory lemmas with more than one color. * * @return a partial interpolant for this leaf. */ private Formula leafInterpolant() { assert (subProofs.isEmpty()); assert (type.equals(VeriTToken.INPUT) || type .equals(VeriTToken.TRANS_CONGR)); Formula result = null; Set<Integer> partitions = this.getPartitionsFromSymbols(); partitions.remove(-1); if (partitions.size() > 1) { // uncolorable theory lemma assert (type.equals(VeriTToken.TRANS_CONGR)); Formula impliedLiteral = Util.getImpliedLiteral(literalConclusions); if (impliedLiteral instanceof UninterpretedPredicateInstance) result = predicateLeafInterpolantNew(); else { TransitivityCongruenceChain chain = TransitivityCongruenceChain .create(this); result = chain.fuchsEtAlInterpolant(); } assert (SuraqOptions.getInstance().getCheckLeafInterpolants() ? Util .checkTheoryLemmaInterpolant(result, getConclusionsAsOrFormula()) : true); } else { assert (partitions.size() <= 1); int partition; if (partitions.isEmpty()) { // Leaf with only global symbols if (this.type.equals(VeriTToken.INPUT)) partition = this.proof.getPartitionOfLeaf(this); else // arbitrary choice partition = 1; } else { assert (partitions.size() == 1); partition = partitions.iterator().next(); } if ((partition - 1) % 2 == 0) { // "A" clause result = PropositionalConstant.create(false); } else { // "B" clause result = PropositionalConstant.create(true); } } assert (result != null); return result; } /** * This method follows Pudlak's interpolation system. * * @param partialInterpolants * for recursive calls * @return even vs. odd partial interpolant for a resolution node. */ private Formula resolutionInterpolant( Map<VeritProofNode, Formula> partialInterpolants) { assert (this.type.equals(VeriTToken.RESOLUTION)); assert (this.subProofs.size() == 2); Formula resolvingLiteral = findResolvingLiteral(); VeritProofNode posChild = null; VeritProofNode negChild = null; if (subProofs.get(0).getLiteralConclusions().contains(resolvingLiteral)) { posChild = subProofs.get(0); assert (subProofs.get(1).getLiteralConclusions().contains(Util .invertLiteral(resolvingLiteral))); negChild = subProofs.get(1); } else { assert (subProofs.get(1).getLiteralConclusions() .contains(resolvingLiteral)); posChild = subProofs.get(1); assert (subProofs.get(0).getLiteralConclusions().contains(Util .invertLiteral(resolvingLiteral))); negChild = subProofs.get(0); } assert (posChild != null); assert (negChild != null); Formula posPartialInterpolant = posChild .interpolateEvenVsOddPartitions(partialInterpolants); Formula negPartialInterpolant = negChild .interpolateEvenVsOddPartitions(partialInterpolants); Formula result = resolutionInterpolant(resolvingLiteral, posPartialInterpolant, negPartialInterpolant); assert (result != null); return result; } private Formula resolutionInterpolant(Formula resolvingLiteral, Formula posPartialInterpolant, Formula negPartialInterpolant) { Formula result = null; Set<Integer> partitions = resolvingLiteral.getPartitionsFromSymbols(); partitions.remove(-1); assert (partitions.size() <= 1); if (partitions.size() == 0) { // global literal List<Formula> disjunctsPos = new ArrayList<Formula>(2); List<Formula> disjunctsNeg = new ArrayList<Formula>(2); List<Formula> conjuncts = new ArrayList<Formula>(2); disjunctsPos.add(resolvingLiteral); disjunctsPos.add(posPartialInterpolant); disjunctsNeg.add(Util.invertLiteral(resolvingLiteral)); disjunctsNeg.add(negPartialInterpolant); OrFormula posConjunct = OrFormula.generate(disjunctsPos); OrFormula negConjunct = OrFormula.generate(disjunctsNeg); conjuncts.add(posConjunct); conjuncts.add(negConjunct); result = AndFormula.generate(conjuncts); } else { assert (partitions.size() == 1); int partition = partitions.iterator().next() - 1; if (partition % 2 == 0) { // even ("A") literal List<Formula> disjuncts = new ArrayList<Formula>(2); disjuncts.add(posPartialInterpolant); disjuncts.add(negPartialInterpolant); result = OrFormula.generate(disjuncts); } else { // odd ("B") literal List<Formula> conjuncts = new ArrayList<Formula>(2); conjuncts.add(posPartialInterpolant); conjuncts.add(negPartialInterpolant); result = AndFormula.generate(conjuncts); } } assert (result != null); return result; } }