/** * Author: Bettina Koenighofer <bettina.koenighofer@iaik.tugraz.at> */ package at.iaik.suraq.smtlib; import java.io.Serializable; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import at.iaik.suraq.exceptions.IncomparableTermsException; import at.iaik.suraq.sexp.SExpression; import at.iaik.suraq.sexp.SExpressionConstants; 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.DomainVariable; import at.iaik.suraq.smtlib.formula.EqualityFormula; import at.iaik.suraq.smtlib.formula.Formula; import at.iaik.suraq.smtlib.formula.FormulaTerm; import at.iaik.suraq.smtlib.formula.FunctionMacro; import at.iaik.suraq.smtlib.formula.NotFormula; import at.iaik.suraq.smtlib.formula.OrFormula; import at.iaik.suraq.smtlib.formula.PropositionalEq; import at.iaik.suraq.smtlib.formula.PropositionalTerm; import at.iaik.suraq.smtlib.formula.PropositionalVariable; import at.iaik.suraq.smtlib.formula.Term; import at.iaik.suraq.smtlib.formula.UninterpretedFunction; import at.iaik.suraq.smtlib.formula.UninterpretedFunctionInstance; import at.iaik.suraq.smtlib.formula.UninterpretedPredicateInstance; import at.iaik.suraq.smtsolver.SMTSolver; import at.iaik.suraq.util.DagOperationManager; import at.iaik.suraq.util.ImmutableSet; import at.iaik.suraq.util.SoftReferenceWithEquality; import at.iaik.suraq.util.Timer; import at.iaik.suraq.util.Util; /** * @author Bettina Koenighofer <bettina.koenighofer@iaik.tugraz.at> * */ public class Z3Proof implements Comparable<Z3Proof>, Serializable { /** * */ private static final long serialVersionUID = 7871807524124015582L; protected static final DecimalFormat myFormatter = new DecimalFormat( "###,###,###"); /** * lists operations which already traversed this node. */ protected Set<Long> visitedByOperation = new HashSet<Long>(); @Deprecated protected Set<String> assertedStr = new HashSet<String>(); /** * The proof type. */ protected Token proofType; /** * The list of sub proofs. */ protected List<Z3Proof> subProofs; /** * The set of parents of this node. */ protected Set<SoftReferenceWithEquality<Z3Proof>> parents = null; /** * This formula is the consequent of this proof. It should either be an * <code>OrFormula</code> or the constant formula <code>false</code>. */ protected Formula consequent; /** * Flag that indicates from which assert an asserted node comes. Only valid * for nodes of type ASSERTED. */ protected int assertPartition = 0; /** * A unique ID of the node. */ protected final int id; /** * Specifies if this proof object is an axiom introduced during * transformation. */ protected boolean axiom = false; private static int instanceCounter = 1; /** * A cache for hypotheses on which this node depends. */ protected ImmutableSet<Z3Proof> hypothesesCache = null; /** * A cache for hypothesis formulas. */ protected ImmutableSet<Formula> hypothesisFormulasCache = null; /** * Store the modification counter at which the hypotheses cache was last * updated * * @deprecated use hypCacheDirty flag instead */ @Deprecated protected long hypCacheModCount = 0; /** * Indicated whether the hypotheses cache is dirty or usable. */ protected boolean hypCacheDirty = true; /** * The set of literals that have become obsolete by proof rewriting. * Resolutions with these literals are no longer necessary. Literals are * added in the reverse polarity of that in which they would originally have * occurred in this node. I.e., the polarity is the same as that of the * corresponding hypothesis (that is no longer present), and also the same * of the one that is supposed to occur in another node with which this one * will be resolved. I.e., the clause that contains the literal in the same * polarity as in this set is the obsolete clause. The set should contain * the literals directly, not their unit clauses. */ protected ImmutableSet<Formula> obsoleteLiterals = null; /** * Is incremented every time a structural change is made to the proof DAG, * which might affect the hypotheses cache. * */ protected static long hypModCount = 0; private static final Timer debugGetHypothesesTimer = new Timer(); private static long debugGetHypothesesCallCounter = 0; private static long debugGetHypothesesCallUsingCacheCounter = 0; private static long debugLastGetHypothesesStatsTime = 0; private static final void printDebugGetHypothesesTimerStats() { if ((Z3Proof.debugGetHypothesesTimer.getTotalTimeMillis() - Z3Proof.debugLastGetHypothesesStatsTime) > 60000) { Z3Proof.debugLastGetHypothesesStatsTime = Z3Proof.debugGetHypothesesTimer .getTotalTimeMillis(); System.out .println("INFO: Spent a total of " + Z3Proof.debugGetHypothesesTimer + " on " + Z3Proof.myFormatter .format(Z3Proof.debugGetHypothesesCallCounter) + " calls (" + Z3Proof.myFormatter .format(Z3Proof.debugGetHypothesesCallUsingCacheCounter) + " calls using cache) to get hypotheses(formulas)."); } } /** * * Constructs a new <code>Z3Proof</code>. * */ public Z3Proof() { this.proofType = null; this.subProofs = new ArrayList<Z3Proof>(); this.parents = new HashSet<SoftReferenceWithEquality<Z3Proof>>(); this.consequent = null; this.id = Z3Proof.instanceCounter++; if (this.id == 1063) assert (this.id != 1468192489); if (this.id % 10000 == 0) { String output = Z3Proof.myFormatter.format(this.id); Util.printToSystemOutWithWallClockTimePrefix("INFO: Created the " + output + " proof node."); } // this.assertPartition = -1; } /** * * Constructs a new <code>Z3Proof</code>. * * @param proofType * the type of the proof * @param subProofs * the list of all subproofs * @param consequent * the formula which has to be proved */ public Z3Proof(Token proofType, Z3Proof subProof1, Z3Proof subProof2, Formula consequent) { if (proofType == null) throw new RuntimeException("null prooftype not allowed!"); this.proofType = proofType; this.subProofs = new ArrayList<Z3Proof>(); this.parents = new HashSet<SoftReferenceWithEquality<Z3Proof>>(); if (subProof1 != null) this.subProofs.add(subProof1); if (subProof2 != null) this.subProofs.add(subProof2); this.consequent = consequent; this.id = Z3Proof.instanceCounter++; if (this.id == 1063) assert (this.id != 1468192489); if (this.id % 10000 == 0) { String output = Z3Proof.myFormatter.format(this.id); Util.printToSystemOutWithWallClockTimePrefix("INFO: Created the " + output + " proof node."); } this.setAssertPartition(); // assert (this.checkZ3ProofNode()); } /** * * Constructs a new <code>Z3Proof</code>. * * @param proofType * the type of the proof * @param subProofs * the list of all subproofs * @param consequent * the formula which has to be proved */ public Z3Proof(Token proofType, List<? extends Z3Proof> subProofs, Formula consequent) { this(proofType, subProofs, consequent, 0, false); this.setAssertPartition(); } /** * * Constructs a new <code>Z3Proof</code>. * * @param proofType * the type of the proof * @param subProofs * the list of all subproofs * @param consequent * the formula which has to be proved * @param partition * the partition for this node. (Only used if * <code>proofType</code> equals <code>ASSERTED</code>.) * @param axiom * <code>true</code> if this is an axiom. */ public Z3Proof(Token proofType, List<? extends Z3Proof> subProofs, Formula consequent, int partition, boolean axiom) { this.axiom = axiom; if (proofType == null) throw new RuntimeException("null prooftype not allowed!"); this.proofType = proofType; assert (subProofs != null); this.subProofs = new ArrayList<Z3Proof>(); this.parents = new HashSet<SoftReferenceWithEquality<Z3Proof>>(); this.subProofs.addAll(subProofs); this.consequent = consequent; this.id = Z3Proof.instanceCounter++; if (this.id == 1063) assert (this.id != 1468192489); if (this.id % 10000 == 0) { String output = Z3Proof.myFormatter.format(this.id); Util.printToSystemOutWithWallClockTimePrefix("INFO: Created the " + output + " proof node."); } if (this.proofType.equals(SExpressionConstants.ASSERTED)) { if (partition > 0) assertPartition = partition; else { Set<Integer> symbolPartitions = consequent .getPartitionsFromSymbols(); symbolPartitions.remove(-1); if (symbolPartitions.isEmpty()) assertPartition = 1; // arbitrary choice else { if (symbolPartitions.size() != 1) assert (false); assertPartition = symbolPartitions.iterator().next(); } } assert (assertPartition > 0); } // assert (this.checkZ3ProofNode()); } private void setAssertPartition() { if (proofType.equals(SExpressionConstants.ASSERTED)) { Set<Integer> partitions = consequent.getPartitionsFromSymbols(); assert (partitions.size() <= 2); partitions.remove(new Integer(-1)); if (partitions.size() > 0) { assert (partitions.size() == 1); assertPartition = partitions.iterator().next(); } else assertPartition = 1; // arbitrary choice for globals assert (assertPartition > 0); } } /** * @param proof */ protected void takeValuesFrom(Z3Proof proof) { this.axiom = proof.axiom; this.subProofs = new ArrayList<Z3Proof>(proof.subProofs); // parents are not copied. // This node only takes the "values" of the other node. // its position in the DAG stays the same. Z3Proof.hypModCount++; this.hypCacheModCount = proof.hypCacheModCount; this.hypCacheDirty = proof.hypCacheDirty; if (this.hypCacheDirty) this.markHypCacheDirty(); this.hypothesesCache = proof.hypothesesCache; this.hypothesisFormulasCache = proof.hypothesisFormulasCache; this.proofType = proof.proofType; this.consequent = proof.consequent; this.assertPartition = proof.assertPartition; assert (this.assertPartition > 0 || !this.proofType .equals(SExpressionConstants.ASSERTED)); } public void addParent(Z3Proof parent) { SoftReferenceWithEquality<Z3Proof> ref = new SoftReferenceWithEquality<Z3Proof>( parent); if (parents == null) parents = new HashSet<SoftReferenceWithEquality<Z3Proof>>(); if (!parents.contains(ref)) parents.add(ref); } public void addParents(Collection<? extends Z3Proof> parents) { for (Z3Proof parent : parents) this.addParent(parent); } public void removeParent(Z3Proof parent) { assert (parents != null); parents.remove(new SoftReferenceWithEquality<Z3Proof>(parent)); } public Set<Z3Proof> getParents() { if (parents == null) return new HashSet<Z3Proof>(0); Set<Z3Proof> result = new HashSet<Z3Proof>(); for (SoftReferenceWithEquality<Z3Proof> ref : parents) result.add(ref.get()); return result; } /** * Creates a new <code>Z3Proof</code> which is of the same type as * <code>this</code> object and has the given subProofs and consequent. * * @param subProofs * List of sub-proofs * @param consequent * the consequent * @return a new <code>Z3Proof</code> with the same type as * <code>this</code>. */ @Deprecated protected Z3Proof create(List<Z3Proof> subProofs, Formula consequent) { List<Z3Proof> newSubProofs = new ArrayList<Z3Proof>(); for (Z3Proof subProof : subProofs) { newSubProofs.add(subProof); } Z3Proof instance = new Z3Proof(Token.generate(this.proofType), newSubProofs, consequent); return instance; } /** * Returns the type of the proof. * * @return the <code>proofType</code> */ public Token getProofType() { return this.proofType; } /** * Returns the list of sub proofs * * @return the <code>subProofs</code> (live list!) */ public List<Z3Proof> getSubProofs() { return this.subProofs; } /** * Returns the formula which has to be proved * * @return the <code>consequent</code> */ public Formula getConsequent() { return this.consequent; } public void addObsoleteLiteral(Formula literal) { assert (Util.isLiteral(literal)); obsoleteLiterals = obsoleteLiterals.addToCopy(literal); } /** * Converts this proof into an s-expression compatible with SMTLIBv2. Only * the proof itself is converted. No variable/function/macro declarations * are included. * * @return this proof as an SMTLIBv2 s-expression. */ public SExpression toSmtlibV2() { List<SExpression> children = new ArrayList<SExpression>(); children.add(this.proofType); for (Z3Proof subProof : this.subProofs) children.add(subProof.toSmtlibV2()); children.add(this.consequent.toSmtlibV2()); return new SExpression(children); } /** * * @see java.lang.Object#toString() */ @Override public String toString() { return this.toSmtlibV2().toString(); } /** * Returns the elements assert-partition. * * @return assert-partition of the element. */ public Set<Integer> getPartitionsFromSymbols() { long operationId = DagOperationManager.startDAGOperation(); Set<Integer> result = this .getPartitionsFromSymbolsRecursion(operationId); DagOperationManager.endDAGOperation(operationId); return result; } private Set<Integer> getPartitionsFromSymbolsRecursion(long operationId) { visitedByDAGOperation(operationId); Set<Integer> partitions = consequent.getPartitionsFromSymbols(); for (Z3Proof proof : subProofs) { if (proof.wasVisitedByDAGOperation(operationId)) continue; partitions.addAll(proof .getPartitionsFromSymbolsRecursion(operationId)); } return partitions; } /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { return this.id; } /** * Compares the object references (pointers). This helps for distinguishing * trees from DAGs. * * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { return (this == obj); } /** * This method is based on just looking at nodes with type ASSERTED and * checking from which assert statement they originate (according to their * own claim). Symbols are not checked. * * This method and the returned set does not have a notion of "global". If * the subtree is just from one assert statement, the cardinality of the * returned set should be 1. * * @return */ public Set<Integer> getPartitionsFromAsserts() { long operationId = DagOperationManager.startDAGOperation(); Set<Integer> result = new HashSet<Integer>(); this.getPartitionsFromAssertsRecursion(operationId, result); DagOperationManager.endDAGOperation(operationId); return result; } private void getPartitionsFromAssertsRecursion(long operationId, Set<Integer> result) { visitedByDAGOperation(operationId); assert (result != null); for (Z3Proof z3Proofchild : this.subProofs) { if (z3Proofchild.wasVisitedByDAGOperation(operationId)) continue; z3Proofchild.getPartitionsFromAssertsRecursion(operationId, result); } if (proofType.equals(SExpressionConstants.ASSERTED)) { if (assertPartition <= 0) assert (false); result.add(new Integer(assertPartition)); } } public int getID() { return this.id; } public String getIdString() { return Z3Proof.myFormatter.format(id); } /** * Gets all the lemmas that "close" any hypothesis that <code>this</code> * node depends on. * * @return lemmas found <code>this</code> node "upwards" (towards parents). */ public Set<Z3Proof> getClosingLemmas() { long operationId = DagOperationManager.startDAGOperation(); Set<Z3Proof> result = new HashSet<Z3Proof>(); this.getClosingLemmasRecursion(operationId, result); DagOperationManager.endDAGOperation(operationId); return result; } private void getClosingLemmasRecursion(long operationId, Set<Z3Proof> result) { if (wasVisitedByDAGOperation(operationId)) return; visitedByDAGOperation(operationId); if (this.proofType.equals(SExpressionConstants.LEMMA)) { result.add(this); return; } if (parents != null) { for (SoftReferenceWithEquality<Z3Proof> parentRef : parents) { Z3Proof parent = parentRef.get(); if (parent != null) parent.getClosingLemmasRecursion(operationId, result); } } } /** * Gets all the lemmas that occur in subproofs. * * @return lemmas found from <code>this</code> node "downwards" (towards * parents). */ public Set<Z3Proof> getLemmas() { long operationId = DagOperationManager.startDAGOperation(); Set<Z3Proof> result = this.getLemmasRecursion(operationId); DagOperationManager.endDAGOperation(operationId); return result; } private Set<Z3Proof> getLemmasRecursion(long operationId) { visitedByDAGOperation(operationId); Set<Z3Proof> lemmas = new HashSet<Z3Proof>(); if (proofType.equals(SExpressionConstants.LEMMA)) { lemmas.add(this); } for (Z3Proof z3Proofchild : this.subProofs) { if (z3Proofchild.wasVisitedByDAGOperation(operationId)) continue; lemmas.addAll(z3Proofchild.getLemmasRecursion(operationId)); } return lemmas; } public ImmutableSet<Formula> getHypothesisFormulas() { Z3Proof.debugGetHypothesesCallCounter++; Z3Proof.debugGetHypothesesTimer.start(); if (hypothesisFormulasCache != null) { // if (this.hypCacheModCount == Z3Proof.hypModCount) { if (!this.hypCacheDirty) { Z3Proof.debugGetHypothesesCallUsingCacheCounter++; Z3Proof.debugGetHypothesesTimer.stop(); Z3Proof.printDebugGetHypothesesTimerStats(); return hypothesisFormulasCache; } } Set<Z3Proof> hypotheses = this.getHypotheses(); Z3Proof.debugGetHypothesesTimer.start(); // timer is stopped by the call // above. Set<Formula> tmp = new HashSet<Formula>(); for (Z3Proof hypothesis : hypotheses) tmp.add(hypothesis.getConsequent().transformToConsequentsForm()); hypothesisFormulasCache = ImmutableSet.create(tmp); Z3Proof.debugGetHypothesesTimer.stop(); Z3Proof.printDebugGetHypothesesTimerStats(); return hypothesisFormulasCache; } public Set<Z3Proof> getHypotheses() { Z3Proof.debugGetHypothesesCallCounter++; Z3Proof.debugGetHypothesesTimer.start(); if (hypothesesCache != null) { // if (this.hypCacheModCount == Z3Proof.hypModCount) { if (!this.hypCacheDirty) { Z3Proof.debugGetHypothesesCallUsingCacheCounter++; Z3Proof.debugGetHypothesesTimer.stop(); Z3Proof.printDebugGetHypothesesTimerStats(); return hypothesesCache; } } long operationId = DagOperationManager.startDAGOperation(); Set<Z3Proof> result = this.getHypothesesRecursion(operationId); DagOperationManager.endDAGOperation(operationId); if (!result.equals(hypothesesCache) || hypothesesCache == null || hypothesisFormulasCache == null) { hypothesesCache = ImmutableSet.create(result); Set<Formula> tmp = new HashSet<Formula>(); for (Z3Proof hypothesis : result) tmp.add(hypothesis.getConsequent().transformToConsequentsForm()); hypothesisFormulasCache = ImmutableSet.create(tmp); this.markHypCacheDirty(); } this.hypCacheModCount = Z3Proof.hypModCount; this.hypCacheDirty = false; Z3Proof.debugGetHypothesesTimer.stop(); Z3Proof.printDebugGetHypothesesTimerStats(); return hypothesesCache; } private Set<Z3Proof> getHypothesesRecursion(long operationId) { visitedByDAGOperation(operationId); if (!this.hypCacheDirty) { assert (hypothesesCache != null); return hypothesesCache; } if (proofType.equals(SExpressionConstants.LEMMA)) { this.hypCacheDirty = false; this.hypothesesCache = ImmutableSet.create(new HashSet<Z3Proof>()); return hypothesesCache; } if (proofType.equals(SExpressionConstants.HYPOTHESIS)) { assert (this.subProofs.size() == 0); this.hypothesesCache = ImmutableSet.create(Collections .singleton(this)); this.hypCacheDirty = false; return hypothesesCache; } if (this instanceof TransformedZ3Proof) { if (((TransformedZ3Proof) this).isHypothesis()) { assert (this.subProofs.size() == 0); this.hypothesesCache = ImmutableSet.create(Collections .singleton(this)); this.hypCacheDirty = false; return hypothesesCache; } } Set<Z3Proof> tmp = new HashSet<Z3Proof>(); for (Z3Proof z3Proofchild : this.subProofs) { if (z3Proofchild.wasVisitedByDAGOperation(operationId)) { assert (!z3Proofchild.hypCacheDirty); tmp.addAll(z3Proofchild.hypothesesCache); } else { tmp.addAll(z3Proofchild.getHypothesesRecursion(operationId)); } } this.hypothesesCache = ImmutableSet.create(tmp); this.hypCacheDirty = false; return hypothesesCache; } /** * Marks the hypothesis cache of this node and all its ancestors as dirty. */ public void markHypCacheDirty() { this.hypCacheDirty = true; for (Z3Proof ancestor : this.allAncestorNodes()) ancestor.hypCacheDirty = true; } @Deprecated public void localLemmasToAssertions() { long operationId = DagOperationManager.startDAGOperation(); this.localLemmasToAssertionsRecursion(operationId); DagOperationManager.endDAGOperation(operationId); } @Deprecated private void localLemmasToAssertionsRecursion(long operationId) { visitedByDAGOperation(operationId); if (proofType.equals(SExpressionConstants.LEMMA)) { assert (subProofs.size() == 1); Set<Integer> partitionsFromAsserts = subProofs.get(0) .getPartitionsFromAsserts(); assert (!partitionsFromAsserts.contains(new Integer(-1))); if (partitionsFromAsserts.size() > 1) { if (!(subProofs.get(0).wasVisitedByDAGOperation(operationId))) subProofs.get(0).localLemmasToAssertionsRecursion( operationId); return; } Set<Integer> symbolsPartitions = consequent .getPartitionsFromSymbols(); symbolsPartitions.remove(new Integer(-1)); if (partitionsFromAsserts.equals(symbolsPartitions)) { proofType = SExpressionConstants.ASSERTED; if (partitionsFromAsserts.size() == 1) assertPartition = partitionsFromAsserts.iterator().next(); else if (partitionsFromAsserts.size() == 0) assertPartition = 1; // arbitrary choice else assert (false); // unreachable assert (assertPartition > 0); subProofs = new ArrayList<Z3Proof>(); return; } else return; } else for (Z3Proof child : subProofs) { if (child.wasVisitedByDAGOperation(operationId)) continue; child.localLemmasToAssertionsRecursion(operationId); } } /** * Removes local subproofs (including lemmas, if they are local). */ public void removeLocalSubProofs() { long operationId = DagOperationManager.startDAGOperation(); Map<Z3Proof, Set<Integer>> partitionMap = new HashMap<Z3Proof, Set<Integer>>(); Map<Z3Proof, Boolean> existHypothesesMap = new HashMap<Z3Proof, Boolean>(); this.removeLocalSubProofsRecursion(operationId, partitionMap, existHypothesesMap); DagOperationManager.endDAGOperation(operationId); } private void removeLocalSubProofsRecursion(long operationId, Map<Z3Proof, Set<Integer>> partitionMap, Map<Z3Proof, Boolean> existHypothesesMap) { assert (!this.wasVisitedByDAGOperation(operationId)); this.visitedByDAGOperation(operationId); assert (partitionMap != null); assert (existHypothesesMap != null); Set<Integer> partitions = new HashSet<Integer>(); boolean existHypotheses = false; if (this.proofType.equals(SExpressionConstants.HYPOTHESIS)) { existHypotheses = true; partitions.addAll(this.consequent.getPartitionsFromSymbols()); } else if (this.proofType.equals(SExpressionConstants.ASSERTED)) { assert (this.assertPartition > 0); partitions.add(this.assertPartition); } for (Z3Proof child : this.subProofs) { if (!child.wasVisitedByDAGOperation(operationId)) child.removeLocalSubProofsRecursion(operationId, partitionMap, existHypothesesMap); assert (partitionMap.containsKey(child)); Set<Integer> childPartitions = partitionMap.get(child); assert (childPartitions != null); partitions.addAll(childPartitions); assert (existHypothesesMap.containsKey(child)); Boolean existChildHypotheses = existHypothesesMap.get(child); assert (existChildHypotheses != null); existHypotheses |= existChildHypotheses; } partitionMap.put(this, new HashSet<Integer>(partitions)); existHypothesesMap.put(this, existHypotheses); partitions.remove(-1); if (this.proofType.equals(SExpressionConstants.LEMMA) || !existHypotheses) { if (partitions.size() <= 1) { int partition = partitions.size() == 0 ? 1 : partitions .iterator().next(); // arbitrary choice 1 for // "global facts" this.subProofs = new ArrayList<Z3Proof>(0); this.proofType = SExpressionConstants.ASSERTED; this.assertPartition = partition; assert (this.assertPartition > 0); } } // DEBUG // int mapSize = partitionMap.keySet().size(); // if (mapSize % 100 == 0) { // DecimalFormat myFormatter = new DecimalFormat("###,###,###"); // String output = myFormatter.format(mapSize); // System.out.println(" DEBUG-INFO: Already " + output // + " nodes in partitionMap."); // } // END DEBUG } public String prettyPrint() { long operationId = DagOperationManager.startDAGOperation(); StringBuffer buffer = new StringBuffer(); this.prettyPrintRecursive(operationId, buffer); DagOperationManager.endDAGOperation(operationId); return buffer.toString(); } private void prettyPrintRecursive(long operationId, StringBuffer buffer) { visitedByDAGOperation(operationId); buffer.append("----------------------------------------------\n"); buffer.append("ID: "); buffer.append(this.id); buffer.append(" (partition: "); buffer.append(this.assertPartition); buffer.append(")"); buffer.append("\n"); buffer.append("Rule: "); buffer.append(proofType.toString()); buffer.append("\n"); buffer.append("Antecedents:\n"); for (Z3Proof child : subProofs) { buffer.append(child.id); buffer.append(": "); buffer.append(child.consequent.toString() .replaceAll("\\s{2,}", " ").replace("\n", "")); buffer.append("\n"); } buffer.append("Proofs: "); buffer.append(consequent.toString().replaceAll("\\s{2,}", " ") .replace("\n", "")); buffer.append("\n"); for (Z3Proof child : subProofs) { if (child.wasVisitedByDAGOperation(operationId)) continue; child.prettyPrintRecursive(operationId, buffer); } } /** * marks a node as visited by this operation. * * @param operationId * unique id of the operation. */ protected void visitedByDAGOperation(long operationId) { // check for consistency. if (this.visitedByOperation.contains(operationId)) throw new RuntimeException("revisited node#" + this.id + " with operation#" + operationId + ". this should not happen!"); this.visitedByOperation.removeAll(DagOperationManager .getFinishedOperations()); this.visitedByOperation.add(operationId); DagOperationManager.incrementNodeCounter(operationId); } /** * checks if this node was already visited by this operation. * * @param operationId * unique id of the operation. * @return true if was visited. */ protected boolean wasVisitedByDAGOperation(long operationId) { return this.visitedByOperation.contains(operationId); } /** * * @return the set of all nodes in this DAG. */ public Set<Z3Proof> allNodes() { Set<Z3Proof> result = new HashSet<Z3Proof>(); this.allNodes(result); return result; } private void allNodes(Set<Z3Proof> set) { set.add(this); for (Z3Proof child : this.subProofs) { if (!set.contains(child)) child.allNodes(set); } } /** * * @return the set of all ancestor nodes of <code>this</code>. */ public Set<Z3Proof> allAncestorNodes() { Set<Z3Proof> result = new HashSet<Z3Proof>(); this.allAncestorNodes(result); result.remove(this); return result; } private void allAncestorNodes(Set<Z3Proof> set) { set.add(this); if (parents != null) { for (SoftReferenceWithEquality<Z3Proof> parentRef : this.parents) { Z3Proof parent = parentRef.get(); assert (parent != null); if (!set.contains(parent)) parent.allAncestorNodes(set); } } } /** * @return <code>true</code> if the consequent of this node has only a * single literal. */ protected boolean hasSingleLiteralConsequent() { Formula formula = this.consequent.transformToConsequentsForm(); if (!(formula instanceof OrFormula)) return false; OrFormula consequent = (OrFormula) formula; return consequent.getDisjuncts().size() == 1; } /** * Creates a transitivity proof for the given list of subproofs. The list * must have exactly two or three elements, which match a transitivity * premise of the form [(a=b), (b=c)] or [(a=b), (b=c), (c=d)]. * * @param subProofs * the subproofs * @return a transitivity proof for the given term. */ public static Z3Proof createTransitivityProof( List<? extends Z3Proof> subProofs) { assert (subProofs != null); assert (!subProofs.isEmpty()); subProofs = new ArrayList<Z3Proof>(subProofs); List<TransformedZ3Proof> transformedSubProofs = new ArrayList<TransformedZ3Proof>( subProofs.size()); if (subProofs.size() == 1) { return subProofs.get(0); } assert (Util.makeLiteralPositive(Util.getSingleLiteral((subProofs .get(0).consequent.transformToConsequentsForm()))) instanceof EqualityFormula); assert (Util .makeLiteralPositive(Util.getSingleLiteral(subProofs.get(1).consequent .transformToConsequentsForm())) instanceof EqualityFormula); assert (subProofs.size() == 3 ? Util.makeLiteralPositive(Util .getSingleLiteral(subProofs.get(2).consequent .transformToConsequentsForm())) instanceof EqualityFormula : true); final int initialSize = subProofs.size(); Set<Z3Proof> toRemove = new HashSet<Z3Proof>(); for (Z3Proof subProof : subProofs) { if (Util.isReflexivity(subProof.consequent)) toRemove.add(subProof); } subProofs.removeAll(toRemove); if (subProofs.size() < 2) { if (subProofs.size() == 1) return subProofs.get(0); else { assert (subProofs.size() == 0); assert (toRemove.size() == initialSize); Object[] proofs = toRemove.toArray(); assert (proofs.length == initialSize); assert (proofs.length == 2 || proofs.length == 3); assert (proofs[0] instanceof Z3Proof); assert (proofs[1] instanceof Z3Proof); if (proofs.length > 2) { assert (proofs[2] instanceof Z3Proof); assert (((Z3Proof) proofs[0]).consequent .equals(((Z3Proof) proofs[1]).consequent) && ((Z3Proof) proofs[1]).consequent .equals(((Z3Proof) proofs[2]).consequent)); } else { assert (((Z3Proof) proofs[0]).consequent .equals(((Z3Proof) proofs[1]).consequent)); } Z3Proof result = (proofs[0] instanceof TransformedZ3Proof) ? new TransformedZ3Proof( SExpressionConstants.ASSERTED, new ArrayList<TransformedZ3Proof>(), ((Z3Proof) proofs[0]).consequent) : new Z3Proof( SExpressionConstants.ASSERTED, new ArrayList<Z3Proof>(), ((Z3Proof) proofs[0]).consequent); result.axiom = true; return result; } } if (subProofs.size() > 3) { if (subProofs.get(0) instanceof TransformedZ3Proof) { for (Z3Proof subProof : subProofs) { assert (subProof instanceof TransformedZ3Proof); transformedSubProofs.add((TransformedZ3Proof) subProof); } Z3Proof intermediate = (subProofs.get(0) instanceof TransformedZ3Proof) ? TransformedZ3Proof .createTransitivityProofForTransformedZ3Proofs(new ArrayList<TransformedZ3Proof>( transformedSubProofs.subList(0, 2))) : Z3Proof .createTransitivityProof(new ArrayList<Z3Proof>( subProofs.subList(0, 2))); for (int count = 2; count < subProofs.size(); count++) { List<Z3Proof> currentSubProofs = new ArrayList<Z3Proof>(2); // / FIXME ugly hack to deal with transformed proofs. List<TransformedZ3Proof> transformedCurrentSubProofs = new ArrayList<TransformedZ3Proof>( 2); currentSubProofs.add(intermediate); currentSubProofs.add(subProofs.get(count)); if (intermediate instanceof TransformedZ3Proof && subProofs.get(count) instanceof TransformedZ3Proof) { transformedCurrentSubProofs .add((TransformedZ3Proof) intermediate); transformedCurrentSubProofs .add((TransformedZ3Proof) subProofs.get(count)); } intermediate = (subProofs.get(0) instanceof TransformedZ3Proof) ? TransformedZ3Proof .createTransitivityProofForTransformedZ3Proofs(new ArrayList<TransformedZ3Proof>( transformedCurrentSubProofs.subList(0, 2))) : Z3Proof.createTransitivityProof(currentSubProofs); } return intermediate; } } EqualityFormula firstFormula = (EqualityFormula) Util .makeLiteralPositive(Util.getSingleLiteral(subProofs.get(0).consequent .transformToConsequentsForm())); EqualityFormula lastFormula = (EqualityFormula) Util .makeLiteralPositive(Util.getSingleLiteral(subProofs .get(subProofs.size() - 1).consequent .transformToConsequentsForm())); EqualityFormula middleFormula = subProofs.size() == 3 ? (EqualityFormula) Util .makeLiteralPositive(Util.getSingleLiteral(subProofs.get(1).consequent .transformToConsequentsForm())) : null; EqualityFormula[] equalities; if (middleFormula != null) { equalities = new EqualityFormula[3]; equalities[0] = firstFormula; equalities[1] = middleFormula; equalities[2] = lastFormula; } else { equalities = new EqualityFormula[2]; equalities[0] = firstFormula; equalities[1] = lastFormula; } assert (Util.checkEqualityChain(equalities)); int numDisequalities = 0; for (Z3Proof child : subProofs) { if (Util.isNegativeLiteral(Util.getSingleLiteral(child.consequent .transformToConsequentsForm()))) numDisequalities++; } assert (numDisequalities <= 1); assert (firstFormula.getTerms().size() == 2); Term term1 = firstFormula.getTerms().get(0); assert (lastFormula.getTerms().size() == 2); Term term2 = lastFormula.getTerms().get(1); List<Term> newTerms = new ArrayList<Term>(); newTerms.add(term1); newTerms.add(term2); Formula newFormula = null; try { newFormula = EqualityFormula .create(newTerms, numDisequalities == 0) .transformToConsequentsForm(); } catch (IncomparableTermsException exc) { throw new RuntimeException( "Incomparable terms while creating transitivity proof.", exc); } transformedSubProofs = new ArrayList<TransformedZ3Proof>( subProofs.size()); if (subProofs.get(0) instanceof TransformedZ3Proof) { for (Z3Proof subProof : subProofs) { assert (subProof instanceof TransformedZ3Proof); transformedSubProofs.add((TransformedZ3Proof) subProof); } } Z3Proof result = (subProofs.get(0) instanceof TransformedZ3Proof) ? new TransformedZ3Proof( SExpressionConstants.TRANSITIVITY, transformedSubProofs, newFormula) : new Z3Proof(SExpressionConstants.TRANSITIVITY, subProofs, newFormula); // If we have three subproofs, we need to split them, // because conversion to local proof cannot deal with // three subproofs. assert (result.subProofs.size() <= 3); if (result.subProofs.size() == 3) { assert (result.proofType == SExpressionConstants.TRANSITIVITY); if (subProofs.get(0) instanceof TransformedZ3Proof) { transformedSubProofs = new ArrayList<TransformedZ3Proof>(); for (Z3Proof proof : result.subProofs) { assert (proof instanceof TransformedZ3Proof); transformedSubProofs.add((TransformedZ3Proof) proof); } } Z3Proof intermediate = (subProofs.get(0) instanceof TransformedZ3Proof) ? TransformedZ3Proof .createTransitivityProofForTransformedZ3Proofs(new ArrayList<TransformedZ3Proof>( transformedSubProofs.subList(0, 2))) : Z3Proof.createTransitivityProof(new ArrayList<Z3Proof>( result.subProofs.subList(0, 2))); Z3Proof rest = result.subProofs.get(2); result.subProofs.clear(); result.subProofs.add(intermediate); result.subProofs.add(rest); } return result; } /** * Creates a symmetry proof for the given premise. * * @param premise * must have a single literal as a consequence * @return a symmetry proof for the given premise. */ public static Z3Proof createSymmetryProof(Z3Proof premise) { assert (premise.hasSingleLiteralConsequent()); Formula literal = Util.makeLiteralPositive(Util .getSingleLiteral((premise.consequent .transformToConsequentsForm()))); assert (literal instanceof EqualityFormula); boolean equal = !Util.isNegativeLiteral(Util .getSingleLiteral(premise.consequent .transformToConsequentsForm())); List<Term> terms = ((EqualityFormula) literal).getTerms(); Collections.reverse(terms); Formula consequentFormula = null; try { consequentFormula = EqualityFormula.create(terms, equal); consequentFormula = consequentFormula.transformToConsequentsForm(); assert (consequentFormula != null); } catch (IncomparableTermsException exc) { throw new RuntimeException( "Incomparable terms while creating symmetry proof.", exc); } List<Z3Proof> subproofs = new ArrayList<Z3Proof>(); subproofs.add(premise); return new Z3Proof(SExpressionConstants.SYMMETRY, subproofs, consequentFormula); } /** * Checks if node is valid. Therefore, whether the given Subproofs together * produces the consequent of the node. * * @return return true if node is valid */ public boolean checkZ3ProofNode() { SMTSolver z3 = SMTSolver.create(SMTSolver.z3_type, "lib/z3/bin/z3"); if (this.subProofs.size() > 0) { List<Formula> conjuncts = new ArrayList<Formula>(); for (Z3Proof subProof : this.subProofs) { conjuncts.add(subProof.consequent); } conjuncts.add(NotFormula.create(this.consequent)); Formula formulaToCheck = AndFormula.generate(conjuncts); String declarationStr = makeDeclarationsAndDefinitions(formulaToCheck); String formulaSmtStr = buildSMTDescription(declarationStr, formulaToCheck.toString()); z3.solve(formulaSmtStr); switch (z3.getState()) { case SMTSolver.UNSAT: break; case SMTSolver.SAT: System.out.println("Bad Node: " + this.id); throw new RuntimeException( "Error while testing vality of Z3-node with Z3-solver: \n" + "z3 tells us SAT, node is NOT valid."); default: System.out .println("....Z3 OUTCOME ----> UNKNOWN! CHECK ERROR STREAM."); throw (new RuntimeException( "Z3 tells us UNKOWN STATE. CHECK ERROR STREAM.")); } } return true; } /** * Checks recursive if node is valid. Therefore, whether the given Subproofs * together produces the consequent of the node. Also checks this property * recursive for every node of the subtree. * * @return return true if node is valid */ public boolean checkZ3ProofNodeRecursive() { long operationId = DagOperationManager.startDAGOperation(); boolean result = this.checkZ3ProofNodeRecursiveRecursion(operationId); DagOperationManager.endDAGOperation(operationId); return result; } private boolean checkZ3ProofNodeRecursiveRecursion(long operationId) { if (this.wasVisitedByDAGOperation(operationId)) return true; visitedByDAGOperation(operationId); if (this.subProofs.size() > 0) for (Z3Proof subProof : this.subProofs) { if (subProof.wasVisitedByDAGOperation(operationId)) continue; if (!subProof.checkZ3ProofNodeRecursiveRecursion(operationId)) return false; } return checkZ3ProofNode(); } /** * Checks if proof is valid. Takes all asserted nodes (leafs) from a * partition, and checks if the original formula of this partition implies * these leafs. Does this for all partitions. * * @param originalFormulas * asserted formula for each partition * * @return true if <code>Z3Proof</code> is valid */ public boolean checkLeafsAgainstOriginalFormula( Map<Integer, Formula> originalFormulas) { Timer timer = new Timer(); System.out.println(" Check proofs leafs to consequent..."); timer.start(); boolean result = true; Set<Z3Proof> leafes = this.allLeafs(); assert (leafes.size() > 0); HashMap<Integer, ArrayList<Z3Proof>> partitionLeafMap = new HashMap<Integer, ArrayList<Z3Proof>>(); List<Formula> globalLeafs = new ArrayList<Formula>(); List<Formula> axiomsDisjuncts = new ArrayList<Formula>(); for (Z3Proof leaf : leafes) { if (!leaf.proofType.equals(SExpressionConstants.HYPOTHESIS)) { assert (leaf.assertPartition != 0); ArrayList<Z3Proof> leafList = partitionLeafMap .get(leaf.assertPartition); if (leafList == null) { leafList = new ArrayList<Z3Proof>(); partitionLeafMap.put(leaf.assertPartition, leafList); } leafList.add(leaf); if (leaf.assertPartition == -1) globalLeafs.add(NotFormula.create(leaf.consequent)); if (leaf.axiom) axiomsDisjuncts.add(NotFormula.create(leaf.consequent)); } } // check each partition: phi_n /\ (~L1 \/ ~L2 \/ ... ) = UNSAT for (Map.Entry<Integer, ArrayList<Z3Proof>> entry : partitionLeafMap .entrySet()) { int partition = entry.getKey(); if (partition != -1) {// not global List<Formula> disjuncts = new ArrayList<Formula>(); for (Z3Proof leaf : entry.getValue()) disjuncts.add(NotFormula.create(leaf.consequent)); if (globalLeafs != null) disjuncts.addAll(globalLeafs); Formula leafFormula = OrFormula.generate(disjuncts); List<Formula> conjuncts = new ArrayList<Formula>(); conjuncts.add(originalFormulas.get(partition)); conjuncts.add(leafFormula); Formula formulaToCheck = AndFormula.generate(conjuncts); String declarationStr = makeDeclarationsAndDefinitions(formulaToCheck); String formulaSmtStr = buildSMTDescription(declarationStr, formulaToCheck.toString()); SMTSolver z3 = SMTSolver.create(SMTSolver.z3_type, "lib/z3/bin/z3"); z3.solve(formulaSmtStr); switch (z3.getState()) { case SMTSolver.UNSAT: break; case SMTSolver.SAT: System.out .println("Error while testing vality of Z3-proof with Z3-solver: \n" + "z3 tells us SAT, proof is NOT valid."); System.out.println("Bad Node: " + this.id); result = false; break; default: System.out .println("Z3 tells us UNKOWN STATE. CHECK ERROR STREAM."); result = false; } } } // check axioms: (~A1 \/ ~A2 \/ ... ) = UNSAT if (axiomsDisjuncts.size() > 0) { Formula formulaToCheck = OrFormula.generate(axiomsDisjuncts); String declarationStr = makeDeclarationsAndDefinitions(formulaToCheck); String formulaSmtStr = buildSMTDescription(declarationStr, formulaToCheck.toString()); SMTSolver z3 = SMTSolver.create(SMTSolver.z3_type, "lib/z3/bin/z3"); z3.solve(formulaSmtStr); switch (z3.getState()) { case SMTSolver.UNSAT: break; case SMTSolver.SAT: System.out .println("Error while checking axioms with z3-solver Z3-solver: \n" + "z3 tells us SAT. axioms are not valid"); System.out.println("Bad Node: " + this.id); result = false; break; default: System.out .println("Z3 tells us UNKOWN STATE. CHECK ERROR STREAM."); result = false; } } timer.stop(); System.out.println(" Done. (" + timer + ")"); timer.reset(); return result; } /** * Checks if proof is valid. Takes all asserted nodes (leafs) of the proof, * and checks, whether all leafs together imply the consequence of the node. * * @return true if <code>Z3Proof</code> is valid */ public boolean checkProofLeafsToConsequent() { Timer timer = new Timer(); System.out.println(" Check proofs leafs to consequent..."); timer.start(); Set<Z3Proof> leafs = this.allLeafs(); // conjunction of all leafs and not the consequent -> UNSAT // L1 ^ L2 ^ L3 ^ ~C -> UNSAT boolean result = true; assert (leafs.size() > 0); List<Formula> conjuncts = new ArrayList<Formula>(); for (Z3Proof leaf : leafs) { conjuncts.add(leaf.consequent); } conjuncts.add(NotFormula.create(this.consequent)); Formula formulaToCheck = AndFormula.generate(conjuncts); String declarationStr = makeDeclarationsAndDefinitions(formulaToCheck); String formulaSmtStr = buildSMTDescription(declarationStr, formulaToCheck.toString()); SMTSolver z3 = SMTSolver.create(SMTSolver.z3_type, "lib/z3/bin/z3"); z3.solve(formulaSmtStr); switch (z3.getState()) { case SMTSolver.UNSAT: break; case SMTSolver.SAT: System.out .println("Error while testing vality of Z3-node with Z3-solver: \n" + "z3 tells us SAT, node is NOT valid."); System.out.println("Bad Node: " + this.id); result = false; break; default: System.out.println("Z3 tells us UNKOWN STATE. CHECK ERROR STREAM."); result = false; } timer.stop(); System.out.println(" Done. (" + timer + ")"); timer.reset(); return result; } /** * Returns all leafs of the <code>Z3Proof</code>. A leaf is everything with * 0 subProofs. * * @return all leafs of a <code>Z3Proof</code> */ public Set<Z3Proof> allLeafs() { long operationId = DagOperationManager.startDAGOperation(); Set<Z3Proof> leafs = new HashSet<Z3Proof>(); this.allLeafsRecursion(leafs, operationId); DagOperationManager.endDAGOperation(operationId); return leafs; } private void allLeafsRecursion(Set<Z3Proof> set, long operationId) { if (this.wasVisitedByDAGOperation(operationId)) return; visitedByDAGOperation(operationId); if (this.subProofs.size() > 0) for (Z3Proof subProof : this.subProofs) { subProof.allLeafsRecursion(set, operationId); } else { // assert (this.proofType.equals(SExpressionConstants.ASSERTED)); set.add(this); } } /** * Writes the declarations of all domain variables, propositional variables, * uninterpreted functions, as well as the definition of all macros in * <code>formula</code>. * * @param formula * The formula for which to write the definitions. * @return the declaration */ protected String makeDeclarationsAndDefinitions(Formula formula) { Set<SExpression> outputExpressions = new HashSet<SExpression>(); Set<SMTLibObject> done = new HashSet<SMTLibObject>(); Set<PropositionalVariable> pVars = new HashSet<PropositionalVariable>(); formula.getPropositionalVariables(pVars, done); done.clear(); for (PropositionalVariable var : pVars) outputExpressions .add(SExpression.makeDeclareFun((Token) var.toSmtlibV2(), SExpressionConstants.BOOL_TYPE, 0)); Set<DomainVariable> dVars = new HashSet<DomainVariable>(); formula.getDomainVariables(dVars, done); done.clear(); for (DomainVariable var : dVars) outputExpressions.add(SExpression.makeDeclareFun( (Token) var.toSmtlibV2(), SExpressionConstants.VALUE_TYPE, 0)); Set<UninterpretedFunction> ufs = new HashSet<UninterpretedFunction>(); formula.getUninterpretedFunctions(ufs, done); done.clear(); for (UninterpretedFunction function : ufs) outputExpressions.add(SExpression.makeDeclareFun( function.getName(), function.getType(), function.getNumParams())); Set<FunctionMacro> macros = new HashSet<FunctionMacro>(); this.consequent.getFunctionMacros(macros, done); done.clear(); for (FunctionMacro macro : macros) outputExpressions.add(macro.toSmtlibV2()); String declarationsStr = ""; for (SExpression declaration : outputExpressions) declarationsStr += declaration.toString(); return declarationsStr; } /** * Creates an SMT description for a given formula * * @param declarationStr * declarations of the SMT description * @param formulaStr * formula to be checked * @return SMT description * */ protected String buildSMTDescription(String declarationStr, String formulaStr) { String smtStr = ""; smtStr += SExpressionConstants.SET_LOGIC_QF_UF.toString(); smtStr += SExpressionConstants.DECLARE_SORT_VALUE.toString(); smtStr += declarationStr; smtStr += "(assert" + formulaStr + ")"; smtStr += SExpressionConstants.CHECK_SAT.toString(); smtStr += SExpressionConstants.EXIT.toString(); return smtStr; } public static int numInstances() { return Z3Proof.instanceCounter; } /** * * @return the maximum "depth" of this proof. (length of the chain to a * leaf) */ public int depth() { Map<Z3Proof, Integer> map = new HashMap<Z3Proof, Integer>(); return depthRecursion(map); } private int depthRecursion(Map<Z3Proof, Integer> map) { if (this.subProofs.isEmpty()) return 1; if (map.containsKey(this)) return map.get(this); int result = 0; for (Z3Proof child : subProofs) { int childDepth = child.depthRecursion(map); if (++childDepth > result) result = childDepth; } map.put(this, result); return result; } public Set<Z3Proof> getNodesWithConsequent(Formula consequent) { Set<Z3Proof> result = new HashSet<Z3Proof>(); long operationId = DagOperationManager.startDAGOperation(); this.getNodesWithConsequentRecursion(consequent, result, operationId); DagOperationManager.endDAGOperation(operationId); return result; } public void getNodesWithConsequentRecursion(Formula consequent, Set<Z3Proof> result, long operationId) { assert (result != null); if (wasVisitedByDAGOperation(operationId)) return; visitedByDAGOperation(operationId); if (this.consequent.equals(consequent)) result.add(this); for (Z3Proof child : subProofs) child.getNodesWithConsequentRecursion(consequent, result, operationId); } /** * * @return number of nodes in this proof */ public int size() { return this.size(false); } /** * * @param unwind * if <code>true</code> unwind DAG into a tree * @return number of nodes in this proof, unwinding the DAG into a tree, if * <code>unwind</code> is <code>true</code>. */ public int size(boolean unwind) { int result = 1; if (unwind) { for (Z3Proof child : subProofs) result += child.size(); return result; } else { long operationId = DagOperationManager.startDAGOperation(); result = this.sizeRecursion(operationId); DagOperationManager.endDAGOperation(operationId); return result; } } private int sizeRecursion(long operationId) { int result = 1; if (this.wasVisitedByDAGOperation(operationId)) return 0; visitedByDAGOperation(operationId); for (Z3Proof child : subProofs) result += child.sizeRecursion(operationId); return result; } /** * Recursively computes the parents in the proof, starting from * <code>this</code> downwards. * * */ public void computeParents() { long operationId = DagOperationManager.startDAGOperation(); this.computeParentsRecursion(operationId); DagOperationManager.endDAGOperation(operationId); } private void computeParentsRecursion(long operationId) { if (this.wasVisitedByDAGOperation(operationId)) return; visitedByDAGOperation(operationId); for (Z3Proof child : subProofs) { child.addParent(this); child.computeParentsRecursion(operationId); } return; } /** * Walks through the set <code>localNodes</code> starting from * <code>this</code> and duplicates all parents that are contained in * <code>toDuplicate</code>. * * @param toDuplicate * nodes that should be duplicated * @param localNodes * nodes making up the paths we care about. * @return a map from old to new nodes */ public Map<Z3Proof, Z3Proof> duplicate(Set<Z3Proof> toDuplicate, Set<Z3Proof> localNodes) { long operationId = DagOperationManager.startDAGOperation(); Map<Z3Proof, Z3Proof> duplicates = new HashMap<Z3Proof, Z3Proof>(); duplicate(operationId, toDuplicate, duplicates, localNodes); DagOperationManager.endDAGOperation(operationId); assert (duplicates.size() == toDuplicate.size()); return duplicates; } private void duplicate(long operationId, Set<Z3Proof> toDuplicate, Map<Z3Proof, Z3Proof> duplicates, Set<Z3Proof> localNodes) { assert (toDuplicate != null); assert (duplicates != null); assert (Collections.disjoint(toDuplicate, localNodes)); if (this.wasVisitedByDAGOperation(operationId)) return; visitedByDAGOperation(operationId); if (duplicates.containsKey(this)) return; if (localNodes.contains(this)) { for (Z3Proof child : this.subProofs) child.duplicate(operationId, toDuplicate, duplicates, localNodes); return; } if (!toDuplicate.contains(this)) // neither a local node, nor one to duplicate. // this is not on the path that we are interested in. // ignoring it return; assert (toDuplicate.contains(this)); Z3Proof duplicate = new Z3Proof(this.proofType, new ArrayList<Z3Proof>( 0), this.consequent.deepFormulaCopy()); duplicate.takeValuesFrom(this); duplicates.put(this, duplicate); List<Z3Proof> duplicateSubProofs = new ArrayList<Z3Proof>( this.subProofs.size()); for (Z3Proof subProof : this.subProofs) { if (!toDuplicate.contains(subProof)) duplicateSubProofs.add(subProof); else { subProof.duplicate(operationId, toDuplicate, duplicates, localNodes); assert (duplicates.containsKey(subProof)); duplicateSubProofs.add(duplicates.get(subProof)); } } duplicate.subProofs = duplicateSubProofs; // must be overwritten now duplicate.parents = new HashSet<SoftReferenceWithEquality<Z3Proof>>(); for (SoftReferenceWithEquality<Z3Proof> parentRef : this.parents) { Z3Proof parent = parentRef.get(); assert (parent != null); SoftReferenceWithEquality<Z3Proof> newParentRef = null; if (toDuplicate.contains(parent)) { if (!duplicates.containsKey(parent)) parent.duplicate(operationId, toDuplicate, duplicates, localNodes); assert (duplicates.containsKey(parent)); assert (duplicates.get(parent) != null); newParentRef = new SoftReferenceWithEquality<Z3Proof>( duplicates.get(parent)); } else { if (localNodes.contains(parent)) newParentRef = new SoftReferenceWithEquality<Z3Proof>( parent); } if (newParentRef != null) { assert (newParentRef.get() != null); duplicate.parents.add(newParentRef); } } } /** * * @param target * @return all the nodes on paths to the target, including the target * itself. */ public Set<Z3Proof> nodesOnPathTo(Z3Proof target) { long operationId = DagOperationManager.startDAGOperation(); Set<Z3Proof> result = new HashSet<Z3Proof>(); nodesOnPathToRecursion(operationId, target, result); DagOperationManager.endDAGOperation(operationId); return result; } private boolean nodesOnPathToRecursion(long operationId, Z3Proof target, Set<Z3Proof> result) { assert (result != null); if (this == target) { result.add(this); return true; } if (wasVisitedByDAGOperation(operationId)) return false; visitedByDAGOperation(operationId); boolean flag = false; for (Z3Proof child : subProofs) { if (result.contains(child)) { result.add(this); flag = true; continue; } if (child.nodesOnPathToRecursion(operationId, target, result)) { result.add(this); flag = true; } } return flag; } /** * * @param target * @return all the nodes on paths to a hypothesis with the given formula, * excluding the actual hypothesis. */ public Set<Z3Proof> nodesOnPathToHypothesisFormula(Formula target) { long operationId = DagOperationManager.startDAGOperation(); Set<Z3Proof> result = new HashSet<Z3Proof>(); nodesOnPathToHypothesisFormulaRecursion(operationId, target, result); DagOperationManager.endDAGOperation(operationId); return result; } private boolean nodesOnPathToHypothesisFormulaRecursion(long operationId, Formula target, Set<Z3Proof> result) { assert (result != null); if (this.consequent.equals(target)) return true; if (this.wasVisitedByDAGOperation(operationId)) return false; visitedByDAGOperation(operationId); boolean flag = false; for (Z3Proof child : subProofs) { if (result.contains(child)) { result.add(this); flag = true; continue; } if (child.nodesOnPathToHypothesisFormulaRecursion(operationId, target, result)) { result.add(this); flag = true; } } return flag; } /** * @see java.lang.Comparable#compareTo(java.lang.Object) */ @Override public int compareTo(Z3Proof o) { return this.toString().compareTo(o.toString()); } /** * @return the <code>assertPartition</code> */ public int getAssertPartitionOfThisNode() { assert (assertPartition > 0 || !this.proofType .equals(SExpressionConstants.ASSERTED)); return assertPartition; } /** * Necessary for restore after load from cache. Do not tamper with the * instance counter otherwise! * * @param value * the new value for the instance counter */ public static void setInstanceCounter(int value) { Z3Proof.instanceCounter = value; } /** * * @return the current value of the instance counter */ public static int getInstanceCounter() { return Z3Proof.instanceCounter; } /** * * @return <code>true</code> iff this proof object is a leaf. * <code>false</code> otherwise. */ public boolean isLeaf() { return subProofs.isEmpty(); } /** * @return <code>true</code> iff this is a hypothesis node. */ public boolean isHypothesis() { return proofType.equals(SExpressionConstants.HYPOTHESIS); } /** * Adds missing reflexivity proofs to a monotonicity proof. Does nothing on * other proof types. * * Also performs sanity checks. E.g. if the subproofs actually proof what * they are supposed to prove. * */ protected void addMissingReflexivityProofs() { if (!this.proofType.equals(SExpressionConstants.MONOTONICITY)) return; assert (this.hasSingleLiteralConsequent()); assert (Util.getSingleLiteral(this.consequent) instanceof EqualityFormula); EqualityFormula consequentEq = (EqualityFormula) Util .getSingleLiteral(this.consequent); assert (consequentEq.getTerms().size() == 2); List<Term> leftTerms = new ArrayList<Term>(); List<Term> rightTerms = new ArrayList<Term>(); if (consequentEq.getTerms().get(0) instanceof UninterpretedFunctionInstance) { assert (consequentEq.getTerms().get(1) instanceof UninterpretedFunctionInstance); UninterpretedFunctionInstance leftFunctionInstance = (UninterpretedFunctionInstance) consequentEq .getTerms().get(0); UninterpretedFunctionInstance rightFunctionInstance = (UninterpretedFunctionInstance) consequentEq .getTerms().get(1); assert (leftFunctionInstance.getFunction() .equals(rightFunctionInstance.getFunction())); assert (leftFunctionInstance.getParameters().size() == rightFunctionInstance .getParameters().size()); leftTerms.addAll(leftFunctionInstance.getParameters()); rightTerms.addAll(rightFunctionInstance.getParameters()); } else { Formula leftFormula = null; if (consequentEq.getTerms().get(0) instanceof FormulaTerm) leftFormula = ((FormulaTerm) consequentEq.getTerms().get(0)) .getFormula(); else { assert (consequentEq.getTerms().get(0) instanceof PropositionalTerm); leftFormula = ((PropositionalTerm) consequentEq.getTerms().get( 0)); } assert (leftFormula != null); Formula rightFormula = null; if (consequentEq.getTerms().get(1) instanceof FormulaTerm) rightFormula = ((FormulaTerm) consequentEq.getTerms().get(1)) .getFormula(); else { assert (consequentEq.getTerms().get(1) instanceof PropositionalTerm); rightFormula = ((PropositionalTerm) consequentEq.getTerms() .get(1)); } assert (rightFormula != null); if (leftFormula instanceof UninterpretedPredicateInstance) { assert (rightFormula instanceof UninterpretedPredicateInstance); UninterpretedPredicateInstance leftPredicateInstance = (UninterpretedPredicateInstance) leftFormula; UninterpretedPredicateInstance rightPredicateInstance = (UninterpretedPredicateInstance) rightFormula; assert (leftPredicateInstance.getFunction() .equals(rightPredicateInstance.getFunction())); assert (leftPredicateInstance.getParameters().size() == rightPredicateInstance .getParameters().size()); leftTerms.addAll(leftPredicateInstance.getParameters()); rightTerms.addAll(rightPredicateInstance.getParameters()); } else if (leftFormula instanceof DomainEq) { assert (rightFormula instanceof DomainEq); DomainEq leftEq = (DomainEq) leftFormula; DomainEq rightEq = (DomainEq) rightFormula; assert (leftEq.getTerms().size() == 2); assert (rightEq.getTerms().size() == 2); leftTerms.add(leftEq.getTerms().get(0)); leftTerms.add(leftEq.getTerms().get(1)); rightTerms.add(rightEq.getTerms().get(0)); rightTerms.add(rightEq.getTerms().get(1)); } else throw new RuntimeException( "Unexpected type of monotonicity proof. ID: " + this.id); } assert (leftTerms.size() > 0); assert (rightTerms.size() > 0); assert (leftTerms.size() == rightTerms.size()); assert (leftTerms.size() >= subProofs.size()); for (int count = 0; count < leftTerms.size(); count++) { Term leftTerm = leftTerms.get(count); Term rightTerm = rightTerms.get(count); Z3Proof subProof = subProofs.size() > count ? subProofs.get(count) : null; boolean missingSubProof = (subProof == null); if (subProof != null) { assert (Util.getSingleLiteral(subProof.consequent) instanceof EqualityFormula); EqualityFormula subEq = (EqualityFormula) Util .getSingleLiteral(subProof.consequent); assert (subEq.getTerms().size() == 2); if (!subEq.getTerms().get(0).equals(leftTerm)) missingSubProof = true; if (!subEq.getTerms().get(1).equals(rightTerm)) missingSubProof = true; } if (missingSubProof) { if (!leftTerm.equals(rightTerm)) assert (false); Z3Proof missingProof = TransformedZ3Proof .createReflexivityProof(leftTerm); List<Z3Proof> newSubProofs = new ArrayList<Z3Proof>(); newSubProofs.addAll(subProofs.subList(0, count)); newSubProofs.add(missingProof); newSubProofs.addAll(subProofs.subList(count, subProofs.size())); this.subProofs = newSubProofs; } } } /** * * @param formula * the formula to look for * @return the set of all proof nodes in this proof (and transitive * subproofs) which have an IFF node as their consequent where one * of the sides of the IFF equals the given formula. */ public Set<Z3Proof> findIffNodes(Formula formula) { Set<Z3Proof> result = new HashSet<Z3Proof>(); long operationId = DagOperationManager.startDAGOperation(); findIffNodesRecursion(operationId, formula, result); DagOperationManager.endDAGOperation(operationId); return result; } private void findIffNodesRecursion(long operationId, Formula formula, Set<Z3Proof> result) { if (this.wasVisitedByDAGOperation(operationId)) return; this.visitedByDAGOperation(operationId); if (this.consequent instanceof PropositionalEq) { PropositionalEq consequentEq = (PropositionalEq) this.consequent; if (consequentEq.getTerms().size() == 2) { assert (consequentEq.getTerms().get(0) instanceof PropositionalTerm); assert (consequentEq.getTerms().get(1) instanceof PropositionalTerm); PropositionalTerm term1 = (PropositionalTerm) consequentEq .getTerms().get(0); PropositionalTerm term2 = (PropositionalTerm) consequentEq .getTerms().get(1); PropositionalTerm positiveTerm = FormulaTerm.create((Util .makeLiteralPositive(formula))); PropositionalTerm negativeTerm = FormulaTerm.create(NotFormula .create(Util.makeLiteralPositive(formula))); if (term1.equals(formula) || term2.equals(formula) || term1.equals(positiveTerm) || term1.equals(negativeTerm) || term2.equals(positiveTerm) || term2.equals(negativeTerm)) result.add(this); } } for (Z3Proof child : subProofs) { child.findIffNodesRecursion(operationId, formula, result); } } }