def sorted_items(cls, items, reorder=True, assumptions=USE_DEFAULTS): ''' Return the given items in sorted order with respect to this TransitivityRelation class (cls) under the given assumptions using known transitive relations. If reorder is False, raise a TransitivityException if the items are not in sorted order as provided. Weak relations (e.g., <=) are only considered when calling this method on a weak relation class (otherwise, only equality and strong relations are used in the sorting). ''' from proveit.numbers import is_literal_int assumptions = defaults.checked_assumptions(assumptions) if all(is_literal_int(item) for item in items): # All the items are integers. Use efficient n log(n) sorting to # get them in the proper order and then use fixed_transitivity_sort # to efficiently prove this order. items = sorted(items, key=lambda item: item.as_int()) reorder = False if reorder: sorter = TransitivitySorter(cls, items, assumptions=assumptions) return list(sorter) else: return cls._fixed_transitivity_sort( items, assumptions=assumptions).operands
def is_equal_to_or_subset_eq_of(number_set, equal_sets=None, subset_sets=None, subset_eq_sets=None, assumptions=None): ''' A utility function used in the do_reduced_simplification() method to test whether the number set specified by number_set: • is equal to any of the number sets provided in the list of equal_sets • OR is already known/proven to be a proper subset of any of the number sets provided in the list of subset_sets, • OR is already known/proven to be an improper subset of any of the number sets provided in the list of subset_eq_sets, returning True at the first such equality, subset, or subset_eq relation found to be True. ''' # among other things, convert any assumptions=None # to assumptions=() (thus averting len(None) errors) assumptions = defaults.checked_assumptions(assumptions) if equal_sets is not None: for temp_set in equal_sets: if number_set == temp_set: return True if subset_eq_sets is not None: for temp_set in subset_eq_sets: if SubsetEq(number_set, temp_set).proven(assumptions): return True if subset_sets is not None: for temp_set in subset_sets: if ProperSubset(number_set, temp_set).proven(assumptions): return True return False
def apply_transitivity(self, other, assumptions=USE_DEFAULTS): ''' Apply transitivity to derive a new relation from 'self' and 'other'. For example, from self:a<b, other:b=c, derive a<c. This must be implemented for the different types of transitive relations. This default version handles the case where 'other' is an Equals expression. ''' from proveit.logic import Equals # print 'apply transitivity', self, other assumptions = defaults.checked_assumptions(assumptions) if isinstance(other, Equals): if other.normal_lhs in (self.normal_lhs, self.normal_rhs): subrule = other.sub_right_side_into common_expr = other.normal_lhs elif other.normal_rhs in (self.normal_lhs, self.normal_rhs): subrule = other.sub_left_side_into common_expr = other.normal_rhs else: raise ValueError( "Equality does not involve either side of inequality!") if common_expr == self.normal_lhs: # replace the normal_lhs of self with its counterpart # from the "other" equality. return subrule(self.inner_expr().normal_lhs, assumptions=assumptions) elif common_expr == self.normal_rhs: # replace the normal_rhs of self with its counterpart # from the "other" equality. return subrule(self.inner_expr().normal_rhs, assumptions=assumptions) raise NotImplementedError( 'Must implement apply_transitivity appropriately for each ' 'kind of TransitiveRelation')
def apply_transitivities(chain, assumptions=USE_DEFAULTS): ''' Apply transitvity rules on a list of relations in the given chain to proof the relation over the chain end points. Each element of the chain must be a Judgment object that represents a proven relation for which transitivity rules may be applied via an 'apply_transitivity' method (such as a Judgment for a proven Equals statement). The chain must "connect" in the sense that any two neighbors in the chain can be joined vie apply_transitivity. The transitivity rule will be applied left to right. ''' assumptions = defaults.checked_assumptions(assumptions) if len(chain) == 0: raise TransitivityException( None, assumptions, 'Empty transitivity relation train') if not all(isinstance(element, Judgment) for element in chain): raise TypeError('Expecting chain elements to be Judgment objects') while len(chain) >= 2: first = chain.pop(0) second = chain.pop(0) new_relation = first.apply_transitivity( second, assumptions=assumptions) if not isinstance(new_relation, Judgment): raise TypeError( "apply_transitivity should return a Judgment, not %s" % new_relation) chain.insert(0, new_relation) return chain[0] # we are done
def apply_rounding_elimination(expr, rounding_elimination_thm, assumptions=USE_DEFAULTS): ''' Let F(x) represent the relevant Ceil(x), Floor(x), or Round(x) fxn calling the apply_rounding_elimination() method from the respective F(x).rounding_elimination() method. For the trivial case of F(x) where the operand x is already an integer, derive and return this rounding F expression equated with the operand itself: |- F(x) = x. For example, |- Ceil(x) = x. Assumptions may be necessary to deduce necessary conditions (for example, that x actually is an integer) for the simplification. This method is utilized by the F(x).apply_reduced_simplification() method, indirectly via the F(x).rounding_elimination() method, but only after the operand x is verified to already be proven (or assumed) to be an integer. For the case where the operand is of the form x = real + int, see the apply_rounding_extraction() function. ''' from proveit import x # among other things, convert any assumptions=None # to assumptions=() to avoid later len() errors assumptions = defaults.checked_assumptions(assumptions) return rounding_elimination_thm.instantiate({x: expr.operand}, assumptions=assumptions)
def rounding_deduce_in_number_set(expr, number_set, rounding_real_closure_thm, rounding_real_pos_closure_thm, assumptions=USE_DEFAULTS): ''' Given a number set number_set, attempt to prove that the given Ceil, Floor, or Round expression is in that number set using the appropriate closure theorem. ''' from proveit import ProofFailure from proveit import x from proveit.logic import InSet from proveit.numbers import Integer, Natural # among other things, convert any assumptions=None # to assumptions=() assumptions = defaults.checked_assumptions(assumptions) if number_set == Integer: return rounding_real_closure_thm.instantiate({x: expr.operand}, assumptions=assumptions) if number_set == Natural: return rounding_real_pos_closure_thm.instantiate( {x: expr.operand}, assumptions=assumptions) msg = ("The rounding_methods.py function " "'rounding_deduce_in_number_set()' is not implemented for the " "%s set" % str(number_set)) raise ProofFailure(InSet(expr, number_set), assumptions, msg)
def deduce_in_number_set(self, number_set, assumptions=USE_DEFAULTS): ''' Given a number set number_set (such as Integer, Real, etc), attempt to prove that the given Mod expression is in that number set using the appropriate closure theorem. ''' from proveit.logic import InSet from proveit.numbers.modular import ( mod_int_closure, mod_int_to_nat_closure, mod_real_closure) from proveit.numbers import Integer, Natural, Real # among other things, make sure non-existent assumptions # manifest as empty tuple () rather than None assumptions = defaults.checked_assumptions(assumptions) if number_set == Integer: return mod_int_closure.instantiate( {a: self.dividend, b: self.divisor}, assumptions=assumptions) if number_set == Natural: return mod_int_to_nat_closure.instantiate( {a: self.dividend, b: self.divisor}, assumptions=assumptions) if number_set == Real: return mod_real_closure.instantiate( {a: self.dividend, b: self.divisor}, assumptions=assumptions) msg = ("'Mod.deduce_in_number_set()' not implemented for " "the %s set" % str(number_set)) raise ProofFailure(InSet(self, number_set), assumptions, msg)
def yield_known_memberships(element, assumptions=USE_DEFAULTS): ''' Yield the known memberships of the given element applicable under the given assumptions. ''' assumptions = defaults.checked_assumptions(assumptions) if element in InClass.known_memberships: for known_membership in InClass.known_memberships[element]: if known_membership.is_applicable(assumptions): yield known_membership
def deny_via_contradiction(contradictory_expr, conclusion, assumptions): ''' Deny the conclusion (affirm its negation) via reductio ad absurdum. First calls derive_contradiction on the contradictory_expr to derive FALSE, then derive the negated conclusion after proving that the conclusion itself implies FALSE. The conclusion must be a Boolean. ''' from proveit.logic import Not assumptions = defaults.checked_assumptions(assumptions) extended_assumptions = assumptions + (conclusion,) return contradictory_expr.derive_contradiction(extended_assumptions).as_implication( conclusion).derive_via_contradiction(assumptions)
def sort(cls, items, reorder=True, assumptions=USE_DEFAULTS): ''' Return the proven total ordering (conjunction of relations presented in the total ordering style) representing the sorted sequence according to the cls.sorted_items method. Weak relations (e.g., <=) are only considered when calling this method on a weak relation class (otherwise, only equality and strong relations are used in the sorting). ''' automation = True assumptions = defaults.checked_assumptions(assumptions) if reorder: items = cls.sorted_items(items, reorder, assumptions) #print("sorted", items) automation = False # Direct relations already proven. return cls._fixed_transitivity_sort(items, assumptions=assumptions, automation=automation)
def mergesort(cls, item_iterators, assumptions=USE_DEFAULTS, skip_exact_reps=False, skip_equiv_reps=False, requirements=None): ''' Return the proven Sequence, a judgment for an expression of type cls.SequenceClass(), representing the sorted sequence according to the cls.mergesorted_items method. Weak relations (e.g., <=) are only considered when calling this method on a weak relation class (otherwise, only equality and strong relations are used in the sorting). ''' assumptions = defaults.checked_assumptions(assumptions) items = cls.mergesorted_items(item_iterators, assumptions, skip_exact_reps, skip_equiv_reps, requirements) items = list(items) #print("merge sorted", items) return cls._fixed_transitivity_sort(items, assumptions=assumptions, automation=False)
def conclude(self, assumptions=USE_DEFAULTS): ''' From [element != a] AND ... AND [element != n], derive and return [element not in {a, b, ..., n}], where self is the EnumNonmembership object. ''' # among other things, convert any assumptions=None # to assumptions=() assumptions = defaults.checked_assumptions(assumptions) from . import nonmembership_fold enum_elements = self.domain.elements _y = enum_elements _n = _y.num_elements(assumptions=assumptions) return nonmembership_fold.instantiate({ n: _n, x: self.element, y: _y }, assumptions=assumptions)
def conclude_via_transitivity(self, assumptions=USE_DEFAULTS): from proveit.logic import Equals assumptions = defaults.checked_assumptions(assumptions) proven_relation = self.__class__._transitivitySearch( self.normal_lhs, self.normal_rhs, assumptions=assumptions) relation = proven_relation.expr if relation.__class__ != self.__class__: if self.__class__ == self.__class__._checkedWeakRelationClass(): if relation.__class__ == self.__class__._checkedStrongRelationClass(): return relation.derive_relaxed() elif relation.__class__ == Equals: # Derive a weaker relation via the strong relation # of equality: return self.conclude_via_equality(assumptions) msg = ("Not able to conclude the desired relation of %s" " from the proven relation of %s." % (str(self), str(relation))) raise TransitivityException(self, assumptions, msg) return proven_relation
def conclude(self, assumptions=USE_DEFAULTS): ''' Try to deduce that the given element is in the number set under the given assumptions. ''' element = self.element # See if the element is known to be equal with something # that is known to be in the number set. assumptions_set = set(defaults.checked_assumptions(assumptions)) for eq, equiv_elem in Equals.known_relations_from_left( element, assumptions_set): try: equiv_elem_in_set = InSet(equiv_elem, self.number_set) equiv_elem_in_set.prove(assumptions, automation=False) return eq.sub_left_side_into(equiv_elem_in_set, assumptions) except ProofFailure: pass ''' # Maybe let's not simplify first. If # See if we can simplify the element first. if hasattr(element, 'simplification'): simplification = element.simplification(assumptions=assumptions) element = simplification.rhs if element != self.element: # Prove membersip for the simplified element elem_in_set = InSet(element, self.number_set).prove(assumptions) # Substitute into the original. return simplification.sub_left_side_into(elem_in_set, assumptions) ''' # Try the 'deduce_in_number_set' method. if hasattr(element, 'deduce_in_number_set'): return element.deduce_in_number_set(self.number_set, assumptions=assumptions) else: msg = str(element) + " has no 'deduce_in_number_set' method." raise ProofFailure(InSet(self.element, self.number_set), assumptions, msg)
def deduce_in_number_set(self, number_set, assumptions=USE_DEFAULTS): ''' Given a number set number_set (such as Integer, Real, etc), attempt to prove that the given expression is in that number set using the appropriate closure theorem. Created: 3/21/2020 by wdc, based on the same method in the Add and Exp classes. Last modified: 3/26/2020 by wdc. Added defaults.checked_assumptions to avoid ProofFailure error. Previously modified: 3/21/2020 by wdc. Creation Once established, these authorship notations can be deleted. ''' from proveit.logic import InSet from proveit.numbers.absolute_value import ( abs_complex_closure, abs_nonzero_closure, abs_complex_closure_non_neg_real) from proveit.numbers import Complex, Real, RealNonNeg, RealPos # among other things, make sure non-existent assumptions # manifest as empty tuple () rather than None assumptions = defaults.checked_assumptions(assumptions) if number_set == Real: return abs_complex_closure.instantiate({a: self.operand}, assumptions=assumptions) if number_set == RealPos: return abs_nonzero_closure.instantiate({a: self.operand}, assumptions=assumptions) if number_set == RealNonNeg: return abs_complex_closure_non_neg_real.instantiate( {a: self.operand}, assumptions=assumptions) msg = ("'Abs.deduce_in_number_set()' not implemented for " "the %s set" % str(number_set)) raise ProofFailure(InSet(self, number_set), assumptions, msg)
def invert(lambda_map, rhs, assumptions=USE_DEFAULTS): ''' Given some x -> f(x) map and a right-hand-side, find the x for which f(x) = rhs amongst known equalities under the given assumptions. Return this x if one is found; return None otherwise. ''' assumptions_set = set(defaults.checked_assumptions(assumptions)) if (lambda_map, rhs) in Equals.inversions: # Previous solution(s) exist. Use one if the assumptions are # sufficient. for known_equality, inversion in Equals.inversions[(lambda_map, rhs)]: if known_equality.is_sufficient(assumptions_set): return inversion # The mapping may be a trivial identity: f(x) = f(x) try: x = lambda_map.extract_argument(rhs) # Found a trivial inversion. Store it for future reference. known_equality = Equals(rhs, rhs).prove() Equals.inversions.setdefault((lambda_map, rhs), []).append( (known_equality, x)) return x # Return the found inversion. except ArgumentExtractionError: pass # well, it was worth a try # Search among known relations for a solution. for known_equality, lhs in Equals.known_relations_from_right( rhs, assumptions_set): try: x = lambda_map.extract_argument(lhs) # Found an inversion. Store it for future reference. Equals.inversions.setdefault((lambda_map, rhs), []).append( (known_equality, x)) return x # Return the found inversion. except ArgumentExtractionError: pass # not a match. keep looking. raise InversionError("No inversion found to map %s onto %s" % (str(lambda_map), str(rhs)))
def deduce_in_number_set(self, number_set, assumptions=USE_DEFAULTS): ''' Given a number set number_set (such as Integer, Real, etc), attempt to prove that the given expression is in that number set using the appropriate closure theorem. ''' from proveit.numbers.absolute_value import ( abs_rational_closure, abs_rational_non_zero_closure, abs_complex_closure, abs_nonzero_closure, abs_complex_closure_non_neg_real) from proveit.numbers import (Rational, RationalNonZero, RationalPos, RationalNeg, RationalNonNeg, Real, RealNonNeg, RealPos, Complex) # among other things, make sure non-existent assumptions # manifest as empty tuple () rather than None assumptions = defaults.checked_assumptions(assumptions) thm = None if number_set in (RationalPos, RationalNonZero): thm = abs_rational_non_zero_closure elif number_set in (Rational, RationalNonNeg, RationalNeg): thm = abs_rational_closure elif number_set == Real: thm = abs_complex_closure elif number_set == RealPos: thm = abs_nonzero_closure elif number_set == RealNonNeg: thm = abs_complex_closure_non_neg_real if thm is not None: in_set = thm.instantiate({a: self.operand}, assumptions=assumptions) if in_set.domain == number_set: # Exactly the domain we were looking for. return in_set # We must have proven we were in a subset of the # one we were looking for. return InSet(self, number_set).prove(assumptions) # To be thorough and a little more general, we check if the # specified number_set is already proven to *contain* one of # the number sets we have theorems for -- for example, # Y=Complex contain X=Real, and # Y=(-1, inf) contains X=RealPos, # but we don't have specific thms for those supersets Y. # If so, use the appropiate thm to determine that self is in X, # then prove that self must also be in Y since Y contains X. if SubsetEq(Real, number_set).proven(assumptions=assumptions): abs_complex_closure.instantiate({a: self.operand}, assumptions=assumptions) return InSet(self, number_set).prove(assumptions=assumptions) if SubsetEq(RealPos, number_set).proven(assumptions=assumptions): abs_nonzero_closure.instantiate({a: self.operand}, assumptions=assumptions) return InSet(self, number_set).prove(assumptions=assumptions) if SubsetEq(RealNonNeg, number_set).proven(assumptions=assumptions): abs_complex_closure_non_neg_real.instantiate( {a: self.operand}, assumptions=assumptions) return InSet(self, number_set).prove(assumptions=assumptions) # otherwise, we just don't have the right thm to make it work msg = ("'Abs.deduce_in_number_set()' not implemented for " "the %s set" % str(number_set)) raise ProofFailure(InSet(self, number_set), assumptions, msg)
def __init__(self, relation_class, items, assumptions=USE_DEFAULTS, skip_exact_reps=False, skip_equiv_reps=False, presorted_pair=False): ''' Create a TransitivitySorter object to sort the given 'items' (and any added on the fly later) according to the relation_class (a TransitivityRelation) under the given assumptions. If skip_exact_reps is True, only yield the first of an 'item' that is repeated exactly (equal Expressions). If skip_equiv_reps is True, only yield the first of an 'item' that is repeated via a known equivalence. If presorted_pair is True, use the sorter solely to confirm and prove that two given items were provided in sorted order. ''' #print("sorting items", items) self.assumptions = defaults.checked_assumptions(assumptions) self.assumptions_set = set(self.assumptions) self.relation_class = relation_class self.strong_relation_class = \ relation_class._checkedStrongRelationClass() self.is_weak_relation = (relation_class != self.strong_relation_class) self.equiv_class = relation_class.EquivalenceClass() self.is_equiv_relation = (self.equiv_class == self.relation_class) self.skip_exact_repetitions = skip_exact_reps self.skip_equiv_repetitions = skip_equiv_reps if presorted_pair: if len(items) != 2: raise ValueError("Only provide 2 items, not %d" % len(items) + ", if presorted_pair is true.") self.remaining_items = set(items) # Use the original index of the item to maintain the original # order among equivalent items (or using the 'first' when # skip_equiv_repetitions is True). self.item_orig_idx = dict() for k, item in enumerate(items): # Note: use the index of the first occurrence of an item; self.item_orig_idx.setdefault(item, k) # do not overwrite. # When there are known equivalences, this will map each item # of an equivalence set to the full equivalence set # (not used when the relation_class is the equivalence relation): self.eq_sets = {item: {item} for item in items} # Representative for each equvalence set: self.eq_set_rep = {item: item for item in items} self.left_partners = dict() self.right_partners = dict() self.left_most_candidates = set(self.remaining_items) self.repetitions = {item: 0 for item in self.remaining_items} for item in items: self.repetitions[item] += 1 left_going_items = right_going_items = self.remaining_items if presorted_pair: left_item, right_item = items left_going_items = [right_item] right_going_items = [left_item] # Map each item to a list of (end-point, chain) pairs # where the end-point is reachable from the item via the chain # going left: self.frontier_left_chains = { item: deque([(item, [])]) for item in left_going_items } # or going right: self.frontier_right_chains = { item: deque([(item, [])]) for item in right_going_items } # Map left end-points to chains from the end-point to items: self.left_endpoint_chains = {item: [[]] for item in left_going_items} # Map right end-points to chains from items to the end-point: self.right_endpoint_chains = {item: [[]] for item in right_going_items} # Map each item to end-points reachable from that item # via generated chains going left: self.left_reachables = {item: set([item]) for item in left_going_items} # Map each item to end-points reachable from that item # via generated chains going right: self.right_reachables = { item: set([item]) for item in right_going_items } self.item_pair_chains = dict() # (left-item, right-item): chain self.prev_reported = None # Flag to track whether there is anything new to process. self.more_to_process = True self._generator = self._generate_next_items()
def apply_rounding_extraction(expr, rounding_extraction_thm, idx_to_extract=None, assumptions=USE_DEFAULTS): ''' Let F(x) represent the relevant Ceil(x), Floor(x), or Round(x) fxn calling the apply_rounding_extraction() method from the respective F(x).rounding_extraction() method. For the case of F(x) where the operand x = x_real + x_int, derive and return F(x) = F(x_real) + x_int (thus 'extracting' the integer component of x out from inside the function F()). The idx_to_extract is the zero-based index of the item in the operands of an Add(a, b, …, n) expression to attempt to extract. Assumptions may be necessary to deduce necessary conditions (for example, that x_int actually is an integer) for the simplification. For example, let F(x) = Ceil(x+2+y). Calling F(x).rounding_extraction( 1, assumptions=[InSet(x, Real), InSet(y, Real)]), will eventually end up here and return |- F(x) = Ceil(x+y) + 2 This method is utilized by the F(x).apply_reduced_simplification() method, indirectly via the F(x).rounding_extraction() method, but only after the operand x is verified to already be proven (or assumed) to be the sum of reals and integers. For the case where the entire operand x is itself an integer, see the rounding_elimination() method. This works only if the operand x is an instance of the Add class at its outermost level, e.g. x = Add(a, b, …, n). The operands of that Add class can be other things, but the extraction will work only if the inner operands a, b, ..., n are simple. ''' from proveit import n, x, y from proveit.numbers import Add # from . import round_of_real_plus_int # among other things, convert any assumptions=None # to assumptions=() to avoid later len() errors assumptions = defaults.checked_assumptions(assumptions) # for convenience while updating our equation eq = TransRelUpdater(expr, assumptions) # first use Add.commutation to (re-)arrange operands to comform # to theorem format, using user-supplied idx_to_extract if isinstance(expr.operand, Add): expr = eq.update(expr.inner_expr().operand.commutation( idx_to_extract, expr.operand.operands.num_entries() - 1, assumptions=assumptions)) # An association step -- because the later application of # the round_of_real_plus_int thm produces a grouping of the # Round operands in the chain of equivalences. # BUT, only perform the association if multiple operands are # needing to be associated: if expr.operand.operands.num_entries() - 1 > 1: expr = eq.update(expr.inner_expr().operand.association( 0, expr.operand.operands.num_entries() - 1, assumptions=assumptions)) # then update by applying the round_of_real_plus_int thm x_sub = expr.operand.operands[0] n_sub = expr.operand.operands[1] expr = eq.update( rounding_extraction_thm.instantiate({ x: x_sub, n: n_sub }, assumptions=assumptions)) return eq.relation else: raise ValueError("In attempting f(x).apply_rounding_extraction(), " "the operand x is not of class 'Add'.")
def mergesorted_items(cls, item_iterators, assumptions=USE_DEFAULTS, skip_exact_reps=False, skip_equiv_reps=False, requirements=None): ''' Given a list of Expression item iterators, with each generator yielding items in sorted order, yield every item (from all the generators) in sorted order. If skip_exact_reps is True, only yield the first of an 'item' that is repeated exactly (equal Expressions). If skip_equiv_reps is True, only yield the first of an 'item' that is repeated via a known equivalence. Passes back requirements (if provided) that are the specific merger relations. Weak relations (e.g., <=) are only considered when calling this method on a weak relation class (otherwise, only equality and strong relations are used in the sorting). ''' assumptions = defaults.checked_assumptions(assumptions) # item_iterators may be actual iterators or something that # can produce an iterator. Convert them all to actual iterators. for k, iterator in enumerate(item_iterators): try: # Try to convert to an actual iterator. item_iterators[k] = iter(iterator) except BaseException: pass # Assume it is already an actual iterator. # Start with the first item from each generator and track # the generator(s) for each item so we no where to get the # item(s) to be added after yielding the "next" sorted item. first_items = [] front_item_to_iterators = dict() for iterator in item_iterators: try: item = next(iterator) first_items.append(item) front_item_to_iterators.setdefault(item, []).append(iterator) except StopIteration: pass if requirements is None: requirements = [] # Create a TransitivitySorter. sorter = TransitivitySorter(cls, first_items, assumptions=assumptions, skip_exact_reps=skip_exact_reps, skip_equiv_reps=skip_equiv_reps) # Yield items in sorted order from the TransitivitySorter, # add to the TransitivitySorter as new items become "exposed", # and keep front_item_to_iterators updated. prev_item = None prev_iters = None for next_item in sorter: yield next_item # Yield the next item. if next_item not in front_item_to_iterators: prev_item = next_item prev_iters = set() continue # Add newly "exposed" items and update # front_item_to_iterators. iterators = front_item_to_iterators.pop(next_item) if prev_item is not None and prev_iters.isdisjoint(iterators): # next_item and prev_item are in different iterators, # so their relationship is important for establishing # the sorted order of the merger. requirement = cls._transitivitySearch(prev_item, next_item, assumptions=assumptions, automation=False) requirements.append(requirement) for iterator in iterators: try: item_to_add = next(iterator) # Add "exposed" item. sorter.add(item_to_add) # Update front_item_to_iterators. front_item_to_iterators.setdefault(item_to_add, []).append(iterator) except StopIteration: pass # Nothing more from this generator. prev_item = next_item prev_iters = set(iterators)
def insertion_point(cls, sorted_items, item_to_insert, equiv_group_pos='any', assumptions=USE_DEFAULTS, requirements=None): ''' Return the position to insert the "item to insert" into the sorted items to maintain the sorted order (according to the TransitivityRelation class cls). The sorted_items should be provably sorted, with relations between consecutive items that are Judgments. If equiv_group_pos is 'first', the insertion point will be one that would place he "item to insert" prior to any equivalent items; if equiv_group_pos is 'last', the insertion point will be one that would place the "item to insert" after any equivalent items. If it is 'first&last', both insertion points are returned as a tuple pair (first, last). The default of equiv_group_pos='any' will result in an arbitrary position relative to equivalent items. ''' equiv_class = cls.EquivalenceClass() assumptions = defaults.checked_assumptions(assumptions) if item_to_insert in sorted_items: point = sorted_items.index(item_to_insert) else: item_iterators = [sorted_items, [item_to_insert]] # Don't skip equivalent or exact representations because # we don't want the insertion point index to be thrown # off and we need to make sure the 'item_to_insert' is # included: skip_exact_reps = skip_equiv_reps = False # And don't skip exact representations for k, item in enumerate(cls.mergesorted_items(item_iterators, assumptions, skip_exact_reps, skip_equiv_reps, requirements)): if item == item_to_insert: point = k # If equiv_group_pos is 'first' or 'last', we need to make sure # we get the insertion point in the right spot with respect to # equivalent items. orig_point = point equiv_item = item_to_insert if equiv_group_pos == 'first' or equiv_group_pos == 'first&last': while point > 1: prev_point = point - 1 if item_to_insert == sorted_items[prev_point]: point -= 1 continue item1, item2 = sorted_items[prev_point], equiv_item try: relation = \ cls._transitivitySearch(item1, item2, assumptions, automation=False) except ProofFailure: msg = ("Unknown %s relationship between %s and %s" % (cls, item1, item2)) raise TransitivityException(None, assumptions, msg) if isinstance(relation.expr, equiv_class): equiv_item = sorted_items[prev_point] point -= 1 else: break first = point if equiv_group_pos == 'last' or equiv_group_pos == 'first&last': point = orig_point while point < len(sorted_items): if item_to_insert == sorted_items[point]: point += 1 continue item1, item2 = equiv_item, sorted_items[point] try: relation = \ cls._transitivitySearch(item1, item2, assumptions, automation=False) except ProofFailure: msg = ("Unknown %s relationship between %s and %s" % (cls, item1, item2)) raise TransitivityException(None, assumptions, msg) if isinstance(relation.expr, equiv_class): equiv_item = sorted_items[point] point += 1 else: break last = point if equiv_group_pos == 'first&last': return (first, last) return point
def eliminate(skolem_constants, judgment, assumptions=USE_DEFAULTS): ''' For the provided judgment of the form S |– alpha and the tuple of Skolem constants skolem_constants that had been specified earlier using the Exists.choose(), derive and return a new judgment S' |– alpha where all assumptions in S involving only the given skolem_constants are now eliminated. This process will only work if the provided skolem_constants exactly match a set of Skolem constants used earlier in an Exists.choose() method to produce the Skolem constant-based subset of assumptions you wish to eliminate from S. ''' from proveit import Lambda from proveit import n, P, Q, alpha from proveit.logic import And from proveit.core_expr_types import (x_1_to_n, y_1_to_n) from proveit.logic.booleans.quantification.existence import ( skolem_elim) if skolem_constants not in Exists.skolem_consts_to_existential: raise KeyError("In calling Exists.eliminate(), the Skolem " "constants provided were: {}, but you can only " "eliminate Skolem constants that were chosen " "earlier when using Exists.choose() and the " "Skolem constants to be eliminated must appear " "exactly as specified in the original " "Exists.choose() method.".format(skolem_constants)) existential = Exists.skolem_consts_to_existential[skolem_constants] skolem_assumptions = set( existential.choose(*skolem_constants, print_message=False)) assumptions = defaults.checked_assumptions(assumptions) assumptions = [ assumption for assumption in assumptions if assumption not in skolem_assumptions ] _P = Lambda(existential.instance_params, existential.instance_expr) if hasattr(existential, 'condition'): _Q = Lambda(existential.instance_params, existential.condition) else: # there is no condition but we still need to provide # something for _Q so we provide an empty conjunction And() _Q = Lambda(existential.instance_params, And()) _alpha = judgment _n = existential.instance_params.num_elements(assumptions) x_1_to__n = ExprTuple(x_1_to_n.replaced({n: _n})) y_1_to__n = ExprTuple(y_1_to_n.replaced({n: _n})) # express the judgment as an implication to match details of # the skolem_elim theorem being instantiated further below P_implies_alpha = _alpha.as_implication(hypothesis=_P.apply( *skolem_constants)) # the generalization to further match theorem details # can be handled through automation # P_implies_alpha.generalize( # skolem_constants, # conditions=[_Q.apply(*skolem_constants)]) return skolem_elim.instantiate( { n: _n, P: _P, Q: _Q, alpha: _alpha, x_1_to__n: skolem_constants, y_1_to__n: existential.instance_params }, assumptions=assumptions).derive_consequent(assumptions)
def conclude(self, assumptions): ''' If the instance expression, or some instance expression of nested universal quantifiers, is known to be true, conclude via generalization. Otherwise, if the domain has a 'fold_forall' method, attempt to conclude this Forall statement via 'conclude_as_folded'. ''' from proveit.logic import SubsetEq # first try to prove via generalization without automation assumptions = defaults.checked_assumptions(assumptions) expr = self instance_param_lists = [] conditions = [] while isinstance(expr, Forall): new_params = expr.explicit_instance_params() instance_param_lists.append(list(new_params)) conditions += list(expr.conditions.entries) expr = expr.instance_expr new_assumptions = assumptions + tuple(conditions) if expr.proven(assumptions=assumptions + tuple(conditions)): proven_inst_expr = expr.prove(new_assumptions) return proven_inst_expr.generalize(instance_param_lists, conditions=conditions) if (self.has_domain() and self.instance_params.is_single and self.conditions.is_single()): instance_map = Lambda(self.instance_params, self.instance_expr) domain = self.domain known_domains = set() # Next, check the known quantified instance expressions # and known set inclusions of domains to see if we can # construct a proof via inclusive universal quantification. if instance_map in Forall.known_instance_maps: known_foralls = Forall.known_instance_maps[instance_map] for known_forall in known_foralls: if (known_forall.has_domain() and known_forall.instance_params.is_single() and known_forall.conditions.is_single()): if known_forall.is_sufficient(assumptions): known_domains.add(known_forall.domain) if len(known_domains) > 0 and domain in SubsetEq.known_left_sides: # We know this quantification in other domain(s). # Do any of those include this domain? for known_inclusion in SubsetEq.known_left_sides[domain]: if known_inclusion.is_sufficient(assumptions): superset = known_inclusion.superset if superset in known_domains: # We know the quantification over a s # uperset. We can use # inclusive_universal_quantification. return self.conclude_via_domain_inclusion( superset, assumptions=assumptions) # The next 2 'ifs', one for prove_by_cases and one for # conclude_as_folded can eventually be merged as we eliminate the # separate conclude_as_folded() method. Keeping both for now # to ensure no problems as we transition. if self.has_domain() and hasattr(self.first_domain(), 'prove_by_cases'): try: return self.conclude_by_cases(assumptions) except Exception: raise ProofFailure( self, assumptions, "Unable to conclude automatically; the " "prove_by_cases method on the domain " "has failed. :o( ") # next try 'fold_as_forall' on the domain (if applicable) if self.has_domain() and hasattr(self.first_domain(), 'fold_as_forall'): # try fold_as_forall first try: return self.conclude_as_folded(assumptions) except Exception: raise ProofFailure( self, assumptions, "Unable to conclude automatically; " "the 'fold_as_forall' method on the " "domain failed.") else: # If there is no 'fold_as_forall' strategy to try, we can # attempt a different non-trivial strategy of proving # via generalization with automation. try: conditions = list(self.conditions.entries) proven_inst_expr = self.instance_expr.prove( assumptions=assumptions + tuple(conditions)) instance_param_lists = [list(self.explicit_instance_params())] # see if we can generalize multiple levels # simultaneously for a shorter proof while isinstance(proven_inst_expr.proof(), Generalization): new_params = proven_inst_expr.explicit_instance_params() instance_param_lists.append(list(new_params)) conditions += proven_inst_expr.conditions.entries proven_inst_expr = ( proven_inst_expr.proof().required_truths[0]) return proven_inst_expr.generalize(instance_param_lists, conditions=conditions) except ProofFailure: raise ProofFailure( self, assumptions, "Unable to conclude automatically; " "the domain has no 'fold_as_forall' method " "and automated generalization failed.") raise ProofFailure( self, assumptions, "Unable to conclude automatically; a " "universally quantified instance expression " "is not known to be true and the domain has " "no 'fold_as_forall' method.")
def apply_reduced_simplification(expr, assumptions=USE_DEFAULTS): ''' Let F(x) represent the relevant Ceil(x), Floor(x), or Round(x) fxn calling the apply_reduced_simplification() method from the respective F(x).do_reduced_simplification() method (which itself is likely called from the F(x).simplification() method). For the trivial case F(x) where the operand x is already known to be or assumed to be an integer, derive and return this F(x) expression equated with the operand itself: F(x) = x. For example, |- Round(2) = 2 or |- Floor(1) = 1. Assumptions may be necessary to deduce necessary conditions for the simplification (for example, for deducing that the operand really is an integer). For the case where the operand is of the form x = real + int, derive and return this F(x) expression equated with F(real) + int. For example, |- Floor(x + 2) = Floor(x) + 2. Again, assumptions may be necessary to deduce the appropriate set containments for the operands within the Add operand x. ''' from proveit import n, x from proveit.logic import InSet from proveit.numbers import Add, Integer, Real # among other things, convert any assumptions=None # to assumptions=() (thus averting len(None) errors) assumptions = defaults.checked_assumptions(assumptions) #-- -------------------------------------------------------- --# #-- Case (1): F(x) where entire operand x is known or --# #-- assumed to be an Integer. --# #-- -------------------------------------------------------- --# if InSet(expr.operand, Integer).proven(assumptions=assumptions): # Entire operand is known to be or assumed to be an integer # so we can simply remove the Ceil, Floor, or Round wrapper return expr.rounding_elimination(assumptions) #-- -------------------------------------------------------- --# # -- Case (2): F(x) where entire operand x is not yet known --* #-- to be an Integer but can EASILY be proven --# #-- to be an Integer. --# #-- -------------------------------------------------------- --# if expr.operand in InSet.known_memberships.keys(): from proveit.logic.sets import ProperSubset, SubsetEq for kt in InSet.known_memberships[expr.operand]: if kt.is_sufficient(assumptions): if (SubsetEq(kt.expr.operands[1], Integer).proven(assumptions) or ProperSubset(kt.expr.operands[1], Integer).proven(assumptions)): InSet(expr.operand, Integer).prove() return expr.rounding_elimination(assumptions) # for updating our equivalence claim(s) for the # remaining possibilities eq = TransRelUpdater(expr, assumptions) #-- -------------------------------------------------------- --# #-- Case (3): F(x) = F(Add(a,b,...,n)), where operand x is --# #-- an Add object, not known or assumed to be an --# #-- an integer, but addends might be real and --# #-- integer numbers. --# #-- -------------------------------------------------------- --# if isinstance(expr.operand, Add): # Try to partition all suboperands into Integer vs. # Non-Integer numbers, and if there is at least one integer, # try to apply the extraction theorem (allowing an error # message if the instantiation fails). subops = expr.operand.operands # Collect indices of operands known or assumed to be # integers versus real numbers versus neither indices_of_known_ints = [] indices_of_non_ints = [] for i in range(subops.num_entries()): the_subop = subops[i] # (a) first perform easiest check: is the subop already known # to be an Integer? if InSet(the_subop, Integer).proven(assumptions): indices_of_known_ints.append(i) # (b) then try something just a little harder elif the_subop in InSet.known_memberships.keys(): from proveit.logic.sets import ProperSubset, SubsetEq for kt in InSet.known_memberships[the_subop]: if kt.is_sufficient(assumptions): if (SubsetEq(kt.expr.operands[1], Integer).proven(assumptions) or ProperSubset(kt.expr.operands[1], Integer).proven(assumptions)): InSet(the_subop, Integer).prove() indices_of_known_ints.append(i) break # (c) then if the_subop is not an integer, note that instead if (i not in indices_of_known_ints): # we categorize it as a non-integer indices_of_non_ints.append(i) if len(indices_of_known_ints) > 0: # Then we have at least one known integer addend, so we # rearrange and group the addends, associating the non-ints # and associating the ints original_addends = list(subops.entries) desired_order_by_index = list(indices_of_non_ints + indices_of_known_ints) # commute to put reals first, followed by ints for i in range(len(original_addends)): init_idx = expr.operand.operands.index( original_addends[desired_order_by_index[i]]) expr = eq.update(expr.inner_expr().operand.commutation( init_idx, i, assumptions=assumptions)) # associate the non-integers (if more than 1) if len(indices_of_non_ints) > 1: # associate those elements (already re-arranged to # be at the front of the operand.operands): expr = eq.update(expr.inner_expr().operand.association( 0, len(indices_of_non_ints), assumptions=assumptions)) # associate the known integers (if more than 1) if len(indices_of_known_ints) > 1: # associate those elements (already re-arranged to # be at the end of the operand.operands): if len(indices_of_non_ints) > 0: start_idx = 1 else: start_idx = 0 expr = eq.update(expr.inner_expr().operand.association( start_idx, len(indices_of_known_ints), assumptions=assumptions)) if len(indices_of_known_ints) == subops.num_entries(): # all the addends were actually integers # could probably short-circuit this earlier! expr = eq.update(expr.rounding_elimination(assumptions)) else: expr = eq.update(expr.rounding_extraction(1, assumptions)) return eq.relation else: # We did not find any integers. # Instead of returning an error, simply return the original # rounding expression equal to itself return eq.relation #-- -------------------------------------------------------- --# #-- Case (4): F(x) where operand x is not known or assumed --# #-- to be an Integer and x is not an Add object --# #-- -------------------------------------------------------- --# # apply_reduced_simplification() function is expecting simpler # operands; instead of returning an error, though, simply return # the trivial equivalence of the original expression with itself return eq.relation
def do_reduced_simplification(self, assumptions=USE_DEFAULTS): ''' For the case Abs(x) where the operand x is already known to be or assumed to be a non-negative real, derive and return this Abs expression equated with the operand itself: |- Abs(x) = x. For the case where x is already known or assumed to be a negative real, return the Abs expression equated with the negative of the operand: |- Abs(x) = -x. Assumptions may be necessary to deduce necessary conditions for the simplification. ''' from proveit.numbers import greater, greater_eq, Mult, Neg from proveit.numbers import (zero, Natural, NaturalPos, RealNeg, RealNonNeg, RealPos) # among other things, convert any assumptions=None # to assumptions=() (thus averting len(None) errors) assumptions = defaults.checked_assumptions(assumptions) #-- -------------------------------------------------------- --# #-- Case (1): Abs(x) where entire operand x is known or --# #-- assumed to be non-negative Real. --# #-- -------------------------------------------------------- --# if InSet(self.operand, RealNonNeg).proven(assumptions=assumptions): # Entire operand is known to be or assumed to be a # non-negative real, so we can return Abs(x) = x return self.abs_elimination(operand_type='non-negative', assumptions=assumptions) #-- -------------------------------------------------------- --# #-- Case (2): Abs(x) where entire operand x is known or --# #-- assumed to be a negative Real. --# #-- -------------------------------------------------------- --# if InSet(self.operand, RealNeg).proven(assumptions=assumptions): # Entire operand is known to be or assumed to be a # negative real, so we can return Abs(x) = -x return self.abs_elimination(operand_type='negative', assumptions=assumptions) #-- -------------------------------------------------------- --# # -- Case (3): Abs(x) where entire operand x is not yet known --* #-- to be a non-negative Real, but can easily be --# #-- proven to be a non-negative Real because it is --# # -- (a) known or assumed to be ≥ 0 or #-- (b) known or assumed to be in a subset of the --# #-- non-negative Real numbers, or --# #-- (c) the addition or product of operands, all --# #-- of which are known or assumed to be non- --# # -- negative real numbers. TBA! #-- -------------------------------------------------------- --# if (greater(self.operand, zero).proven(assumptions=assumptions) and not greater_eq(self.operand, zero).proven(assumptions=assumptions)): greater_eq(self.operand, zero).prove(assumptions=assumptions) # and then it will get picked up in the next if() below if greater_eq(self.operand, zero).proven(assumptions=assumptions): from proveit.numbers.number_sets.real_numbers import ( in_real_non_neg_if_greater_eq_zero) in_real_non_neg_if_greater_eq_zero.instantiate( {a: self.operand}, assumptions=assumptions) return self.abs_elimination(operand_type='non-negative', assumptions=assumptions) if self.operand in InSet.known_memberships.keys(): for kt in InSet.known_memberships[self.operand]: if kt.is_sufficient(assumptions): if is_equal_to_or_subset_eq_of( kt.expr.operands[1], equal_sets=[RealNonNeg, RealPos], subset_eq_sets=[Natural, NaturalPos, RealPos], assumptions=assumptions): InSet(self.operand, RealNonNeg).prove(assumptions=assumptions) return self.abs_elimination( operand_type='non-negative', assumptions=assumptions) if isinstance(self.operand, Add) or isinstance(self.operand, Mult): count_of_known_memberships = 0 count_of_known_relevant_memberships = 0 for op in self.operand.operands: if op in InSet.known_memberships.keys(): count_of_known_memberships += 1 if (count_of_known_memberships == self.operand.operands.num_entries()): for op in self.operand.operands: op_temp_known_memberships = InSet.known_memberships[op] for kt in op_temp_known_memberships: if (kt.is_sufficient(assumptions) and is_equal_to_or_subset_eq_of( kt.expr.operands[1], equal_sets=[RealNonNeg, RealPos], subset_eq_sets=[ Natural, NaturalPos, RealPos, RealNonNeg ], assumptions=assumptions)): count_of_known_relevant_memberships += 1 break if (count_of_known_relevant_memberships == self.operand.operands.num_entries()): # Prove that the sum or product is in # RealNonNeg and then instantiate abs_elimination. for op in self.operand.operands: InSet(op, RealNonNeg).prove(assumptions=assumptions) return self.abs_elimination(assumptions=assumptions) #-- -------------------------------------------------------- --# #-- Case (4): Abs(x) where operand x can easily be proven --# #-- to be a negative Real number because -x is --# #-- known to be in a subset of the positive Real --# #-- numbers. --# #-- -------------------------------------------------------- --# negated_op = None if isinstance(self.operand, Neg): negated_op = self.operand.operand else: negated_op = Neg(self.operand) negated_op_simp = negated_op.simplification( assumptions=assumptions).rhs if negated_op_simp in InSet.known_memberships.keys(): from proveit.numbers.number_sets.real_numbers import ( neg_is_real_neg_if_pos_is_real_pos) for kt in InSet.known_memberships[negated_op_simp]: if kt.is_sufficient(assumptions): if is_equal_to_or_subset_eq_of( kt.expr.operands[1], equal_sets=[RealNonNeg, RealPos], subset_sets=[NaturalPos, RealPos], subset_eq_sets=[NaturalPos, RealPos], assumptions=assumptions): InSet(negated_op_simp, RealPos).prove(assumptions=assumptions) neg_is_real_neg_if_pos_is_real_pos.instantiate( {a: negated_op_simp}, assumptions=assumptions) return self.abs_elimination(operand_type='negative', assumptions=assumptions) # for updating our equivalence claim(s) for the # remaining possibilities from proveit import TransRelUpdater eq = TransRelUpdater(self, assumptions) return eq.relation
def factorization(self, the_factor, pull="left", group_factor=True, group_remainder=None, assumptions=USE_DEFAULTS): ''' If group_factor is True and the_factor is a product, it will be grouped together as a sub-product. group_remainder is not relevant kept for compatibility with other factor methods. Returns the equality that equates self to this new version. Give any assumptions necessary to prove that the operands are in Complex so that the associative and commutation theorems are applicable. ''' from proveit.numbers.multiplication import distribute_through_summation from proveit.numbers import Mult if not free_vars(the_factor).isdisjoint(self.indices): raise Exception( 'Cannot factor anything involving summation indices out of a summation' ) expr = self # for convenience updating our equation eq = TransRelUpdater(expr, assumptions) assumptions = defaults.checked_assumptions(assumptions) # We may need to factor the summand within the summation summand_assumptions = assumptions + self.condition summand_factorization = self.summand.factorization( the_factor, pull, group_factor=False, group_remainder=True, assumptions=summand_assumptions) summand_instance_equivalence = summand_factor_eq.generalize( self.indices, domain=self.domain).checked(assumptions) eq = Equation( self.instance_substitution(summand_instance_equivalence).checked( assumptions)) factor_operands = the_factor.operands if isinstance( the_factor, Mult) else the_factor x_dummy, z_dummy = self.safe_dummy_vars(2) # Now do the actual factoring by reversing distribution if pull == 'left': Pop, Pop_sub = Operation( P, self.indices), summand_factor_eq.rhs.operands[-1] x_sub = factor_operands z_sub = [] elif pull == 'right': Pop, Pop_sub = Operation( P, self.indices), summand_factor_eq.rhs.operands[0] x_sub = [] z_sub = factor_operands # We need to deduce that the_factor is in Complex and that all # instances of Pop_sup are in Complex. deduce_in_complex(factor_operands, assumptions=assumptions) deduce_in_complex(Pop_sub, assumptions=assumptions | {InSet(idx, self.domain) for idx in self.indices}).generalize( self.indices, domain=self.domain).checked(assumptions) # Now we instantiate distribut_through_summation_rev spec1 = distribute_through_summation_rev.instantiate({ Pop: Pop_sub, S: self.domain, y_etc: self.indices, x_etc: Etcetera(Multi_variable(x_dummy)), z_etc: Etcetera(Multi_variable(z_dummy)) }).checked() eq.update(spec1.derive_conclusion().instantiate({ Etcetera(Multi_variable(x_dummy)): x_sub, Etcetera(Multi_variable(z_dummy)): z_sub })) if group_factor and factor_operands.num_entries() > 1: eq.update( eq.eq_expr.rhs.group(end_idx=factor_operands.num_entries(), assumptions=assumptions)) return eq.eq_expr # .checked(assumptions)
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 conclude(self, assumptions=USE_DEFAULTS): ''' Attempt to conclude the divisibility claim in various ways: (1) simple reflexivity (x|x); (2) simple x|0 for x ≠ 0; (3) simple x|xy or x|yx scenario (4) x^n | y^n if x|y is known or assumed (5) x|y if (x^n)|(y^n) is known or assumed (6) via transitivity. ''' # Check validity of assumptions (and convert assumptions=None # to assumptions=(), thus averting len(None) errors). assumptions = defaults.checked_assumptions(assumptions) #-- -------------------------------------------------------- --# #-- Case (1): x|x with x != 0 known or assumed --# #-- -------------------------------------------------------- --# from proveit.logic import InSet, NotEquals from proveit.numbers import zero, Complex err_str = "In Divides.conclude() we tried:\n" if self.lhs == self.rhs: if (NotEquals(self.lhs, zero).proven(assumptions=assumptions) and InSet(self.lhs, Complex).proven(assumptions=assumptions)): # Trivial x|x with complex x ≠ 0 return self.conclude_via_reflexivity(assumptions) else: err_str = err_str + ( "Case: lhs = rhs. " "Although lhs = rhs = {0}, either {0} is not known to " "be non-zero or {0} is not known to be in the complex " "numbers (or both). Try proving one or both of those " "claims first.\n".format(self.lhs)) # raise ProofFailure(self, assumptions, err_str) #-- -------------------------------------------------------- --# #-- Case (2): x|0 with x != 0 known or assumed --# #-- -------------------------------------------------------- --# if self.rhs == zero: if (NotEquals(self.lhs, zero).proven(assumptions=assumptions) and InSet(self.lhs, Complex).proven(assumptions=assumptions)): # We have 0/x with complex x ≠ 0 return self.conclude_via_zero_factor(assumptions) else: err_str = err_str + ( "Case: rhs = 0. " "Although rhs = 0, either the lhs {0} is not known to " "be non-zero or {0} is not known to be in the complex " "numbers (or both). Try proving one or both of those " "claims first.\n".format(self.lhs)) #-- -------------------------------------------------------- --# #-- Case (3): very simple version of x|xy or x|yx --# #-- -------------------------------------------------------- --# # return self.conclude_via_factor(assumptions) try: return self.conclude_via_factor(assumptions) except Exception as e: err_str = err_str + ( "Case: x|xy. This possible case returned the following " "error message: {0} \n".format(e)) pass #-- -------------------------------------------------------- --# #-- Case (4): x^n|y^n if x|y --# #-- -------------------------------------------------------- --# if (isinstance(self.lhs, Exp) and isinstance(self.rhs, Exp)): if (InSet(self.lhs.base, Integer).proven(assumptions) and InSet(self.rhs.base, Integer).proven(assumptions) and Equals(self.lhs.exponent, self.rhs.exponent) and InSet( self.lhs.exponent, NaturalPos).proven(assumptions) and Divides(self.lhs.base, self.rhs.base).proven(assumptions)): return (Divides(self.lhs.base, self.rhs.base).introduce_common_exponent( self.lhs.exponent, assumptions=assumptions)) else: err_str = err_str + ( "Case: (x^n) | (y^n). One or more of the conditions " "(such as domain requirements or x|y) were not " "already proven. Check the conditions for the " "common_exponent_introduction theorem in the " "number/divisibility package.\n") else: err_str = err_str + ( "Case: (x^n) | (y^n). Does not appear applicable.\n") """ # This case should be handled on the "side-effect" end. #-- -------------------------------------------------------- --# #-- Case (5): x|y if x^n|y^n (for some small pos nat n) --# #-- -------------------------------------------------------- --# possible_exps = range(2,10) for e in possible_exps: # print("exp = {}".format(e)) if (Divides(Exp(self.lhs, num(e)), Exp(self.rhs, num(e))). proven(assumptions)): # print(" Divides found for exp = {}".format(test_exp)) return (Divides(Exp(self.lhs, test_exp), Exp(self.rhs, test_exp)). eliminate_common_exponent(assumptions=assumptions)) err_str = err_str + ( "Case: x|y where we already have (x^n)|(y^n). " "Does not appear applicable.\n") """ #-- -------------------------------------------------------- --# #-- Case (6): x|z with x|y and y|z known or assumed --# #-- -------------------------------------------------------- --# # Seek out the appropriate x|y and y|z and use transitivity # to get x|z, utilizing the conclude_via_transitivity() method # available for instances of TransitiveRelation try: return self.conclude_via_transitivity(assumptions) except Exception as e: err_str = err_str + ( "Case: transitivity search. In attempting to use " "conclude_via_transitivity(), obtained the following " "error message: {0}.".format(e)) pass raise ProofFailure(self, assumptions, err_str)