Ejemplo n.º 1
0
    def conclude(self, assumptions):
        from proveit.logic import InSet, NotEquals
        from proveit.numbers import (Rational, RationalNonZero, RationalPos,
                                     RationalNeg, RationalNonNeg, Less,
                                     greater, greater_eq, zero)

        # If we known the element is in Q, we may be able to
        # prove that is in RationalNonZero, RationalPos, RationalNeg, or
        # RationalNonNeg if we know its relation to zero.
        if (self.number_set != Rational
                and InSet(self.element, Rational).proven(assumptions)):
            if self.number_set == RationalNonZero:
                if NotEquals(self.element, zero).proven(assumptions):
                    from . import non_zero_rational_is_rational_non_zero
                    return non_zero_rational_is_rational_non_zero.instantiate(
                        {q: self.element}, assumptions=assumptions)
            if self.number_set == RationalPos:
                if greater(self.element, zero).proven(assumptions):
                    from . import positive_rational_is_rational_pos
                    return positive_rational_is_rational_pos.instantiate(
                        {q: self.element}, assumptions=assumptions)
            if self.number_set == RationalNeg:
                if Less(self.element, zero).proven():
                    from . import negative_rational_is_rational_neg
                    return negative_rational_is_rational_neg.instantiate(
                        {q: self.element}, assumptions=assumptions)
            if self.number_set == RationalNonNeg:
                if greater_eq(self.element, zero).proven():
                    from . import non_neg_rational_in_rational_neg
                    return non_neg_rational_in_rational_neg.instantiate(
                        {q: self.element}, assumptions=assumptions)

        # Resort to the default NumberMembership.conclude strategies.
        return NumberMembership.conclude(self, assumptions)
Ejemplo n.º 2
0
    def derive_element_in_restricted_number_set(self, **defaults_config):
        '''
        From (member in Interval(x, y)), where x ≥ 0 or y ≤ 0,
        deduce that the element is in Natural, NaturalPos, IntegerNeg,
        or IntegerNonPos as appropriate.
        '''
        _a = self.domain.lower_bound
        _b = self.domain.upper_bound
        _n = self.element

        # We wish to deduce a fact based upon the following
        # membership fact:
        self.expr.prove()

        if (not InSet(_a, Natural).proven()
                and not InSet(_b, IntegerNonPos).proven()):
            # If we don't know that a ≥ 0 or b ≤ 0, we can't prove
            # the element is in either restricted number set
            # (NaturalPos or IntegerNeg).  So, try to sort a, b, 0
            # to work this out.
            LessEq.sort([_a, _b, zero])

        if InSet(_a, Natural).proven():
            try:
                _a.deduce_in_number_set(NaturalPos, automation=False)
            except Exception:
                pass
            if InSet(_a, NaturalPos).proven():
                # member in N^{>0}
                lower_bounding = self.derive_element_lower_bound()
                a_bounding = greater(_a, zero)
                lower_bounding.apply_transitivity(a_bounding)
                return InSet(_n, NaturalPos).prove()
            else:
                # member in N
                lower_bounding = self.derive_element_lower_bound()
                a_bounding = greater_eq(_a, zero)
                lower_bounding.apply_transitivity(a_bounding)
                return InSet(_n, Natural).prove()
        if InSet(_b, IntegerNonPos).proven():
            try:
                _b.deduce_in_number_set(IntegerNeg, automation=False)
            except Exception:
                pass
            if InSet(_b, IntegerNeg).proven():
                # member in Z^{<0}
                upper_bounding = self.derive_element_upper_bound()
                b_bounding = Less(_b, zero)
                upper_bounding.apply_transitivity(b_bounding)
                return InSet(_n, IntegerNeg).prove()
            else:
                # member in Z^{≤0}
                upper_bounding = self.derive_element_upper_bound()
                b_bounding = LessEq(_b, zero)
                upper_bounding.apply_transitivity(b_bounding)
                return InSet(_n, IntegerNonPos).prove()
