def reduceOperands(innerExpr, inPlace=True, mustEvaluate=False, assumptions=USE_DEFAULTS): ''' Attempt to return an InnerExpr object that is provably equivalent to the given innerExpr but with simplified operands at the inner-expression level. If inPlace is True, the top-level expression must be a KnownTruth and the simplified KnownTruth is derived instead of an equivalence relation. If mustEvaluate is True, the simplified operands must be irreducible values (see isIrreducibleValue). ''' # Any of the operands that can be simplified must be replaced with their evaluation from proveit import InnerExpr assert isinstance( innerExpr, InnerExpr), "Expecting 'innerExpr' to be of type 'InnerExpr'" inner = innerExpr.exprHierarchy[-1] while True: allReduced = True for operand in inner.operands: if not mustEvaluate or not isIrreducibleValue(operand): # the operand is not an irreducible value so it must be evaluated operandEval = operand.evaluation( assumptions=assumptions ) if mustEvaluate else operand.simplification( assumptions=assumptions) if mustEvaluate and not isIrreducibleValue(operandEval.rhs): raise EvaluationError( 'Evaluations expected to be irreducible values') if operandEval.lhs != operandEval.rhs: # compose map to replace all instances of the operand within the inner expression lambdaMap = innerExpr.replMap().compose( Lambda.globalRepl(inner, operand)) # substitute in the evaluated value if inPlace: innerExpr = InnerExpr( operandEval.subRightSideInto(lambdaMap), innerExpr.innerExprPath) else: innerExpr = InnerExpr( operandEval.substitution(lambdaMap).rhs, innerExpr.innerExprPath) allReduced = False break # start over (there may have been multiple substitutions) if allReduced: return innerExpr inner = innerExpr.exprHierarchy[-1]
def evaluation(self, assumptions=USE_DEFAULTS): ''' Given operands that may be evaluated to irreducible values that may be compared, or if there is a known evaluation of this equality, derive and return this expression equated to TRUE or FALSE. ''' if self.lhs == self.rhs: # prove equality is true by reflexivity return evaluateTruth(self.prove().expr, assumptions=[]) if isIrreducibleValue(self.lhs) and isIrreducibleValue(self.rhs): # Irreducible values must know how to evaluate the equality # between each other, where appropriate. return self.lhs.evalEquality(self.rhs) return TransitiveRelation.evaluation(self, assumptions)
def sideEffects(self, knownTruth): ''' Record the knownTruth in Equals.knownEqualities, associated from the left hand side and the right hand side. This information may be useful for concluding new equations via transitivity. If the right hand side is an "irreducible value" (see isIrreducibleValue), also record it in Equals.evaluations for use when the evaluation method is called. Some side-effects derivations are also attempted depending upon the form of this equality. ''' from proveit.logic.boolean._common_ import TRUE, FALSE Equals.knownEqualities.setdefault(self.lhs, set()).add(knownTruth) Equals.knownEqualities.setdefault(self.rhs, set()).add(knownTruth) if isIrreducibleValue(self.rhs): Equals.simplifications.setdefault(self.lhs, set()).add(knownTruth) Equals.evaluations.setdefault(self.lhs, set()).add(knownTruth) if (self.lhs != self.rhs): # automatically derive the reversed form which is equivalent yield self.deriveReversed if self.rhs == FALSE: # derive lhs => FALSE from lhs = FALSE yield self.deriveContradiction # derive lhs from Not(lhs) = FALSE, if self is in this form #yield self.deriveViaFalsifiedNegation if self.rhs in (TRUE, FALSE): # automatically derive A from A=TRUE or Not(A) from A=FALSE yield self.deriveViaBooleanEquality if hasattr(self.lhs, 'equalitySideEffects'): for sideEffect in self.lhs.equalitySideEffects(knownTruth): yield sideEffect
def conclude(self, assumptions): ''' Attempt to conclude the equality various ways: simple reflexivity (x=x), via an evaluation (if one side is an irreducible), or via transitivity. ''' from proveit.logic import TRUE, FALSE, Implies, Iff if self.lhs == self.rhs: # Trivial x=x return self.concludeViaReflexivity() if self.lhs or self.rhs in (TRUE, FALSE): try: # Try to conclude as TRUE or FALSE. return self.concludeBooleanEquality(assumptions) except ProofFailure: pass if isIrreducibleValue(self.rhs): return self.lhs.evaluation() elif isIrreducibleValue(self.lhs): return self.rhs.evaluation().deriveReversed() try: Implies(self.lhs, self.rhs).prove(assumptions, automation=False) Implies(self.rhs, self.lhs).prove(assumptions, automation=False) # lhs => rhs and rhs => lhs, so attempt to prove lhs = rhs via lhs <=> rhs # which works when they can both be proven to be Booleans. try: return Iff(self.lhs, self.rhs).deriveEquality(assumptions) except: from proveit.logic.boolean.implication._theorems_ import eqFromMutualImpl return eqFromMutualImpl.specialize({ A: self.lhs, B: self.rhs }, assumptions=assumptions) except ProofFailure: pass """ # Use concludeEquality if available if hasattr(self.lhs, 'concludeEquality'): return self.lhs.concludeEquality(assumptions) if hasattr(self.rhs, 'concludeEquality'): return self.rhs.concludeEquality(assumptions) """ # Use a breadth-first search approach to find the shortest # path to get from one end-point to the other. return TransitiveRelation.conclude(self, assumptions)
def sideEffects(self, knownTruth): ''' Record the knownTruth in Equals.knownEqualities, associated from the left hand side and the right hand side. This information may be useful for concluding new equations via transitivity. If the right hand side is an "irreducible value" (see isIrreducibleValue), also record it in Equals.known_evaluation_sets for use when the evaluation method is called. Some side-effects derivations are also attempted depending upon the form of this equality. If the rhs is an "irreducible value" (see isIrreducibleValue), also record the knownTruth in the Equals.known_simplifications and Equals.known_evaluation_sets dictionaries, for use when the simplification or evaluation method is called. The key for the known_simplifications dictionary is the specific *combination* of the lhs expression along with the assumptions in the form (expr, tuple(sorted(assumptions))); the key for the known_evaluation_sets dictionary is just the lhs expression without the specific assumptions. Some side-effects derivations are also attempted depending upon the form of this equality. ''' from proveit.logic.boolean._common_ import TRUE, FALSE Equals.knownEqualities.setdefault(self.lhs, set()).add(knownTruth) Equals.knownEqualities.setdefault(self.rhs, set()).add(knownTruth) if isIrreducibleValue(self.rhs): assumptions_sorted = sorted(knownTruth.assumptions, key=lambda expr: hash(expr)) lhsKey = (self.lhs, tuple(assumptions_sorted)) # n.b.: the values in the known_simplifications # dictionary consist of single KnownTruths not sets Equals.known_simplifications[lhsKey] = knownTruth Equals.known_evaluation_sets.setdefault(self.lhs, set()).add(knownTruth) if (self.lhs != self.rhs): # automatically derive the reversed form which is equivalent yield self.deriveReversed if self.rhs == FALSE: try: self.lhs.prove(automation=False) # derive FALSE given lhs=FALSE and lhs. yield self.deriveContradiction except ProofFailure: pass # Use this form after merging in 'Expression.proven' commite: #if self.lhs.proven(): # If lhs is proven using default assumptions. # # derive FALSE given lhs=FALSE and lhs. # yield self.deriveContradiction if self.rhs in (TRUE, FALSE): # automatically derive A from A=TRUE or Not(A) from A=FALSE yield self.deriveViaBooleanEquality if hasattr(self.lhs, 'equalitySideEffects'): for sideEffect in self.lhs.equalitySideEffects(knownTruth): yield sideEffect
def conclude(self, assumptions): from proveit.logic import FALSE if isIrreducibleValue(self.lhs) and isIrreducibleValue(self.rhs): # prove that two irreducible values are not equal return self.lhs.notEqual(self.rhs) if self.lhs == FALSE or self.rhs == FALSE: try: # prove something is not false by proving it to be true return self.concludeViaDoubleNegation(assumptions) except: pass if hasattr(self.lhs, 'notEquals') and isIrreducibleValue(self.rhs): try: return self.lhs.notEqual(self.rhs, assumptions) except: pass try: return self.concludeAsFolded(assumptions) except: return Operation.conclude(assumptions) # try the default (reduction)
def evaluation(self, assumptions=USE_DEFAULTS): ''' If possible, return a KnownTruth of this expression equal to its irreducible value. Checks for an existing evaluation. If it doesn't exist, try some default strategies including a reduction. Attempt the Expression-class-specific "doReducedEvaluation" when necessary. ''' from proveit.logic import Equals, defaultSimplification, SimplificationError from proveit import KnownTruth, ProofFailure from proveit.logic.irreducible_value import isIrreducibleValue method_called = None try: # First try the default tricks. If a reduction succesfully occurs, # evaluation will be called on that reduction. evaluation = defaultSimplification(self.innerExpr(), mustEvaluate=True, assumptions=assumptions) method_called = defaultSimplification except SimplificationError as e: # The default failed, let's try the Expression-class specific version. try: evaluation = self.doReducedEvaluation(assumptions) method_called = self.doReducedEvaluation except NotImplementedError: # We have nothing but the default evaluation strategy to try, and that failed. raise e if not isinstance(evaluation, KnownTruth) or not isinstance( evaluation.expr, Equals): msg = ("%s must return an KnownTruth, " "not %s for %s assuming %s" % (method_called, evaluation, self, assumptions)) raise ValueError(msg) if evaluation.lhs != self: msg = ("%s must return an KnownTruth " "equality with self on the left side, " "not %s for %s assuming %s" % (method_called, evaluation, self, assumptions)) raise ValueError(msg) if not isIrreducibleValue(evaluation.rhs): msg = ("%s must return an KnownTruth " "equality with an irreducible value on the right side, " "not %s for %s assuming %s" % (method_name, evaluation, self, assumptions)) raise ValueError(msg) # Note: No need to store in Equals.evaluations or Equals.simplifications; this # is done automatically as a side-effect for proven equalities with irreducible # right sides. return evaluation
def defaultSimplification(innerExpr, inPlace=False, mustEvaluate=False, operandsOnly=False, assumptions=USE_DEFAULTS, automation=True): ''' Default attempt to simplify the given inner expression under the given assumptions. If successful, returns a KnownTruth (using a subset of the given assumptions) that expresses an equality between the expression (on the left) and one with a simplified form for the "inner" part (in some canonical sense determined by the Operation). If inPlace is True, the top-level expression must be a KnownTruth and the simplified KnownTruth is derived instead of an equivalence relation. If mustEvaluate=True, the simplified form must be an irreducible value (see isIrreducibleValue). Specifically, this method checks to see if an appropriate simplification/evaluation has already been proven. If not, but if it is an Operation, call the simplification/evaluation method on all operands, make these substitions, then call simplification/evaluation on the expression with operands substituted for simplified forms. It also treats, as a special case, evaluating the expression to be true if it is in the set of assumptions [also see KnownTruth.evaluation and evaluateTruth]. If operandsOnly = True, only simplify the operands of the inner expression. ''' # among other things, convert any assumptions=None # to assumptions=() to avoid len(None) errors assumptions = defaults.checkedAssumptions(assumptions) from proveit.logic import TRUE, FALSE from proveit.logic.boolean._axioms_ import trueAxiom topLevel = innerExpr.exprHierarchy[0] inner = innerExpr.exprHierarchy[-1] if operandsOnly: # Just do the reduction of the operands at the level below the # "inner expression" reducedInnerExpr = reduceOperands(innerExpr, inPlace, mustEvaluate, assumptions) if inPlace: try: return reducedInnerExpr.exprHierarchy[0].prove( assumptions, automation=False) except: assert False try: eq = Equals(topLevel, reducedInnerExpr.exprHierarchy[0]) return eq.prove(assumptions, automation=False) except: assert False def innerSimplification(innerEquivalence): if inPlace: return innerEquivalence.subRightSideInto(innerExpr, assumptions=assumptions) return innerEquivalence.substitution(innerExpr, assumptions=assumptions) if isIrreducibleValue(inner): return Equals(inner, inner).prove() assumptionsSet = set(defaults.checkedAssumptions(assumptions)) # See if the expression is already known to be true as a special # case. try: inner.prove(assumptionsSet, automation=False) trueEval = evaluateTruth(inner, assumptionsSet) # A=TRUE given A if inner == topLevel: if inPlace: return trueAxiom else: return trueEval return innerSimplification(trueEval) except: pass # See if the negation of the expression is already known to be true # as a special case. try: inner.disprove(assumptionsSet, automation=False) falseEval = evaluateFalsehood(inner, assumptionsSet) # A=FALSE given Not(A) return innerSimplification(falseEval) except: pass # ================================================================ # # See if the expression already has a proven simplification # # ================================================================ # # construct the key for the known_simplifications dictionary assumptions_sorted = sorted(assumptions, key=lambda expr: hash(expr)) known_simplifications_key = (inner, tuple(assumptions_sorted)) if (mustEvaluate and inner in Equals.known_evaluation_sets): evaluations = Equals.known_evaluation_sets[inner] candidates = [] for knownTruth in evaluations: if knownTruth.isSufficient(assumptionsSet): # Found existing evaluation suitable for the assumptions candidates.append(knownTruth) if len(candidates) >= 1: # Return the "best" candidate with respect to fewest number # of steps. min_key = lambda knownTruth: knownTruth.proof().numSteps() simplification = min(candidates, key=min_key) return innerSimplification(simplification) elif (not mustEvaluate and known_simplifications_key in Equals.known_simplifications): simplification = Equals.known_simplifications[ known_simplifications_key] return innerSimplification(simplification) # ================================================================ # if not automation: msg = 'Unknown evaluation (without automation): ' + str(inner) raise SimplificationError(msg) # See if the expression is equal to something that has an evaluation # or is already known to be true. if inner in Equals.knownEqualities: for knownEq in Equals.knownEqualities[inner]: try: if knownEq.isSufficient(assumptionsSet): if inPlace: # Should first substitute in the known # equivalence then simplify that. if inner == knownEq.lhs: knownEq.subRightSideInto(innerExpr, assumptions) elif inner == knownEq.rhs: knownEq.subLeftSideInto(innerExpr, assumptions) # Use mustEvaluate=True. Simply being equal to # something simplified isn't necessarily the # appropriate simplification for "inner" itself. alt_inner = knownEq.otherSide(inner).innerExpr() equivSimp = \ defaultSimplification(alt_inner, inPlace=inPlace, mustEvaluate=True, assumptions=assumptions, automation=False) if inPlace: # Returns KnownTruth with simplification: return equivSimp innerEquiv = knownEq.applyTransitivity( equivSimp, assumptions) if inner == topLevel: return innerEquiv return innerEquiv.substitution(innerExpr, assumptions=assumptions) except SimplificationError: pass # try to simplify via reduction if not isinstance(inner, Operation): if mustEvaluate: raise EvaluationError('Unknown evaluation: ' + str(inner), assumptions) else: # don't know how to simplify, so keep it the same return innerSimplification(Equals(inner, inner).prove()) reducedInnerExpr = reduceOperands(innerExpr, inPlace, mustEvaluate, assumptions) if reducedInnerExpr == innerExpr: if mustEvaluate: # Since it wasn't irreducible to begin with, it must change # in order to evaluate. raise SimplificationError('Unable to evaluate: ' + str(inner)) else: raise SimplificationError('Unable to simplify: ' + str(inner)) # evaluate/simplify the reduced inner expression inner = reducedInnerExpr.exprHierarchy[-1] if mustEvaluate: innerEquiv = inner.evaluation(assumptions) else: innerEquiv = inner.simplification(assumptions) value = innerEquiv.rhs if value == TRUE: # Attempt to evaluate via proving the expression; # This should result in a shorter proof if allowed # (e.g., if theorems are usable). try: evaluateTruth(inner, assumptions) except: pass if value == FALSE: # Attempt to evaluate via disproving the expression; # This should result in a shorter proof if allowed # (e.g., if theorems are usable). try: evaluateFalsehood(inner, assumptions) except: pass reducedSimplification = innerSimplification(innerEquiv) if inPlace: simplification = reducedSimplification else: # Via transitivity, go from the original expression to the # reduced expression (simplified inner operands) and then the # final simplification (simplified inner expression). reduced_top_level = reducedInnerExpr.exprHierarchy[0] eq1 = Equals(topLevel, reduced_top_level) eq1.prove(assumptions, automation=False) eq2 = Equals(reduced_top_level, reducedSimplification.rhs) eq2.prove(assumptions, automation=False) simplification = eq1.applyTransitivity(eq2, assumptions) if not inPlace and topLevel == inner: # Store direct simplifications in the known_simplifications # dictionary for next time. Equals.known_simplifications[ known_simplifications_key] = simplification if isIrreducibleValue(value): # also store it in the known_evaluation_sets dictionary for # next time, since it evaluated to an irreducible value. Equals.known_evaluation_sets.setdefault(topLevel, set()).add(simplification) return simplification
def reduceOperands(innerExpr, inPlace=True, mustEvaluate=False, assumptions=USE_DEFAULTS): ''' Attempt to return an InnerExpr object that is provably equivalent to the given innerExpr but with simplified operands at the inner-expression level. If inPlace is True, the top-level expression must be a KnownTruth and the simplified KnownTruth is derived instead of an equivalence relation. If mustEvaluate is True, the simplified operands must be irreducible values (see isIrreducibleValue). ''' # Any of the operands that can be simplified must be replaced with # their simplification. from proveit import InnerExpr, ExprRange assert isinstance(innerExpr, InnerExpr), \ "Expecting 'innerExpr' to be of type 'InnerExpr'" inner = innerExpr.exprHierarchy[-1] substitutions = [] while True: allReduced = True for operand in inner.operands: if (not isIrreducibleValue(operand) and not isinstance(operand, ExprRange)): # The operand isn't already irreducible, so try to # simplify it. if mustEvaluate: operandEval = operand.evaluation(assumptions=assumptions) else: operandEval = operand.simplification( assumptions=assumptions) if mustEvaluate and not isIrreducibleValue(operandEval.rhs): msg = 'Evaluations expected to be irreducible values' raise EvaluationError(msg, assumptions) if operandEval.lhs != operandEval.rhs: # Compose map to replace all instances of the # operand within the inner expression. global_repl = Lambda.globalRepl(inner, operand) lambdaMap = innerExpr.repl_lambda().compose(global_repl) # substitute in the evaluated value if inPlace: subbed = operandEval.subRightSideInto(lambdaMap) innerExpr = InnerExpr(subbed, innerExpr.innerExprPath) else: sub = operandEval.substitution(lambdaMap) innerExpr = InnerExpr(sub.rhs, innerExpr.innerExprPath) substitutions.append(sub) allReduced = False # Start over since there may have been multiple # substitutions: break if allReduced: break # done! inner = innerExpr.exprHierarchy[-1] if not inPlace and len(substitutions) > 1: # When there have been multiple substitutions, apply # transtivity over the chain of substitutions to equate the # end-points. Equals.applyTransitivities(substitutions, assumptions) return innerExpr
def conclude(self, assumptions): ''' Attempt to conclude the equality various ways: simple reflexivity (x=x), via an evaluation (if one side is an irreducible), or via transitivity. ''' from proveit.logic import TRUE, FALSE, Implies, Iff, inBool if self.lhs == self.rhs: # Trivial x=x return self.concludeViaReflexivity() if self.lhs or self.rhs in (TRUE, FALSE): try: # Try to conclude as TRUE or FALSE. return self.concludeBooleanEquality(assumptions) except ProofFailure: pass if isIrreducibleValue(self.rhs): try: evaluation = self.lhs.evaluation(assumptions) if evaluation.rhs != self.rhs: raise ProofFailure( self, assumptions, "Does not match with evaluation: %s" % str(evaluation)) return evaluation except EvaluationError as e: raise ProofFailure(self, assumptions, "Evaluation error: %s" % e.message) elif isIrreducibleValue(self.lhs): try: evaluation = self.rhs.evaluation(assumptions) if evaluation.rhs != self.lhs: raise ProofFailure( self, assumptions, "Does not match with evaluation: %s" % str(evaluation)) return evaluation.deriveReversed() except EvaluationError as e: raise ProofFailure(self, assumptions, "Evaluation error: %s" % e.message) if (Implies(self.lhs, self.rhs).proven(assumptions) and Implies(self.rhs, self.lhs).proven(assumptions) and inBool(self.lhs).proven(assumptions) and inBool(self.rhs).proven(assumptions)): # There is mutual implication both sides are known to be # boolean. Conclude equality via mutual implication. return Iff(self.lhs, self.rhs).deriveEquality(assumptions) if hasattr(self.lhs, 'deduceEquality'): # If there is a 'deduceEquality' method, use that. # The responsibility then shifts to that method for # determining what strategies should be attempted # (with the recommendation that it should not attempt # multiple non-trivial automation strategies). eq = self.lhs.deduceEquality(self, assumptions) if eq.expr != self: raise ValueError("'deduceEquality' not implemented " "correctly; must deduce the 'equality' " "that it is given if it can: " "'%s' != '%s'" % (eq.expr, self)) return eq else: ''' If there is no 'deduceEquality' method, we'll try simplifying each side to see if they are equal. ''' # Try to prove equality via simplifying both sides. lhs_simplification = self.lhs.simplification(assumptions) rhs_simplification = self.rhs.simplification(assumptions) simplified_lhs = lhs_simplification.rhs simplified_rhs = rhs_simplification.rhs try: if simplified_lhs != self.lhs or simplified_rhs != self.rhs: simplified_eq = Equals(simplified_lhs, simplified_rhs).prove(assumptions) return Equals.applyTransitivities([ lhs_simplification, simplified_eq, rhs_simplification ], assumptions) except ProofFailure: pass raise ProofFailure( self, assumptions, "Unable to automatically conclude by " "standard means. To try to prove this via " "transitive implication relations, try " "'concludeViaTransitivity'.")