def conclude(self, **defaults_config): from proveit.logic import FALSE, Not, evaluation_or_simplification if is_irreducible_value(self.lhs) and is_irreducible_value(self.rhs): # prove that two irreducible values are not equal return self.lhs.not_equal(self.rhs) if self.lhs == FALSE or self.rhs == FALSE: try: # prove something is not false by proving it to be true return self.conclude_via_double_negation() except BaseException: pass if Not(Equals(self.lhs, self.rhs)).proven(): # Conclude (x ≠ y) by knowing that Not(x = y) is true. return self.conclude_as_folded() # Try the standard Relation strategies -- evaluate or # simplify both sides. try: return Relation.conclude(self) except ProofFailure: # Both sides are already irreducible or simplified. pass if hasattr(self.lhs, 'not_equal'): # If there is a 'not_equal' 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). # A good practice is to try the 'conclude_as_folded' # strategy if it doesn't fall into any specially-handled # case. return self.lhs.not_equal(self.rhs) return self.conclude_as_folded()
def conclude(self, assumptions): from proveit.logic import FALSE if is_irreducible_value(self.lhs) and is_irreducible_value(self.rhs): # prove that two irreducible values are not equal return self.lhs.not_equal(self.rhs, assumptions) if self.lhs == FALSE or self.rhs == FALSE: try: # prove something is not false by proving it to be true return self.conclude_via_double_negation(assumptions) except BaseException: pass if hasattr(self.lhs, 'not_equal') and is_irreducible_value(self.rhs): try: return self.lhs.not_equal(self.rhs, assumptions) except BaseException: pass if hasattr(self.lhs, 'deduce_not_equal'): # If there is a 'deduce_not_equal' 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.deduce_not_equal(self, assumptions) if eq.expr != self: raise ValueError("'deduce_not_equal' not implemented " "correctly; must deduce the 'inequality' " "that it is given if it can: " "'%s' != '%s'" % (eq.expr, self)) return eq try: return self.conclude_as_folded(assumptions) except BaseException: # try the default (reduction) return Operation.conclude(assumptions)
def evaluation(self, assumptions=USE_DEFAULTS, automation=True): ''' 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 automation: if self.lhs == self.rhs: # prove equality is true by reflexivity return evaluate_truth(self.prove().expr, assumptions=[]) if is_irreducible_value(self.lhs) and is_irreducible_value( self.rhs): # Irreducible values must know how to evaluate the equality # between each other, where appropriate. return self.lhs.eval_equality(self.rhs) return TransitiveRelation.evaluation(self, assumptions) return Operation.evaluation(self, assumptions, automation)
def side_effects(self, judgment): ''' Record the judgment in Equals.known_equalities, 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 is_irreducible_value), 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 is_irreducible_value), also record the judgment 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.booleans import TRUE, FALSE Equals.known_equalities.setdefault(self.lhs, set()).add(judgment) Equals.known_equalities.setdefault(self.rhs, set()).add(judgment) if is_irreducible_value(self.rhs): assumptions_sorted = sorted(judgment.assumptions, key=lambda expr: hash(expr)) lhs_key = (self.lhs, tuple(assumptions_sorted)) # n.b.: the values in the known_simplifications # dictionary consist of single Judgments not sets Equals.known_simplifications[lhs_key] = judgment Equals.known_evaluation_sets.setdefault(self.lhs, set()).add(judgment) if (self.lhs != self.rhs): # automatically derive the reversed form which is equivalent yield self.derive_reversed if self.rhs == FALSE: try: self.lhs.prove(automation=False) # derive FALSE given lhs=FALSE and lhs. yield self.derive_contradiction 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.derive_contradiction if self.rhs in (TRUE, FALSE): # automatically derive A from A=TRUE or Not(A) from A=FALSE yield self.derive_via_boolean_equality if hasattr(self.lhs, 'equality_side_effects'): for side_effect in self.lhs.equality_side_effects(judgment): yield side_effect
def default_simplification(inner_expr, in_place=False, must_evaluate=False, operands_only=False, assumptions=USE_DEFAULTS, automation=True): ''' Default attempt to simplify the given inner expression under the given assumptions. If successful, returns a Judgment (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 in_place is True, the top-level expression must be a Judgment and the simplified Judgment is derived instead of an equivalence relation. If must_evaluate=True, the simplified form must be an irreducible value (see is_irreducible_value). 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 Judgment.evaluation and evaluate_truth]. If operands_only = 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.checked_assumptions(assumptions) from proveit.logic import TRUE, FALSE from proveit.logic.booleans import true_axiom top_level = inner_expr.expr_hierarchy[0] inner = inner_expr.expr_hierarchy[-1] if operands_only: # Just do the reduction of the operands at the level below the # "inner expression" reduced_inner_expr = reduce_operands(inner_expr, in_place, must_evaluate, assumptions) if in_place: try: return reduced_inner_expr.expr_hierarchy[0].prove( assumptions, automation=False) except BaseException: assert False try: eq = Equals(top_level, reduced_inner_expr.expr_hierarchy[0]) return eq.prove(assumptions, automation=False) except BaseException: assert False def inner_simplification(inner_equivalence): if in_place: return inner_equivalence.sub_right_side_into( inner_expr, assumptions=assumptions) return inner_equivalence.substitution(inner_expr, assumptions=assumptions) if is_irreducible_value(inner): return Equals(inner, inner).prove() assumptions_set = set(defaults.checked_assumptions(assumptions)) # See if the expression is already known to be true as a special # case. try: inner.prove(assumptions_set, automation=False) true_eval = evaluate_truth(inner, assumptions_set) # A=TRUE given A if inner == top_level: if in_place: return true_axiom else: return true_eval return inner_simplification(true_eval) except BaseException: pass # See if the negation of the expression is already known to be true # as a special case. try: inner.disprove(assumptions_set, automation=False) false_eval = evaluate_falsehood( inner, assumptions_set) # A=FALSE given Not(A) return inner_simplification(false_eval) except BaseException: 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 (must_evaluate and inner in Equals.known_evaluation_sets): evaluations = Equals.known_evaluation_sets[inner] candidates = [] for judgment in evaluations: if judgment.is_sufficient(assumptions_set): # Found existing evaluation suitable for the assumptions candidates.append(judgment) if len(candidates) >= 1: # Return the "best" candidate with respect to fewest number # of steps. def min_key(judgment): return judgment.proof().num_steps() simplification = min(candidates, key=min_key) return inner_simplification(simplification) elif (not must_evaluate and known_simplifications_key in Equals.known_simplifications): simplification = Equals.known_simplifications[ known_simplifications_key] if simplification.is_usable(): return inner_simplification(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.known_equalities: for known_eq in Equals.known_equalities[inner]: try: if known_eq.is_sufficient(assumptions_set): if in_place: # Should first substitute in the known # equivalence then simplify that. if inner == known_eq.lhs: known_eq.sub_right_side_into( inner_expr, assumptions) elif inner == known_eq.rhs: known_eq.sub_left_side_into( inner_expr, assumptions) # Use must_evaluate=True. Simply being equal to # something simplified isn't necessarily the # appropriate simplification for "inner" itself. alt_inner = known_eq.other_side(inner).inner_expr() equiv_simp = \ default_simplification(alt_inner, in_place=in_place, must_evaluate=True, assumptions=assumptions, automation=False) if in_place: # Returns Judgment with simplification: return equiv_simp inner_equiv = known_eq.apply_transitivity( equiv_simp, assumptions) if inner == top_level: return inner_equiv return inner_equiv.substitution(inner_expr, assumptions=assumptions) except SimplificationError: pass # try to simplify via reduction if not isinstance(inner, Operation): if must_evaluate: raise EvaluationError('Unknown evaluation: ' + str(inner), assumptions) else: # don't know how to simplify, so keep it the same return inner_simplification(Equals(inner, inner).prove()) reduced_inner_expr = reduce_operands(inner_expr, in_place, must_evaluate, assumptions) if reduced_inner_expr == inner_expr: if must_evaluate: # 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 = reduced_inner_expr.expr_hierarchy[-1] if must_evaluate: inner_equiv = inner.evaluation(assumptions) else: inner_equiv = inner.simplification(assumptions) value = inner_equiv.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: evaluate_truth(inner, assumptions) except BaseException: 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: evaluate_falsehood(inner, assumptions) except BaseException: pass reduced_simplification = inner_simplification(inner_equiv) if in_place: simplification = reduced_simplification 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 = reduced_inner_expr.expr_hierarchy[0] eq1 = Equals(top_level, reduced_top_level) eq1.prove(assumptions, automation=False) eq2 = Equals(reduced_top_level, reduced_simplification.rhs) eq2.prove(assumptions, automation=False) simplification = eq1.apply_transitivity(eq2, assumptions) if not in_place and top_level == inner: # Store direct simplifications in the known_simplifications # dictionary for next time. Equals.known_simplifications[ known_simplifications_key] = simplification if is_irreducible_value(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(top_level, set()).add(simplification) return simplification
def reduce_operands(inner_expr, in_place=True, must_evaluate=False, assumptions=USE_DEFAULTS): ''' Attempt to return an InnerExpr object that is provably equivalent to the given inner_expr but with simplified operands at the inner-expression level. If in_place is True, the top-level expression must be a Judgment and the simplified Judgment is derived instead of an equivalence relation. If must_evaluate is True, the simplified operands must be irreducible values (see is_irreducible_value). ''' # Any of the operands that can be simplified must be replaced with # their simplification. from proveit import InnerExpr, ExprRange assert isinstance(inner_expr, InnerExpr), \ "Expecting 'inner_expr' to be of type 'InnerExpr'" inner = inner_expr.expr_hierarchy[-1] substitutions = [] while True: all_reduced = True for operand in inner.operands: if (not is_irreducible_value(operand) and not isinstance(operand, ExprRange)): # The operand isn't already irreducible, so try to # simplify it. if must_evaluate: operand_eval = operand.evaluation(assumptions=assumptions) else: operand_eval = operand.simplification( assumptions=assumptions) if must_evaluate and not is_irreducible_value( operand_eval.rhs): msg = 'Evaluations expected to be irreducible values' raise EvaluationError(msg, assumptions) if operand_eval.lhs != operand_eval.rhs: # Compose map to replace all instances of the # operand within the inner expression. global_repl = Lambda.global_repl(inner, operand) lambda_map = inner_expr.repl_lambda().compose(global_repl) # substitute in the evaluated value if in_place: subbed = operand_eval.sub_right_side_into(lambda_map) inner_expr = InnerExpr(subbed, inner_expr.inner_expr_path) else: sub = operand_eval.substitution(lambda_map) inner_expr = InnerExpr(sub.rhs, inner_expr.inner_expr_path) substitutions.append(sub) all_reduced = False # Start over since there may have been multiple # substitutions: break if all_reduced: break # done! inner = inner_expr.expr_hierarchy[-1] if not in_place and len(substitutions) > 1: # When there have been multiple substitutions, apply # transtivity over the chain of substitutions to equate the # end-points. Equals.apply_transitivities(substitutions, assumptions) return inner_expr
def conclude(self, assumptions): ''' Attempt to conclude the equality various ways: simple reflexivity (x=x), via an evaluation (if one side is an irreducible). Use conclude_via_transitivity for transitivity cases. ''' from proveit.logic import TRUE, FALSE, Implies, Iff, in_bool if self.lhs == self.rhs: # Trivial x=x return self.conclude_via_reflexivity() if (self.lhs in (TRUE, FALSE)) or (self.rhs in (TRUE, FALSE)): try: # Try to conclude as TRUE or FALSE. return self.conclude_boolean_equality(assumptions) except ProofFailure: pass if is_irreducible_value(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 is_irreducible_value(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.derive_reversed() 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 in_bool(self.lhs).proven(assumptions) and in_bool(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).derive_equality(assumptions) if hasattr(self.lhs, 'deduce_equality'): # If there is a 'deduce_equality' 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.deduce_equality(self, assumptions) if eq.expr != self: raise ValueError("'deduce_equality' 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 'deduce_equality' 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.apply_transitivities([ 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 relations, try " "'conclude_via_transitivity'.")