示例#1
0
    def shallow_simplification(self,
                               *,
                               must_evaluate=False,
                               **defaults_config):
        '''
        From this forall statement, evaluate it to TRUE or FALSE if
        possible by calling the domain's forall_evaluation method
        '''
        from proveit.logic import EvaluationError
        if must_evaluate and not self.has_domain():
            # Cannot automatically evaluate a forall statement with
            # no domain.
            raise EvaluationError(self)

        if hasattr(self, 'instance_param'):
            if hasattr(self.domain, 'forall_evaluation'):
                # Use the domain's forall_evaluation method
                return self.domain.forall_evaluation(self)
        '''
        # Let's not do this fancy stuff just yet.
        # Evaluate an unbundled version
        unbundle_eq = self.unbundle_equality()
        return unbundle_eq.rhs.evaluation()
        '''
        if must_evaluate:
            raise EvaluationError(self)

        return OperationOverInstances.shallow_simplification(self)
示例#2
0
 def forallEvaluation(self, forallStmt, assumptions):
     '''
     Given a forall statement over the BOOLEANS domain, evaluate to TRUE or FALSE
     if possible.
     '''        
     from proveit.logic import Forall, Equals, EvaluationError
     from ._theorems_ import falseEqFalse, trueEqTrue 
     from ._theorems_ import forallBoolEvalTrue, forallBoolEvalFalseViaTF, forallBoolEvalFalseViaFF, forallBoolEvalFalseViaFT
     from ._common_ import TRUE, FALSE, Booleans
     from conjunction import compose
     assert(isinstance(forallStmt, Forall)), "May only apply forallEvaluation method of BOOLEANS to a forall statement"
     assert(forallStmt.domain == Booleans), "May only apply forallEvaluation method of BOOLEANS to a forall statement with the BOOLEANS domain"
     assert(len(forallStmt.instanceVars) == 1), "May only apply forallEvaluation method of BOOLEANS to a forall statement with 1 instance variable"
     instanceVar = forallStmt.instanceVars[0]
     instanceExpr = forallStmt.instanceExpr
     P_op = Operation(P, instanceVar)
     trueInstance = instanceExpr.substituted({instanceVar:TRUE})
     falseInstance = instanceExpr.substituted({instanceVar:FALSE})
     if trueInstance == TRUE and falseInstance == FALSE:
         # special case of Forall_{A in BOOLEANS} A
         falseEqFalse # FALSE = FALSE
         trueEqTrue # TRUE = TRUE
         return forallBoolEvalFalseViaTF.specialize({P_op:instanceExpr}).deriveConclusion()
     else:
         # must evaluate for the TRUE and FALSE case separately
         evalTrueInstance = trueInstance.evaluation(assumptions)
         evalFalseInstance = falseInstance.evaluation(assumptions)
         if not isinstance(evalTrueInstance.expr, Equals) or not isinstance(evalFalseInstance.expr, Equals):
             raise EvaluationError('Quantified instances must produce equalities as evaluations')            
         # proper evaluations for both cases (TRUE and FALSE)
         trueCaseVal = evalTrueInstance.rhs
         falseCaseVal = evalFalseInstance.rhs
         if trueCaseVal == TRUE and falseCaseVal == TRUE:
             # both cases are TRUE, so the forall over booleans is TRUE
             compose([evalTrueInstance.deriveViaBooleanEquality(), evalFalseInstance.deriveViaBooleanEquality()], assumptions)
             forallBoolEvalTrue.specialize({P_op:instanceExpr, A:instanceVar})
             return forallBoolEvalTrue.specialize({P_op:instanceExpr, A:instanceVar}, assumptions=assumptions).deriveConclusion(assumptions)
         else:
             # one case is FALSE, so the forall over booleans is FALSE
             compose([evalTrueInstance, evalFalseInstance], assumptions)
             if trueCaseVal == FALSE and falseCaseVal == FALSE:
                 return forallBoolEvalFalseViaFF.specialize({P_op:instanceExpr, A:instanceVar}, assumptions=assumptions).deriveConclusion(assumptions)
             elif trueCaseVal == FALSE and falseCaseVal == TRUE:
                 return forallBoolEvalFalseViaFT.specialize({P_op:instanceExpr, A:instanceVar}, assumptions=assumptions).deriveConclusion(assumptions)
             elif trueCaseVal == TRUE and falseCaseVal == FALSE:
                 return forallBoolEvalFalseViaTF.specialize({P_op:instanceExpr, A:instanceVar}, assumptions=assumptions).deriveConclusion(assumptions)
             else:
                 raise EvaluationError('Quantified instance evaluations must be TRUE or FALSE')         
