Exemple #1
0
    def shallow_simplification(self, *, must_evaluate=False, **defaults_config):
        '''
        For the simple binary case Max(a, b), returns a proven
        simplification equation for this Max expression assuming the
        operands 'a' and 'b' have been simplified and that we know or
        have assumed that either a >= b or b > a. If such relational
        knowledge is not to be had, we simply return the equation of
        the Max expression with itself.
        Cases with more than 2 operands are not yet handled.
        '''
        from proveit.logic import Equals
        from proveit.numbers import greater_eq

        # We're only set up to deal with binary operator version
        if not self.operands.is_double():
            # Default is no simplification if not a binary operation.
            return Equals(self, self).prove()

        # If binary and we know how operands 'a' and 'b' are related ...
        op_01, op_02 = self.operands[0], self.operands[1]
        if (greater_eq(op_01, op_02).proven()
            or greater_eq(op_02, op_01).proven()):
            from proveit import x, y
            from proveit.numbers.ordering import max_def_bin
            return max_def_bin.instantiate({x: op_01, y: op_02})

        # Otherwise still no simplification.
        return Equals(self, self).prove()
Exemple #2
0
 def left_mult_both_sides(self,
                          multiplier,
                          *,
                          simplify=True,
                          assumptions=USE_DEFAULTS):
     '''
     Multiply both sides of the relation by the 'multiplier'
     on the left.
     '''
     from proveit.numbers import greater_eq, zero
     from proveit.numbers.multiplication import (left_mult_pos_lesseq,
                                                 left_mult_neg_lesseq)
     if greater_eq(multiplier, zero).proven(assumptions):
         new_rel = left_mult_pos_lesseq.instantiate(
             {
                 a: multiplier,
                 x: self.lower,
                 y: self.upper
             },
             assumptions=assumptions)._simplify_both_sides(
                 simplify=simplify, assumptions=assumptions)
     elif LessEq(multiplier, zero).proven(assumptions):
         new_rel = left_mult_neg_lesseq.instantiate(
             {
                 a: multiplier,
                 x: self.lower,
                 y: self.upper
             },
             assumptions=assumptions)._simplify_both_sides(
                 simplify=simplify, assumptions=assumptions)
     else:
         raise Exception(
             "Cannot 'left_mult_both_sides' a LessEq relation without "
             "knowing the multiplier's relation with zero.")
     return new_rel.with_mimicked_style(self)
Exemple #3
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)
Exemple #4
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()
Exemple #5
0
 def left_mult_both_sides(self, multiplier, **defaults_config):
     '''
     Multiply both sides of the relation by the 'multiplier'
     on the left.
     '''
     from proveit.numbers import greater_eq, zero, deduce_number_set
     from proveit.numbers.multiplication import (
         weak_bound_via_right_factor_bound,
         reversed_weak_bound_via_right_factor_bound)
     was_reversed = False
     deduce_number_set(multiplier)
     if greater_eq(multiplier, zero).proven():
         new_rel = weak_bound_via_right_factor_bound.instantiate({
             a:
             multiplier,
             x:
             self.lower,
             y:
             self.upper
         })
     elif LessEq(multiplier, zero).proven():
         new_rel = reversed_weak_bound_via_right_factor_bound.instantiate({
             a:
             multiplier,
             x:
             self.lower,
             y:
             self.upper
         })
         was_reversed = True
     else:
         raise Exception(
             "Cannot left-multiply both sides of %s by %s "
             "without knowing the multiplier's relation with zero." %
             (self, multiplier))
     new_rel = new_rel.with_mimicked_style(self)
     if was_reversed:
         new_rel = new_rel.with_direction_reversed()
     return new_rel
Exemple #6
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
Exemple #7
0
def _generateCoordOrderAssumptions(coords):
    from proveit.numbers import LessEq, greater_eq
    for prev_coord, next_coord in zip(coords[:-1], coords[1:]):
        yield LessEq(prev_coord, next_coord)
        yield greater_eq(next_coord, prev_coord)
Exemple #8
0
 def conclude(self, **defaults_config):
     from proveit.numbers import zero, greater_eq
     if (InSet(self.element, Real).proven() and
             greater_eq(self.element, zero).proven()):
         return self.conclude_as_last_resort()
     return NumberMembership.conclude(self)
Exemple #9
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
Exemple #10
0
 def conclude(self, **defaults_config):
     if (InSet(self.element, Integer).proven()
             and greater_eq(self.element, zero).proven()):
         return self.conclude_as_last_resort()
     return NumberMembership.conclude(self)
Exemple #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()
Exemple #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