Ejemplo n.º 3
0
 def deduce_number_set(self, **defaults_config):
     from proveit.numbers import deduce_number_set, greater
     _a = self.digits[0]
     _b = self.digits[1:]
     try:
         deduce_number_set(_a)
     except UnsatisfiedPrerequisites:
         pass
     if greater(_a, zero).proven():
         return self.deduce_in_natural_pos()
     else:
         return self.deduce_in_natural()
Ejemplo n.º 4
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.º 5
0
 def deduce_positive(self, **defaults_config):
     # Deduce that this absolute value is greater than zero
     # given its argument is not equal zero.
     from proveit.numbers import RealPos, zero, greater
     InSet(self, RealPos).prove()
     return greater(self, zero).prove()
Ejemplo n.º 6
0
 def conclude(self, **defaults_config):
     from proveit.numbers import zero, greater
     if (InSet(self.element, Real).proven() and
             greater(self.element, zero).proven()):
         return self.conclude_as_last_resort()
     return NumberMembership.conclude(self)
Ejemplo n.º 7
0
    def bound_via_operand_bound(self, operand_relation, **defaults_config):
        '''
        For simple cases, deduce a bound on this Logarithmic (Log)
        object given a bound on its operand. For example, suppose
        x = Log(2, y) and we know that y >= 2. Then
        x.bound_via_operand_bound(y >= 2) returns x >= Log(2, 2) = 1.
        This method currently works MAINLY for expressions of the
        form Log(a, b) where a > 1, where we know something of the
        form b < y or b <= y or b > y or b >= y involving the 'antilog'
        argument b. b itself will also need to be in RealPos, which
        might need to be pre-proven or provided as an assumption.
        
        Future development should address cases where 0 < a < 1,
        which result in decreasing logarithmic functions.

        Also see NumberOperation.deduce_bound() and compare to the
        bound_via_operand_bound() method found in the Div, Neg, and
        Exp classes.
        '''
        from proveit import Judgment
        from proveit import a, x, y
        from proveit.numbers import (zero, one, two, greater, greater_eq, Less,
                                     LessEq, NumberOrderingRelation,
                                     RealNonNeg)
        if isinstance(operand_relation, Judgment):
            operand_relation = operand_relation.expr
        if not isinstance(operand_relation, NumberOrderingRelation):
            raise TypeError(
                "In Exp.bound_via_operand_bound(), the "
                "'operand_relation' argument is expected to be a number "
                "relation (<, >, ≤, or ≥), but instead was {}.".format(
                    operand_relation))

        lhs = operand_relation.lhs
        # should be able to generalize this later
        # no need to limit to just lhs, right?
        if lhs != self.antilog:
            raise ValueError(
                "In Log.bound_via_operand_bound(), the left side of "
                "the 'operand_relation' argument {0} is expected to "
                "match the Log antilog operand {1}.".format(
                    operand_relation, self.antilog))

        # assign x and y subs according to std Less or LessEq relations
        _x_sub = operand_relation.normal_lhs
        _y_sub = operand_relation.normal_rhs
        _a_sub = self.base

        # Multiple cases to eventually consider but for now
        # we only consider four related Log(a, x) vs. Log(a, y)
        # cases for the increasing (base a > 1) version of a Log fxn:
        #  (1) base a > 1, 0 < x <  y
        #  (2) base a > 1, 0 < x <= y
        #  (3) base a > 1, 0 < y < x
        #  (4) base a > 1, 0 < y <= x

        # Cases (1)-(4): 0 < x < y or 0 < y < x
        if (greater(_a_sub, one).proven() and greater(_x_sub, zero).proven()):
            if isinstance(operand_relation, Less):
                from proveit.numbers.logarithms import (log_increasing_less)
                bound = log_increasing_less.instantiate({
                    x: _x_sub,
                    y: _y_sub,
                    a: _a_sub
                })
            elif isinstance(operand_relation, LessEq):
                from proveit.numbers.logarithms import (log_increasing_less_eq)
                bound = log_increasing_less_eq.instantiate({
                    x: _x_sub,
                    y: _y_sub,
                    a: _a_sub
                })
            else:
                raise TypeError(
                    "In Log.bound_via_operand_bound(), the 'operand_relation' "
                    "argument is expected to be a 'Less', 'LessEq', 'greater', "
                    "or 'greater_eq' relation. Instead we have {}.".format(
                        operand_relation))

        else:
            raise ValueError(
                "In calling Log.bound_via_operand_bound(), a "
                "specific matching case was not found for {0}. "
                "Check/confirm that {1} > 1 and {2} is a positive "
                "Real".format(self, _a_sub, _x_sub))

        if bound.rhs == self:
            return bound.with_direction_reversed()
        return bound