示例#3
0
    def evaluation(self, **defaults_config):
        '''
        If possible, return a Judgment of this expression equal to an
        irreducible value.  This Operation.evaluation version
        simplifies the operands and then calls shallow_simplification
        with must_evaluat=True.
        '''
        from proveit import UnsatisfiedPrerequisites, ProofFailure
        from proveit.logic import EvaluationError, SimplificationError

        # Try to simplify the operands first.
        reduction = self.simplification_of_operands(
            simplify_with_known_evaluations=True)

        # After making sure the operands have been simplified,
        # try 'shallow_simplification' with must_evaluate=True.
        try:
            if reduction.lhs == reduction.rhs:
                # _no_eval_check is a directive to the @equality_prover wrapper
                # to tell it not to check for an existing evaluation if we have
                # already checked.
                return self.shallow_simplification(must_evaluate=True,
                                                   _no_eval_check=True)
            evaluation = reduction.rhs.shallow_simplification(
                must_evaluate=True)
        except (SimplificationError, UnsatisfiedPrerequisites,
                NotImplementedError, ProofFailure):
            raise EvaluationError(self)
        return reduction.apply_transitivity(evaluation)
示例#4
0
 def shallow_simplification(self,
                            *,
                            must_evaluate=False,
                            **defaults_config):
     '''
     For a generic Function expression (e.g., "f(x)"), there is
     no evaluation strategy.
     '''
     from proveit.logic import EvaluationError
     if must_evaluate:
         raise EvaluationError(self)
     return Operation.shallow_simplification(self)
示例#5
0
 def doReducedEvaluation(self, assumptions=USE_DEFAULTS, **kwargs):
     '''
     Only handles -0 = 0 or double negation.
     '''
     from proveit.logic import EvaluationError
     from ._theorems_ import negatedZero
     from proveit.number import zero
     if self.operand == zero:
         return negatedZero
     if isinstance(self.operand, Neg) and isIrreducibleValue(
             self.operand.operand):
         return self.doubleNegSimplification(assumptions)
     raise EvaluationError(self, assumptions)
示例#6
0
 def shallow_simplification(self, *, must_evaluate=False,
                            **defaults_config):
     '''
     Proves that this ExprTuple is equal to an ExprTuple
     with ExprRanges reduced unless these are "preserved"
     expressions.
     '''
     from proveit.relation import TransRelUpdater
     from proveit import ExprRange
     from proveit.logic import is_irreducible_value, EvaluationError
     expr = self
     eq = TransRelUpdater(expr)
     if defaults.preserve_all:
         # Preserve all sub-expressions -- don't simplify.
         return eq.relation
     _k = 0
     for entry in self.entries:
         if isinstance(entry, ExprRange):
             if entry in defaults.preserved_exprs:
                 if must_evaluate:
                     # An ExprRange is not irreducible.
                     raise EvaluationError(self)
                 # Preserve this entry -- don't simplify it.
                 _k += ExprTuple(entry).num_entries()
                 continue
             entry_simp = entry.reduction()
             if must_evaluate and not is_irreducible_value(entry_simp.rhs):
                 raise EvaluationError(self)
             if entry_simp.lhs != entry_simp.rhs:
                 substitution = expr.substitution(
                         entry_simp, start_idx=_k, preserve_all=True)
                 expr = eq.update(substitution)
             _k += entry_simp.rhs.num_entries()
         else:
             if must_evaluate and not is_irreducible_value(entry):
                 raise EvaluationError(self)
             _k += 1
     return eq.relation
示例#7
0
 def do_reduced_evaluation(self, assumptions=USE_DEFAULTS, **kwargs):
     '''
     Only handles -0 = 0 or double negation.
     '''
     from proveit.logic import EvaluationError
     from . import negated_zero
     from proveit.numbers import zero
     if self.operand == zero:
         return negated_zero
     if isinstance(
             self.operand,
             Neg) and is_irreducible_value(
             self.operand.operand):
         return self.double_neg_simplification(assumptions)
     raise EvaluationError(self, assumptions)
