Ejemplo n.º 1
0
 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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
 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')
Ejemplo n.º 4
0
 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
Ejemplo n.º 5
0
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)
Ejemplo n.º 6
0
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)
Ejemplo n.º 7
0
    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)
Ejemplo n.º 8
0
 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
Ejemplo n.º 9
0
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)
Ejemplo n.º 10
0
 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)
Ejemplo n.º 11
0
 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)
Ejemplo n.º 12
0
 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)
Ejemplo n.º 13
0
 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
Ejemplo n.º 14
0
    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)
Ejemplo n.º 15
0
    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)
Ejemplo n.º 16
0
 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)))
Ejemplo n.º 17
0
    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)
Ejemplo n.º 18
0
    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()
Ejemplo n.º 19
0
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'.")
Ejemplo n.º 20
0
    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)
Ejemplo n.º 21
0
    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
Ejemplo n.º 22
0
    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)
Ejemplo n.º 23
0
    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.")
Ejemplo n.º 24
0
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
Ejemplo n.º 25
0
    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
Ejemplo n.º 26
0
    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)
Ejemplo n.º 27
0
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
Ejemplo n.º 28
0
    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)