Ejemplo n.º 8
0
    def bound_via_denominator_bound(self, relation, **defaults_config):
        '''
        Given a relation applicable to the numerator,  bound this
        division accordingly.  For example,
        if self is "a / b" and the relation is b > y
        return (a / b) < (a / y), provided a, b, and y are positive.

        Also see NumberOperation.deduce_bound.
        '''
        from proveit.numbers import zero, Less, LessEq, greater, greater_eq
        from . import (strong_div_from_denom_bound__all_pos,
                       weak_div_from_denom_bound__all_pos,
                       strong_div_from_denom_bound__all_neg,
                       weak_div_from_denom_bound__all_neg,
                       strong_div_from_denom_bound__neg_over_pos,
                       weak_div_from_denom_bound__neg_over_pos,
                       strong_div_from_denom_bound__pos_over_neg,
                       weak_div_from_denom_bound__pos_over_neg)
        if isinstance(relation, Judgment):
            relation = relation.expr
        if not (isinstance(relation, Less) or isinstance(relation, LessEq)):
            raise TypeError("relation is expected to be Less "
                            "or LessEq number relations, not %s" % relation)
        if self.denominator not in relation.operands:
            raise ValueError("relation is expected to involve the "
                             "denominator of %s.  %s does not." %
                             (self, relation))
        _a = self.numerator
        _x = relation.normal_lhs
        _y = relation.normal_rhs
        try:
            # Ensure that we relate both _x and _y to zero by knowing
            # one of these.
            ordering = LessEq.sort((_x, _y, zero))
            ordering.operands[0].apply_transitivity(ordering.operands[1])
        except:
            pass  # We'll generate an appropriate error below.
        try:
            deduce_number_set(self.numerator)
            deduce_number_set(self.denominator)
        except UnsatisfiedPrerequisites:
            pass
        pos_numer = greater_eq(self.numerator, zero).proven()
        neg_numer = LessEq(self.numerator, zero).proven()
        pos_denom = greater(self.denominator, zero).proven()
        neg_denom = Less(self.denominator, zero).proven()
        if not (pos_numer or neg_numer) or not (pos_denom or neg_denom):
            raise UnsatisfiedPrerequisites(
                "We must know the sign of the numerator and "
                "denominator of %s before we can use "
                "'bound_via_denominator_bound'." % self)
        if pos_numer and pos_denom:
            if isinstance(relation, Less):
                bound = strong_div_from_denom_bound__all_pos.instantiate({
                    a: _a,
                    x: _x,
                    y: _y
                })
            elif isinstance(relation, LessEq):
                bound = weak_div_from_denom_bound__all_pos.instantiate({
                    a: _a,
                    x: _x,
                    y: _y
                })
        elif neg_numer and neg_denom:
            if isinstance(relation, Less):
                bound = strong_div_from_denom_bound__all_neg.instantiate({
                    a: _a,
                    x: _x,
                    y: _y
                })
            elif isinstance(relation, LessEq):
                bound = weak_div_from_denom_bound__all_neg.instantiate({
                    a: _a,
                    x: _x,
                    y: _y
                })
        elif pos_numer and neg_denom:
            if isinstance(relation, Less):
                bound = strong_div_from_denom_bound__pos_over_neg.instantiate({
                    a:
                    _a,
                    x:
                    _x,
                    y:
                    _y
                })
            elif isinstance(relation, LessEq):
                bound = weak_div_from_denom_bound__pos_over_neg.instantiate({
                    a:
                    _a,
                    x:
                    _x,
                    y:
                    _y
                })
        elif neg_numer and pos_denom:
            if isinstance(relation, Less):
                bound = strong_div_from_denom_bound__neg_over_pos.instantiate({
                    a:
                    _a,
                    x:
                    _x,
                    y:
                    _y
                })
            elif isinstance(relation, LessEq):
                bound = weak_div_from_denom_bound__neg_over_pos.instantiate({
                    a:
                    _a,
                    x:
                    _x,
                    y:
                    _y
                })
        else:
            raise UnsatisfiedPrerequisites(
                "We must know whether or not the denominator of %s "
                "is positive or negative before we can use "
                "'bound_via_denominator_bound'." % self)
        if bound.rhs == self:
            return bound.with_direction_reversed()
        return bound