示例#8
0
 def shallow_simplification(self, *, must_evaluate=False,
                            **defaults_config):
     '''
     If the operands that to TRUE or FALSE, we can 
     evaluate this expression as TRUE or FALSE.
     '''
     # IMPORTANT: load in truth-table evaluations
     from . import implies_t_t, implies_t_f, implies_f_t, implies_f_f  
     try:
         return Operation.shallow_simplification(
                 self, must_evaluate=must_evaluate)
     except NotImplementedError:
         # Should have been able to do the evaluation from the
         # loaded truth table.
         # If it can't we are unable to evaluate it.
         from proveit.logic import EvaluationError
         raise EvaluationError(self)
示例#9
0
 def do_reduced_evaluation(self, assumptions=USE_DEFAULTS):
     '''
     For trivial cases, a zero or one exponent or zero or one base,
     derive and return this exponential expression equated with a
     evaluated form. Assumptions may be necessary to deduce
     necessary conditions for the simplification.
     '''
     from proveit.logic import EvaluationError
     from proveit.numbers import zero, one
     from . import exp_zero_eq_one, exponentiated_zero, exponentiated_one
     if self.exponent == zero:
         return exp_zero_eq_one.instantiate({a: self.base})  # =1
     elif self.base == zero:
         return exponentiated_zero.instantiate({x: self.exponent})  # =0
     elif self.base == one:
         return exponentiated_one.instantiate({x: self.exponent})  # =1
     else:
         raise EvaluationError(
             'Only trivial evaluation is implemented '
             '(zero or one for the base or exponent).', assumptions)
示例#10
0
    def shallow_simplification(self,
                               *,
                               must_evaluate=False,
                               **defaults_config):
        from proveit.logic import TRUE, FALSE, EvaluationError, evaluate_truth
        from proveit.logic.booleans.negation import (negation_intro,
                                                     falsified_negation_intro)
        from . import not_t, not_f  # load in truth-table evaluations
        if self.operand == TRUE:
            return not_t
        elif self.operand == FALSE:
            return not_f
        elif self.operand.proven():
            # evaluate to FALSE via falsified_negation_intro
            return falsified_negation_intro.instantiate({A: self.operand})
        elif self.operand.disproven():
            # evaluate to TRUE via falsified_negation_intro
            return evaluate_truth(negation_intro.instantiate({A:
                                                              self.operand}))
        elif must_evaluate:
            # The simplification of the operands may not have
            # worked hard enough.  Let's work harder if we must
            # evaluate.
            evaluated = self.operand.evaluation().rhs
            if evaluated in (TRUE, FALSE):
                # All the pieces should be there now.
                return self.evaluation(automation=True)
            raise EvaluationError(self)

        if hasattr(self.operand, 'shallow_simplification_of_negation'):
            # If the operand has a 'shallow_simplification_of_negation',
            # use that.
            return self.operand.shallow_simplification_of_negation()

        # May now be able to evaluate via loaded truth tables.
        return Operation.shallow_simplification(self)
示例#11
0
        def wrapper(*args, **kwargs):
            '''
            The wrapper for the equality_prover decorator.
            '''
            from proveit._core_.expression.expr import Expression
            from proveit.logic import Equals, TRUE, EvaluationError
            # Obtain the original Expression to be on the left side
            # of the resulting equality Judgment.
            _self = args[0]
            if isinstance(_self, Expression):
                expr = _self
            elif hasattr(_self, 'expr'):
                expr = _self.expr
            else:
                raise TypeError("@equality_prover, %s, expected to be a "
                                "method for an Expression type or it must "
                                "have an 'expr' attribute." % func)
            proven_truth = None
            if is_simplification_method or is_evaluation_method:
                from proveit.logic import is_irreducible_value
                if is_irreducible_value(expr):
                    # Already irreducible.  Done.
                    proven_truth = (Equals(expr,
                                           expr).conclude_via_reflexivity())

            # If _no_eval_check is set to True, don't bother
            # checking for an existing evaluation.  Used internally
            # in Operation.simplification, Operation.evaluation,
            # Conditional.simplification, and Judgment.simplify.
            _no_eval_check = kwargs.pop('_no_eval_check', False)
            if (not _no_eval_check
                    and (is_evaluation_method or
                         (defaults.simplify_with_known_evaluations
                          and is_simplification_method))):
                from proveit.logic import evaluate_truth
                if expr.proven():
                    # The expression is proven so it equals true.
                    proven_truth = Equals(expr,
                                          TRUE).conclude_boolean_equality()
                else:
                    # See if there is a known evaluation (or if one may
                    # be derived via known equalities if
                    # defaults.automation is enabled).
                    if 'assumptions' in kwargs:
                        # Use new assumptions temporarily.
                        with defaults.temporary() as tmp_defaults:
                            tmp_defaults.assumptions = kwargs.get(
                                'assumptions')
                            if expr.proven():
                                # expr is proven, so it evaluates
                                # to TRUE.
                                proven_truth = evaluate_truth(expr)
                            else:
                                proven_truth = Equals.get_known_evaluation(
                                    expr)
                    else:
                        proven_truth = Equals.get_known_evaluation(expr)
                # For an 'evaluation' or 'simplification', we should
                # force auto_simplify on and preserve_all off to
                # simplify as much as possible.
                kwargs['auto_simplify'] = True
                kwargs['preserve_all'] = False
            if proven_truth is None:
                proven_truth = decorated_relation_prover(*args, **kwargs)
            proven_expr = proven_truth.expr
            if not isinstance(proven_expr, Equals):
                raise TypeError("@equality_prover, %s, expected to prove an "
                                "Equals expression, not %s of type %s." %
                                (func, proven_expr, proven_expr.__class__))
            if is_evaluation_method or (is_shallow_simplification_method
                                        and kwargs.get('must_evaluate',
                                                       False) == True):
                # The right side of an evaluation must be irreducible.
                from proveit.logic import is_irreducible_value
                if not is_irreducible_value(proven_expr.rhs):
                    raise EvaluationError(_self)
            return proven_truth