Ejemplo n.º 9
0
    def bound_via_numerator_bound(self, relation, **defaults_config):
        '''
        Given a relation applicable to the numerator,  bound this
        division accordingly.  For example,
        if self is "a / b" and the relation is a < x
        return (a / b) < (x / b), provided b > 0.

        Also see NumberOperation.deduce_bound.
        '''
        from proveit.numbers import zero, Less, LessEq, greater
        from . import (strong_div_from_numer_bound__pos_denom,
                       weak_div_from_numer_bound__pos_denom,
                       strong_div_from_numer_bound__neg_denom,
                       weak_div_from_numer_bound__neg_denom)
        if isinstance(relation, Judgment):
            relation = relation.expr
        if not (isinstance(relation, Less) or isinstance(relation, LessEq)):
            raise TypeError("relation is expected to be Less "
                            "or LessEq number relations, not %s" % relation)
        if self.numerator not in relation.operands:
            raise ValueError("relation is expected to involve the "
                             "numerator of %s.  %s does not." %
                             (self, relation))
        _a = self.denominator
        _x = relation.normal_lhs
        _y = relation.normal_rhs
        try:
            deduce_number_set(self.denominator)
        except UnsatisfiedPrerequisites:
            pass
        if greater(self.denominator, zero).proven():
            if isinstance(relation, Less):
                bound = strong_div_from_numer_bound__pos_denom.instantiate({
                    a:
                    _a,
                    x:
                    _x,
                    y:
                    _y
                })
            elif isinstance(relation, LessEq):
                bound = weak_div_from_numer_bound__pos_denom.instantiate({
                    a: _a,
                    x: _x,
                    y: _y
                })
        elif Less(self.denominator, zero).proven():
            if isinstance(relation, Less):
                bound = strong_div_from_numer_bound__neg_denom.instantiate({
                    a:
                    _a,
                    x:
                    _x,
                    y:
                    _y
                })
            elif isinstance(relation, LessEq):
                bound = weak_div_from_numer_bound__neg_denom.instantiate({
                    a: _a,
                    x: _x,
                    y: _y
                })
        else:
            raise UnsatisfiedPrerequisites(
                "We must know whether the denominator of %s "
                "is positive or negative before we can use "
                "'bound_via_numerator_bound'." % self)
        if bound.rhs == self:
            return bound.with_direction_reversed()
        return bound
Ejemplo n.º 10
0
 def conclude(self, **defaults_config):
     if (InSet(self.element, Integer).proven()
             and greater(self.element, zero).proven()):
         return self.conclude_as_last_resort()
     return NumberMembership.conclude(self)
Ejemplo n.º 11
0
    def derive_element_in_restricted_number_set(self, **defaults_config):
        '''
        From (element in IntervalXX(x, y)), where x ≥ 0 or y ≤ 0,
        derive that the element is in RealPos, RealNeg, RealNonPos, or
        RealNonNeg as appropriate.
        '''
        from proveit.numbers import (zero, RealPos, RealNonNeg, 
                                     RealNeg, RealNonPos)
        from proveit.numbers import Less, LessEq, greater, greater_eq        
        _a = self.domain.lower_bound
        _b = self.domain.upper_bound
        _n = self.element
        
        # We wish to deduce a fact based upon the following
        # membership fact:
        self.expr.prove()

        if (not InSet(_a, RealNonNeg).proven() and 
                not InSet(_b, RealNonPos).proven()):
            try:
                _a.deduce_in_number_set(RealNonNeg, automation=False)
            except Exception:
                pass
            try:
                _b.deduce_in_number_set(RealNonPos, automation=False)
            except Exception:
                pass
            if (not InSet(_a, RealNonNeg).proven() and 
                    not InSet(_b, RealNonPos).proven()):
                # If we don't know that a ≥ 0 or b ≤ 0, we can't prove
                # the element is in either restricted number set
                # (NaturalPos or IntegerNeg).  So, try to sort a, b, 0
                # to work this out.
                LessEq.sort([_a, _b, zero])

        if InSet(_a, RealNonNeg).proven():
            try:
                _a.deduce_in_number_set(RealPos, automation=False)
            except Exception:
                pass
            lower_bound = self.derive_element_lower_bound()
            a_bound = greater_eq(_a, zero)
            if InSet(_a, RealPos).proven():
                a_bound = greater(_a, zero)
            lower_bound.apply_transitivity(a_bound)
            if (isinstance(self, IntervalOO) 
                    or isinstance(self, IntervalOC)
                    or InSet(_a, RealPos).proven()):
                # member in R^{>0}
                return InSet(_n, RealPos).prove()
            else:
                # member in R^{≥0}
                return InSet(_n, RealNonNeg).prove()
        if InSet(_b, RealNonPos).proven():
            try:
                _b.deduce_in_number_set(RealNeg, automation=False)
            except Exception:
                pass
            upper_bound = self.derive_element_upper_bound()
            b_bound = LessEq(_b, zero)
            if InSet(_b, RealNeg).proven():
                b_bound = Less(_b, zero)                
            upper_bound.apply_transitivity(b_bound)
            if (isinstance(self, IntervalOO)
                    or isinstance(self, IntervalCO)
                    or InSet(_b, RealNeg).proven()):
                # member in R^{<0}
                return InSet(_n, RealNeg).prove()
            else:
                # member in R^{≤0}
                return InSet(_n, RealNonPos).prove()