示例#12
0
    def doReducedEvaluation(self, assumptions=USE_DEFAULTS, **kwargs):
        '''
        Derive and return this multiplication expression equated with an irreducible value.
        Handle the trivial case of a zero factor or do pairwise evaluation
        after simplifying negations and eliminating one factors.
        '''
        from ._theorems_ import multZeroLeft, multZeroRight, multZeroAny
        from proveit.logic import isIrreducibleValue, EvaluationError
        from proveit.number import zero

        # First check for any zero factors -- quickest way to do an evaluation.
        try:
            zeroIdx = self.operands.index(zero)
            if len(self.operands) == 2:
                if zeroIdx == 0:
                    return multZeroLeft.specialize({x: self.operands[1]},
                                                   assumptions=assumptions)
                else:
                    return multZeroRight.specialize({x: self.operands[0]},
                                                    assumptions=assumptions)
            _a = self.operands[:zeroIdx]
            _b = self.operands[zeroIdx + 1:]
            _i = ExprTuple(*_a.length(assumptions))
            _j = ExprTuple(*_b.length(assumptions))
            return multZeroAny.specialize({
                i: _i,
                j: _j,
                a: _a,
                b: _b
            },
                                          assumptions=assumptions)
        except (ValueError, ProofFailure):
            pass  # No such "luck" regarding a simple multiplication by zero.

        expr = self

        # A convenience to allow successive update to the equation via transitivities.
        # (starting with self=self).
        eq = TransRelUpdater(self, assumptions)

        # Simplify negations -- factor them out.
        expr = eq.update(expr.negSimplifications(assumptions))

        if not isinstance(expr, Mult):
            # The expression may have changed to a negation after doing
            # negSimplification.  Start the simplification of this new
            # expression fresh at this point.
            eq.update(expr.evaluation(assumptions))
            return eq.relation

        # Eliminate any factors of one.
        expr = eq.update(expr.oneEliminations(assumptions))

        if isIrreducibleValue(expr):
            return eq.relation  # done

        if len(self.operands) > 2:
            eq.update(pairwiseEvaluation(expr, assumptions))
            return eq.relation

        raise EvaluationError(self, assumptions)
示例#13
0
    def shallow_simplification(self,
                               *,
                               must_evaluate=False,
                               **defaults_config):
        '''
        Attempt to determine whether this disjunction, with
        simplified operands, evaluates to TRUE or FALSE under the given
        assumptions.  If all operands have simplified to FALSE,
        the disjunction is FALSE.  If any of the operands have
        simplified to TRUE, the disjunction may be TRUE (if the
        other operands are provably Boolean).
        If it can't be evaluated, and must_evaluate is False,
        ungroup nested disjunctions if that is an active
        simplification direction.  Also, if applicable, perform
        a unary reduction: Or(A) = A.
        '''
        from proveit.logic import (Equals, FALSE, TRUE, EvaluationError,
                                   is_irreducible_value)
        # load in truth-table evaluations
        from . import or_t_t, or_t_f, or_f_t, or_f_f
        if self.operands.num_entries() == 0:
            from proveit.logic.booleans.disjunction import \
                empty_disjunction_eval
            # Or() = FALSE
            return empty_disjunction_eval

        # Check whether or not all of the operands are FALSE
        # or any are TRUE.
        all_are_false = True
        for operand in self.operands:
            if operand != FALSE:
                all_are_false = False
            if operand == TRUE:
                # If any simplified operand is TRUE, the disjunction
                # may only evaluate to TRUE if it can be evaluated.
                # Only use automation here if 'must_evaluate' is True.
                self.conclude(automation=must_evaluate)
                return Equals(self, TRUE).prove()

        # If all of the operands are FALSE, we can prove that the
        # conjunction is equal to FALSE.
        if all_are_false:
            self.conclude_negation()
            return Equals(self, FALSE).prove()

        if must_evaluate:
            if not all(
                    is_irreducible_value(operand)
                    for operand in self.operands):
                # The simplification of the operands may not have
                # worked hard enough.  Let's work harder if we
                # must evaluate.
                for operand in self.operands:
                    if not is_irreducible_value(operand):
                        operand.evaluation()
                return self.evaluation()
            # Can't evaluate the conjunction if no operand was
            # FALSE but they aren't all TRUE.
            raise EvaluationError(self)

        if self.operands.is_single():
            # Or(A) = A
            return self.unary_reduction()

        expr = self
        # for convenience updating our equation
        eq = TransRelUpdater(expr)

        if Or._simplification_directives_.ungroup:
            # ungroup the expression (disassociate nested disjunctions).
            _n = 0
            length = expr.operands.num_entries() - 1
            # loop through all operands
            while _n < length:
                operand = expr.operands[_n]
                if isinstance(operand, Or):
                    # if it is grouped, ungroup it
                    expr = eq.update(
                        expr.disassociation(_n, auto_simplify=False))
                length = expr.operands.num_entries()
                _n += 1

        return Expression.shallow_simplification(self)
示例#14
0
    def shallow_simplification(self,
                               *,
                               must_evaluate=False,
                               **defaults_config):
        '''
        Attempt to determine whether this conjunction, with
        simplified operands, evaluates to TRUE or FALSE under the given
        assumptions.  If all operands have simplified to TRUE,
        the conjunction is TRUE.  If any of the operands have
        simplified to FALSE, the conjunction may be FALSE (if the
        other operands are provably Boolean).
        If it can't be evaluated, and must_evaluate is False,
        ungroup nested conjunctions if that is an active
        simplification direction.  Also, if applicable, perform
        a unary reduction: And(A) = A.
        '''
        from proveit import ExprRange
        from proveit.logic import (Equals, FALSE, TRUE, EvaluationError,
                                   is_irreducible_value)
        # load in truth-table evaluations
        from . import (and_t_t, and_t_f, and_f_t, and_f_f,
                       conjunction_eq_quantification)

        if self.operands.num_entries() == 0:
            from proveit.logic.booleans.conjunction import \
                empty_conjunction_eval
            # And() = TRUE
            return empty_conjunction_eval

        # Check whether or not all of the operands are TRUE
        # or any are FALSE.
        all_are_true = True
        for operand in self.operands:
            if operand != TRUE:
                all_are_true = False
            if operand == FALSE:
                # If any simplified operand is FALSE, the conjunction
                # may only evaluate to FALSE if it can be evaluated.
                # Only use automation here if 'must_evaluate' is True.
                self.conclude_negation(automation=must_evaluate)
                return Equals(self, FALSE).prove()

        # If all of the operands are TRUE, we can prove that the
        # conjunction is equal to TRUE.
        if all_are_true:
            self.conclude()
            return Equals(self, TRUE).prove()

        if must_evaluate:
            if self.operands.contains_range():
                if self.operands.num_entries() == 1:
                    # Conjunction of a single ExprRange.  Convert
                    # to a universal quantification and evaluate that.
                    expr_range = self.operands[0]
                    _i = expr_range.true_start_index
                    _j = expr_range.true_end_index
                    _P = expr_range.lambda_map
                    conj_eq_quant = (conjunction_eq_quantification.instantiate(
                        {
                            i: _i,
                            j: _j,
                            P: _P
                        }, preserve_all=True))
                    return conj_eq_quant.apply_transitivity(
                        conj_eq_quant.rhs.evaluation())
                return prove_via_grouping_ranges(
                    self, lambda expr, **kwargs: expr.evaluation(**kwargs))
            if not all(
                    is_irreducible_value(operand)
                    for operand in self.operands):
                # The simplification of the operands may not have
                # worked hard enough.  Let's work harder if we
                # must evaluate.
                for operand in self.operands:
                    if not is_irreducible_value(operand):
                        operand.evaluation()
                return self.evaluation()
            # Can't evaluate the conjunction if no operand was
            # FALSE but they aren't all TRUE.
            raise EvaluationError(self)

        if self.operands.is_single():
            # And(A) = A
            return self.unary_reduction()

        expr = self
        # for convenience updating our equation
        eq = TransRelUpdater(expr)

        if And._simplification_directives_.ungroup:
            # ungroup the expression (disassociate nested conjunctions).
            _n = 0
            length = expr.operands.num_entries() - 1
            # loop through all operands
            while _n < length:
                operand = expr.operands[_n]
                if isinstance(operand, And):
                    # if it is grouped, ungroup it
                    expr = eq.update(
                        expr.disassociation(_n, auto_simplify=False))
                length = expr.operands.num_entries()
                _n += 1

        return Expression.shallow_simplification(self)