Ejemplo n.º 12
0
    def bound_via_operand_bound(self, operand_relation, **defaults_config):
        '''
        For simple cases, deduce a bound on this Exp object given a
        bound on its operand. For example, suppose x = Exp(y, 2) and
        we know that y >= 2. Then x.bound_via_operand_bound(y >= 2)
        returns x >= 2^2 = 4.
        This method currently works MAINLY for expressions
        of the form Exp(x, a) for non-negative real x and real exponent
        'a', where we know something of the form x < y (or x ≤ y, x > y,
        x ≥ y) involving the base of the exponential expression.
        The result also depends on knowing the relationship between the
        exponent 'a' and zero, which might need to be pre-proven or
        provided as an assumption (e.g. in the form 'a > 0' or
        InSet(a, RealNeg), etc).
        A special case also deals with a negative base raised to the
        power of 2.
        This method also works for special cases of the form Exp(a, x),
        where a > 1 and the operand_relation involve the exponent x.

        Future development will address operand_relations
        involving the exponent x in expressions of the form a^x when
        the base 0 < a < 1, and expand the special negative
        base case to include all even and odd exponent cases.
        Also see NumberOperation.deduce_bound and compare to the
        bound_via_operand_bound() method found in the Div and Neg
        classes.
        '''
        from proveit import Judgment
        from proveit.numbers import (
                two, greater, greater_eq, Less, LessEq,
                NumberOrderingRelation, RealNonNeg)
        if isinstance(operand_relation, Judgment):
            operand_relation = operand_relation.expr
        if not isinstance(operand_relation, NumberOrderingRelation):
            raise TypeError(
                    "In Exp.bound_via_operand_bound(), the "
                    "'operand_relation' argument is expected to be a number "
                    "relation (<, >, ≤, or ≥), but instead was {}.".
                    format(operand_relation))

        lhs = operand_relation.lhs
        # should be able to generalize this later
        # no need to limit to just lhs, right?
        if lhs != self.base and lhs != self.exponent:
            raise ValueError(
                    "In Exp.bound_via_operand_bound(), the left side of "
                    "the 'operand_relation' argument {0} is expected to "
                    "match either the Exp base operand {1} or the "
                    "Exp exponent operand {2}.".
                    format(operand_relation, self.base, self.exponent))

        # assign x and y subs according to std Less or LessEq relations
        _x_sub = operand_relation.normal_lhs
        _y_sub = operand_relation.normal_rhs
        if lhs == self.base:
            _a_sub = self.exponent
        else:
            _a_sub = self.base

        # I. Default case: the user-supplied operand relation involves
        #    the BASE of the Exp expression x^a
        if lhs == self.base:

            # Several cases to consider:
            #  (1) a > 0, 0 ≤ x < y
            #  (2) a > 0, 0 ≤ x ≤ y
            #  (3) a ≥ 0, 0 < x < y
            #  (4) a ≥ 0, 0 < x ≤ y
            #  (5) a < 0, 0 < x < y
            #  (6) a < 0, 0 < x ≤ y
            #  (7) a ≤ 0, 0 < x < y
            #  (8) a ≤ 0, 0 < x ≤ y
            # =====================
            #  (9) a = 2, y < x < 0
            # (10) a = 2, y ≤ x < 0

            # Cases (1) and (2): exponent a > 0
            if (greater(_a_sub, zero).proven() and
                greater_eq(_x_sub, zero).proven()):
                if isinstance(operand_relation, Less):
                    from proveit.numbers.exponentiation import exp_pos_less
                    bound = exp_pos_less.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                elif isinstance(operand_relation, LessEq):
                    from proveit.numbers.exponentiation import exp_pos_lesseq
                    bound = exp_pos_lesseq.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                else:
                    raise TypeError(
                        "In Exp.bound_via_operand_bound(), the 'operand_relation' "
                        "argument is expected to be a 'Less', 'LessEq', 'greater', "
                        "or 'greater_eq' relation. Instead we have {}.".
                        format(operand_relation))

            # Cases (3) and (4): exponent a ≥ 0
            elif (greater_eq(_a_sub, zero).proven() and
                greater(_x_sub, zero).proven()):
                if isinstance(operand_relation, Less):
                    from proveit.numbers.exponentiation import exp_nonneg_less
                    bound = exp_nonneg_less.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                elif isinstance(operand_relation, LessEq):
                    from proveit.numbers.exponentiation import exp_nonneg_lesseq
                    bound = exp_nonneg_lesseq.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                else:
                    raise TypeError(
                        "In Exp.bound_via_operand_bound(), the 'operand_relation' "
                        "argument is expected to be a 'Less', 'LessEq', 'greater', "
                        "or 'greater_eq' relation. Instead we have {}.".
                        format(operand_relation))

            # Cases (5) and (6): exponent a < 0
            elif (Less(_a_sub, zero).proven() and
                greater(_x_sub, zero).proven()):
                if isinstance(operand_relation, Less):
                    from proveit.numbers.exponentiation import exp_neg_less
                    bound = exp_neg_less.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                elif isinstance(operand_relation, LessEq):
                    from proveit.numbers.exponentiation import exp_neg_lesseq
                    bound = exp_neg_lesseq.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                else:
                    raise TypeError(
                        "In Exp.bound_via_operand_bound(), the 'operand_relation' "
                        "argument is expected to be a 'Less', 'LessEq', 'greater', "
                        "or 'greater_eq' relation. Instead we have {}.".
                        format(operand_relation))

            # Cases (7) and (8): exponent a ≤ 0
            elif (LessEq(_a_sub, zero).proven() and
                greater(_x_sub, zero).proven()):
                if isinstance(operand_relation, Less):
                    from proveit.numbers.exponentiation import exp_nonpos_less
                    bound = exp_nonpos_less.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                elif isinstance(operand_relation, LessEq):
                    from proveit.numbers.exponentiation import exp_nonpos_lesseq
                    bound = exp_nonpos_lesseq.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                else:
                    raise TypeError(
                        "In Exp.bound_via_operand_bound(), the 'operand_relation' "
                        "argument is expected to be a 'Less', 'LessEq', 'greater', "
                        "or 'greater_eq' relation. Instead we have {}.".
                        format(operand_relation))

            # Cases (9) and (10): exponent a = 2
            # with x < y < 0 or x ≤ y < 0

            elif (_a_sub == two and
                Less(_y_sub, zero).proven()):
                if isinstance(operand_relation, Less):
                    from proveit.numbers.exponentiation import (
                            exp_even_neg_base_less)
                    bound = exp_even_neg_base_less.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                elif isinstance(operand_relation, LessEq):
                    from proveit.numbers.exponentiation import (
                            exp_even_neg_base_lesseq)
                    bound = exp_even_neg_base_lesseq.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                else:
                    raise TypeError(
                        "In Exp.bound_via_operand_bound(), the 'operand_relation' "
                        "argument is expected to be a 'Less', 'LessEq', 'greater', "
                        "or 'greater_eq' relation. Instead we have {}.".
                        format(operand_relation))

            else:
                raise ValueError(
                        "In calling Exp.bound_via_operand_bound(), a "
                        "specific matching case was not found for {}.".
                        format(self))

        # II. 2nd main case: the user-supplied operand relation involves
        #    the EXPONENT of the Exp expression a^x
        elif lhs == self.exponent:

            # Several cases to consider (others to be developed)
            # considering the Exp expression a^x with a, x in Real:
            #  (1) a > 1, x < y
            #  (2) a > 1, x ≤ y
            #  (3) a > 1, y < x
            #  (4) a > 1, y ≤ x
            # Other cases to be developed involving base a < 1,
            # which produces a monotonically-decreasing function.

            # Cases (1)-(4): base a > 1, a^x monotonically increasing
            if (greater(_a_sub, one).proven() and
                InSet(_x_sub, Real).proven()):
                if isinstance(operand_relation, Less):
                    from proveit.numbers.exponentiation import (
                            exp_monotonicity_large_base_less)
                    bound = exp_monotonicity_large_base_less.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                elif isinstance(operand_relation, LessEq):
                    from proveit.numbers.exponentiation import (
                        exp_monotonicity_large_base_less_eq)
                    bound = exp_monotonicity_large_base_less_eq.instantiate(
                            {x: _x_sub, y: _y_sub, a: _a_sub})
                else:
                    raise TypeError(
                        "In Exp.bound_via_operand_bound(), the 'operand_relation' "
                        "argument is expected to be a 'Less', 'LessEq', 'greater', "
                        "or 'greater_eq' relation. Instead we have {}.".
                        format(operand_relation))
            else:
                raise ValueError(
                        "In Exp.bound_via_operand_bound(), either the "
                        "base {0} is not known to be greater than 1 and/or "
                        "the operand {1} is not known to be Real.".
                        format(_a_sub, _x_sub))

        else:
            raise ValueError("OOOPS!")

        if bound.rhs == self:
            return bound.with_direction_reversed()
        return bound