示例#15
0
    def range_expansion(self, **defaults_config):
        '''
        For self an ExprTuple with a single entry that is an ExprRange
        of the form f(i),...,f(j), where 0 <= (j-i) <= 9 (i.e. the
        ExprRange represents 1 to 10 elements), derive and
        return an equality between self and an ExprTuple with explicit
        entries replacing the ExprRange. For example, if
            self = ExprTuple(f(3),...,f(6)),
        then self.range_expansion() would return:
        |- ExprTuple(f(3),...,f(6)) = ExprTuple(f(3), f(4), f(5), f(6))
        '''

        # Check that we have a an ExprTuple with
        # (1) a single entry
        # (2) and the single entry is an ExprRange
        # (these restrictions can be relaxed later to make the
        # method more general)

        # ExprTuple with single entry
        if not self.num_entries() == 1:
            raise ValueError(
                    "ExprTuple.range_expansion() implemented only for "
                    "ExprTuples with a single entry (and the single "
                    "entry must be an ExprRange). Instead, the ExprTuple "
                    "{0} has {1} entries.".format(self, self.num_entries))

        # and the single entry is an ExprRange:
        from proveit import ExprRange
        if not isinstance(self.entries[0], ExprRange):
            raise ValueError(
                    "ExprTuple.range_expansion() implemented only for "
                    "ExprTuples with a single entry (and the single "
                    "entry must be an ExprRange). Instead, the ExprTuple "
                    "is {0}.".format(self))

        from proveit import Function
        from proveit.logic import EvaluationError
        from proveit.numbers import subtract

        _the_expr_range = self[0]

        # _n = self.num_elements()
        try:
            _n = subtract(self[0].true_end_index, self[0].true_start_index).evaluated()
        except EvaluationError as the_error:
            _diff = subtract(self[0].true_end_index, self[0].true_start_index)
            print("EvaluationError: {0}. The ExprRange {1} must represent "
                  "a known, finite number of elements, but all we know is "
                  "that it represents {2} elements.".format(
                    the_error, self[0], _diff))
            raise EvaluationError(
                subtract(self[0].true_end_index, self[0].true_start_index))
        
        _n = _n.as_int() + 1 # actual number of elems being represented
        if not (1 <= _n and _n <= 9):
            raise ValueError(
                    "ExprTuple.range_expansion() implemented only for "
                    "ExprTuples with a single entry, with the single "
                    "entry being an ExprRange representing a finite "
                    "number of elements n with 1 <= n <= 9. Instead, "
                    "the ExprTuple is {0} with number of elements equal "
                    "to {1}.".format(self[0], _n))

        # id the correct theorem for the number of entries
        import proveit.numbers.numerals.decimals
        expansion_thm = proveit.numbers.numerals.decimals\
                        .__getattr__('range_%d_expansion' % _n)

        # instantiate and return the identified expansion theorem
        _f, _i, _j = expansion_thm.instance_vars
        _safe_var = self.safe_dummy_var()
        _idx_param = _the_expr_range.parameter
        _fxn_sub = _the_expr_range.body.basic_replaced(
                {_idx_param: _safe_var})
        _i_sub = _the_expr_range.true_start_index
        _j_sub = _the_expr_range.true_end_index
        return expansion_thm.instantiate(
                {Function(_f, _safe_var): _fxn_sub, _i: _i_sub, _j: _j_sub})