Beispiel #1
0
    def conversionToAddition(self, assumptions=USE_DEFAULTS):
        '''
        From multiplication by an integer as the first factor,
        derive and return the equivalence of this multiplication
        to a repeated addition; for example, 3*c = c + c + c.
        '''
        from ._axioms_ import multDef
        if hasattr(self.operands[0], 'asInt'):
            reps = self.operands[0].asInt()
        else:
            raise ValueError(
                "Cannot 'expandAsAddition' unless the first operand is a literal integer: %s"
                % str(self))

        expr = self
        eq = TransRelUpdater(
            self, assumptions)  # for convenience updating our equation
        # Group together the remaining factors if necessary:
        if len(self.operands) > 2:
            expr = eq.update(
                expr.association(1,
                                 len(self.operands) - 1, assumptions))
        _x = self.operands[1]
        _n = num(reps)
        eq.update(
            multDef.specialize({
                n: _n,
                a: [_x] * reps,
                x: _x
            },
                               assumptions=assumptions))
        return eq.relation
Beispiel #2
0
def groupCommutation(expr,
                     initIdx,
                     finalIdx,
                     length,
                     disassociate=True,
                     assumptions=USE_DEFAULTS):
    '''
    Derive a commutation equivalence on a group of multiple operands by associating them
    together first.  If 'dissassociate' is true, the group will be disassociated at end.
    '''
    from proveit import TransRelUpdater

    if initIdx < 0:
        initIdx = len(expr.operands) + initIdx  # use wrap-around indexing
    if finalIdx < 0:
        finalIdx = len(expr.operands) + finalIdx  # use wrap-around indexing
    if length == 1:
        return expr.commutation(initIdx, finalIdx, assumptions=assumptions)

    eq = TransRelUpdater(
        expr, assumptions)  # for convenience while updating our equation
    expr = eq.update(expr.association(initIdx, length,
                                      assumptions=assumptions))
    expr = eq.update(
        expr.commutation(initIdx, finalIdx, assumptions=assumptions))
    if disassociate:
        expr = eq.update(expr.disassociation(finalIdx,
                                             assumptions=assumptions))
    return eq.relation
Beispiel #3
0
    def negSimplifications(self, assumptions=USE_DEFAULTS):
        '''
        Equivalence method that derives a simplification in which
        negated factors are factored out.  For example:
            (-w)*(-x)*y*(-z) = -(w*x*y*z)
        '''
        from proveit.number import Neg

        expr = self

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

        # Work in reverse order so indices don't need to be updated.
        for rev_idx, operand in enumerate(reversed(self.operands)):
            if isinstance(operand, Neg):
                idx = len(self.operands) - rev_idx - 1
                if isinstance(expr, Mult):
                    expr = eq.update(expr.negSimplification(idx, assumptions))
                elif isinstance(expr, Neg):
                    expr = eq.update(
                        expr.innerNegMultSimplification(idx, assumptions))

        return eq.relation
Beispiel #4
0
def pairwise_evaluation(expr, **defaults_config):
    '''
    Evaluation routine applicable to associative operations in which
    operands at the beginning are paired and evaluated sequentially.
    '''
    from proveit import TransRelUpdater
    from proveit.logic import is_irreducible_value
    # successively evaluate and replace the operation performed on
    # the first two operands

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

    if is_irreducible_value(expr):
        # The expression is already irreducible, so we are done.
        return eq.relation

    if expr.operands.num_entries() == 2:
        raise ValueError("pairwise_evaluation may only be used when there "
                         "are more than 2 operands.")

    # While there are more than 2 operands, associate the first 2
    # and auto-simplify.
    while expr.operands.num_entries() > 2:
        expr = eq.update(expr.association(0, length=2, auto_simplify=True))
        if is_irreducible_value(expr):
            # The new expression is irreducible, so we are done.
            return eq.relation
        elif not is_irreducible_value(expr.operands[0]):
            # Auto-simplication failed to convert the first operand
            # to an irreducible value, so break out of this loop
            # and generate an appropriate error by trying to
            # evaluate it directly.
            expr = eq.update(expr.inner_expr().operands[0].evaluate())
    return eq.relation
Beispiel #5
0
    def shallow_simplification(self, *, must_evaluate=False,
                               **defaults_config):
        '''
        Returns a proven simplification equation for this TensorProd
        expression assuming the operands have been simplified.
        
        Currently deals only with:
        (1) simplifying a TensorProd(x) (i.e. a TensorProd with a
            single operand x) to x itself. For example,
            TensorProd(x) = x.
        (2) Ungrouping nested tensor products.
        (3) Factoring out scalars.
        '''

        if self.operands.is_single():
            from . import unary_tensor_prod_def
            _V = VecSpaces.known_vec_space(self.operand)
            _K = VecSpaces.known_field(_V)
            return unary_tensor_prod_def.instantiate(
                {K:_K, V:_V, A:self.operands[0]}, preserve_all=True)

        # for convenience updating our equation:
        expr = self
        eq = TransRelUpdater(expr)
        
        if TensorProd._simplification_directives_.ungroup:
            # ungroup the expression (disassociate nested additions).
            _n = 0
            length = expr.operands.num_entries() - 1
            # loop through all operands
            while _n < length:
                operand = expr.operands[_n]
                if isinstance(operand, TensorProd):
                    # if it is grouped, ungroup it
                    expr = eq.update(expr.disassociation(
                            _n, preserve_all=True))
                length = expr.operands.num_entries()
                _n += 1
        
        if TensorProd._simplification_directives_.factor_scalars:
            # Next, pull out scalar factors
            try:
                VecSpaces.known_vec_space(self)
            except ValueError:
                raise UnsatisfiedPrerequisites(
                        "No known vector space for %s"%self)
            for _k, operand in enumerate(expr.operands):
                if isinstance(operand, ScalarMult):
                    # Just pull out the first one we see and let
                    # recursive simplifications take care of any more.
                    # To make sure this happens we turn auto_simplify
                    # on, and those simpiflications should all be fair
                    # game as part of shallow_simplification.
                    expr = eq.update(expr.scalar_factorization(
                        _k, auto_simplify=True))
                    break
        
        # Future processing possible here.
        return eq.relation
Beispiel #6
0
    def cancelations(self, **defaults_config):
        '''
        Deduce and return an equality between self and a form in which
        all simple division cancellations are performed.
        '''
        from proveit.numbers import Mult
        expr = self

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

        numer_factors = (self.numerator.operands if isinstance(
            self.numerator, Mult) else [self.numerator])
        denom_factors = (self.denominator.operands if isinstance(
            self.denominator, Mult) else [self.denominator])
        denom_factors_set = set(denom_factors)

        for numer_factor in numer_factors:
            if numer_factor in denom_factors_set:
                expr = eq.update(
                    expr.cancelation(numer_factor, preserve_all=True))
                denom_factors_set.remove(numer_factor)

        return eq.relation
Beispiel #7
0
 def factorization(self,
                   theFactor,
                   pull="left",
                   groupFactor=True,
                   groupRemainder=False,
                   assumptions=USE_DEFAULTS):
     '''
     Factor out "theFactor" from this product, pulling it either to the "left" or "right".
     If "theFactor" is a product, this may factor out a subset of the operands as
     long as they are next to each other (use commute to make this happen).  If
     there are multiple occurrences, the first occurrence is used.  If groupFactor is
     True and theFactor is a product, these operands are grouped together as a sub-product.
     If groupRemainder is True and there are multiple remaining operands (those not in
     "theFactor"), then these remaining operands are grouped together as a sub-product.
     Returns the equality that equates self to this new version.
     Give any assumptions necessary to prove that the operands are in Complexes so that
     the associative and commutation theorems are applicable.
     '''
     expr = self
     eq = TransRelUpdater(expr, assumptions)
     idx, num = self.index(theFactor, alsoReturnNum=True)
     expr = eq.update(
         self.groupCommutation(idx,
                               0 if pull == 'left' else -num,
                               length=num,
                               assumptions=assumptions))
     if groupFactor and num > 1:
         if pull == 'left':  # use 0:num type of convention like standard pythong
             expr = eq.update(
                 expr.association(0, num, assumptions=assumptions))
         elif pull == 'right':
             expr = eq.update(
                 expr.association(-num, num, assumptions=assumptions))
     if groupRemainder and len(self.operands) - num > 1:
         # if the factor has been group, effectively there is just 1 factor operand now
         num_factor_operands = 1 if groupFactor else num
         if pull == 'left':
             expr = eq.update(
                 expr.association(num_factor_operands,
                                  len(self.operands) - num_factor_operands,
                                  assumptions=assumptions))
         elif pull == 'right':
             expr = eq.update(
                 expr.association(0,
                                  num_factor_operands,
                                  assumptions=assumptions))
     return eq.relation
Beispiel #8
0
def group_commutation(expr,
                      init_idx,
                      final_idx,
                      length,
                      disassociate=True,
                      assumptions=USE_DEFAULTS):
    '''
    Derive a commutation equivalence on a group of multiple operands by
    associating them together first.  If 'dissassociate' is true, the
    group will be disassociated at end. For example, the following call:
    Or(A,B,C,D).group_commutation(0, 1, length=2,
                                 assumptions=in_bool(A,B,C,D))
    essentially goes through the following steps:
    (1) associates 2 elements (i.e. length = 2) starting at index 0 to
        obtain (A V B) V C V D
    (2) removes the element to be commuted to obtain C V D
    (2) inserts the element to be commuted at the desire index to
        obtain C V (A V B) V D
    (3) then disassociates to obtain C V A V B V D
    (4) eventually producing the output:
    {A in Bool, ..., D in Bool} |- (A V B V C V D) = (C V A V B V D)
    '''
    from proveit import TransRelUpdater

    # use the following to allow/acknowledge wrap-around indexing
    if init_idx < 0:
        init_idx = expr.operands.num_entries() + init_idx  # wrap
    if final_idx < 0:
        final_idx = expr.operands.num_entries() + final_idx  # wrap
    if length == 1:
        return expr.commutation(init_idx, final_idx, assumptions=assumptions)

    # for convenience while updating our equation:
    eq = TransRelUpdater(expr, assumptions)
    expr = eq.update(
        expr.association(init_idx, length, assumptions=assumptions))
    expr = eq.update(
        expr.commutation(init_idx, final_idx, assumptions=assumptions))
    if disassociate:
        expr = eq.update(
            expr.disassociation(final_idx, assumptions=assumptions))
    return eq.relation
Beispiel #9
0
    def deep_one_eliminations(self, **defaults_config):
        '''
        Eliminate ones from the numerator, the denominator,
        and as a division by one.
        '''
        from proveit.numbers import one
        expr = self

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

        for _i, operand in enumerate(self.operands):
            if hasattr(operand, 'deep_one_eliminations'):
                expr = eq.update(
                    expr.inner_expr().operands[_i].deep_one_eliminations())

        if expr.denominator == one:
            expr = eq.update(expr.divide_by_one_elimination())
        return eq.relation
Beispiel #10
0
    def elem_substitution(self,
                          elem=None,
                          sub_elem=None,
                          assumptions=USE_DEFAULTS):
        '''
        Deduce that this enum Set expression is equal to the Set
        obtained when every instance of elem in self is replaced by the
        sub_elem. The deduction depends on the sub_elem being equal to
        the elem it is replacing.
        Examples: Let S = Set(a, b, a, b, a, c). Then
        S.elem_substitution() gives ERROR;
        S.elem_substitution(elem=d, sub_elem=two,
                            assumptions=[Equals(d, two)] gives ERROR;
        S.elem_substitution(elem=b, sub_elem=four,
                            assumptions=[Equals(b, four)])
            gives |- S = {a, 4, a, 4, a, c};
        S.single_elem_substitution(elem=a, sub_elem=two,
                                   assumptions=[Equals(a, two)])
            gives |- S = {2, b, 2, b, 2, c};
        S.single_elem_substitution(elem=c, sub_elem=d,
                                   assumptions=[Equals(c, d)])
            gives |- S = {a, b, a, b, a, d};
        '''
        # First, a quick check on elem and sub_elem arguments
        if elem == None or sub_elem == None:
            raise ValueError("Set.elem_substitution() method requires "
                             "the specification of the element to be replaced "
                             "and the requested substitution value, but "
                             "found elem={0} and sub_elem={1}.".format(
                                 elem, sub_elem))

        if elem not in self.operands:
            raise ValueError("In calling the Set.elem_substitution() method, "
                             "the element '{0}' to be replaced does not "
                             "appear to exist in the original set {1}.".format(
                                 elem, self))

        # count the number of elems to replace with the sub_elem
        self_list = list(self.operands)
        num_elems_to_replace = self_list.count(elem)

        from proveit import TransRelUpdater
        eq = TransRelUpdater(self, assumptions)

        expr = self
        while num_elems_to_replace > 0:
            expr = eq.update(
                expr.single_elem_substitution(elem=elem,
                                              sub_elem=sub_elem,
                                              assumptions=assumptions))
            num_elems_to_replace -= 1

        return eq.relation
Beispiel #11
0
def pairwise_evaluation(expr, assumptions):
    '''
    Evaluation routine applicable to associative operations in which
    operands at the beginning are paired and evaluated sequentially.
    '''
    from proveit import TransRelUpdater
    # successively evaluate and replace the operation performed on
    # the first two operands

    # for convenience while updating our equation:
    eq = TransRelUpdater(expr, assumptions)

    if expr.operands.num_entries() == 2:
        raise ValueError("pairwise_evaluation may only be used when there "
                         "are more than 2 operands.")
    while expr.operands.num_entries() > 2:
        expr = eq.update(expr.association(0, length=2,
                                          assumptions=assumptions))
        expr = eq.update(expr.inner_expr().operands[0].evaluation(assumptions))
    eq.update(expr.evaluation(assumptions=assumptions))
    return eq.relation
Beispiel #12
0
    def vec_sum_elimination(self, field=None, **defaults_config):
        '''
        For a VecSum in which the summand does not depend on the 
        summation index, return an equality between this VecSum and
        the equivalent expression in which the VecSum is eliminated.
        For example, suppose self = VecSum(i, v, Interval(2, 4)).
        Then self.vec_sum_elimination() would return
        |- self = 3*v
        where the 3*v is actually ScalarMult(3, v).
        The method works only for a VecSum over a single summation
        index, and simply returns self = self if the VecSum elimination
        is not possible due to the summand being dependent on the
        index of summation.
        '''

        expr = self
        summation_index = expr.index
        eq = TransRelUpdater(expr)

        if summation_index not in free_vars(expr.summand):
            vec_space_membership = expr.summand.deduce_in_vec_space(
                field=field,
                assumptions = defaults.assumptions + expr.conditions.entries)
            _V_sub = vec_space_membership.domain
            _K_sub = VecSpaces.known_field(_V_sub)
            _j_sub = expr.condition.domain.lower_bound
            _k_sub = expr.condition.domain.upper_bound
            _v_sub = expr.summand
            from proveit.linear_algebra.addition import vec_sum_of_constant_vec
            eq.update(vec_sum_of_constant_vec.instantiate(
                    {V: _V_sub, K: _K_sub, j: _j_sub, k: _k_sub, v: _v_sub}))

        else:
            print("VecSum cannot be eliminated. The summand {0} appears "
                  "to depend on the index of summation {1}".
                  format(expr.summand, summation_index))

        return eq.relation
Beispiel #13
0
    def doReducedSimplification(self, assumptions=USE_DEFAULTS, **kwargs):
        '''
        Derive and return this multiplication expression equated with a simpler form.
        Deals with disassociating any nested multiplications,simplifying negations, and
        factors of one, in that order.
        doReducedEvaluation deals with factors of 0.
        '''

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

        # Ungroup the expression (disassociate nested multiplications).
        idx = 0
        length = len(expr.operands) - 1
        while idx < length:
            # loop through all operands
            if isinstance(expr.operands[idx], Mult):
                # if it is grouped, ungroup it
                expr = eq.update(expr.disassociation(idx, assumptions))
            else:
                idx += 1
            length = len(expr.operands)

        # 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.simplification(assumptions))
            return eq.relation

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

        return eq.relation
Beispiel #14
0
    def do_reduced_simplification(self, assumptions=USE_DEFAULTS, **kwargs):
        '''
        Perform simplifications of a Divide expression after the
        operands have individually been simplified.
        Cancels common factors...
        '''
        from proveit.numbers import one
        expr = self
        # for convenience updating our equation
        eq = TransRelUpdater(expr, assumptions)

        # perform cancelations where possible
        expr = eq.update(expr.cancelations(assumptions))
        if not isinstance(expr, Div):
            # complete cancelation.
            return eq.relation

        if self.denominator == one:
            # eliminate division by one
            eq.update(expr.eliminate_divide_by_one(assumptions))
            return eq.relation  # no more division simplifications.

        return eq.relation
Beispiel #15
0
    def shallow_simplification(self,
                               *,
                               must_evaluate=False,
                               **defaults_config):
        '''
        Returns a proven simplification equation for this ScalarMult
        expression assuming the operands have been simplified.
        Handles:
        (1) Doubly-nested scalar multiplication -- for example,
            taking ScalarMult(a, ScalarMult(b, v))
            to ScalarMult(ScalarMult(a, b), v)
        (2) ScalarMult identity -- for example, taking ScalarMult(1, v)
            to v.
        '''
        from proveit.numbers import Complex
        if (InSet(self.scalar, Complex).proven()
                and InSet(self.scaled, Complex).proven()):
            # If the operands are both complex numbers, this will
            # ScalarMult will reduce to number multiplication.
            return self.number_mult_reduction()

        expr = self
        # A convenience to allow successive updating of the equation
        # via transitivities (starting with self=self).
        eq = TransRelUpdater(self)

        # (1) Simplify doubly-nested scalar multiplication
        if isinstance(expr.scaled, ScalarMult):
            # Reduce a double-nested scalar multiplication.
            expr = eq.update(self.double_scaling_reduction())

        # (2) Simplify multiplicative identity
        #     (if expr is still a ScalarMult)
        if isinstance(expr, ScalarMult) and expr.scalar == one:
            expr = eq.update(expr.scalar_one_elimination(preserve_all=True))

        return eq.relation
Beispiel #16
0
    def doReducedEvaluation(self, assumptions=USE_DEFAULTS):
        '''
        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, SimplificationError
        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)
            Afactors = self.operands[:zeroIdx]
            Bfactors = self.operands[zeroIdx+1:]
            return multZeroAny.specialize({m:len(Afactors), n:len(Bfactors), AA:Afactors, BB:Bfactors}, 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 SimplificationError("Unable to evaluate %s"%str(self))
Beispiel #17
0
    def evaluate_add_digit(self, assumptions=USE_DEFAULTS):
        """
        Evaluates each addition within the DecimalSequence
        """
        from proveit import TransRelUpdater, ExprTuple
        from proveit.numbers import Add
        from . import deci_sequence_reduction

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

        for i, digit in enumerate(self.digits):
            if isinstance(digit, Add):
                # only implemented for addition.

                _m = expr.digits[:i].num_elements(assumptions=assumptions)
                _n = digit.operands.num_elements(assumptions=assumptions)
                _k = expr.digits[i + 1:].num_elements(assumptions=assumptions)
                # _a = expr.inner_expr().operands[:i]
                _b = digit.operands
                _c = digit.evaluation(assumptions=assumptions).rhs
                # _d = expr.inner_expr().operands[i + 1:]

                _a = expr.digits[:i]
                _d = expr.digits[i + 1:]

                expr = eq.update(
                    deci_sequence_reduction.instantiate(
                        {
                            m: _m,
                            n: _n,
                            k: _k,
                            a: _a,
                            b: _b,
                            c: _c,
                            d: _d
                        },
                        assumptions=assumptions))

        return eq.relation
Beispiel #18
0
    def reduction(self, assumptions=USE_DEFAULTS):
        '''
        Deduce that this enum Set expression is equal to the Set's
        support -- i.e. equal to a Set with all multiplicities reduced
        to 1. For example, the Set(a, a, b, b, c, d)={a, a, b, b, c, d}
        is equal to its support {a, b, c, d}. The deduction is
        achieved by successively applying the element-by-element
        reduction_elem() method until no further reduction is possible.
        '''
        from proveit import TransRelUpdater
        eq = TransRelUpdater(self, assumptions)
        current_operands = list(self.operands)
        # the following does not preserve the order, but we really
        # just want the size of the support set
        desired_operands = set(self.operands)
        desired_num_operands = len(set(self.operands))
        expr = self
        while len(current_operands) > desired_num_operands:
            expr = eq.update(expr.reduction_elem(assumptions=assumptions))
            current_operands = expr.operands

        return eq.relation
Beispiel #19
0
 def oneEliminations(self, assumptions=USE_DEFAULTS):
     '''
     Equivalence method that derives a simplification in which
     factors of one are eliminated.  For example:
         x*1*y*1*z*1 = x*y*z
     '''
     from proveit.number import one
     
     expr = self
     
     # A convenience to allow successive update to the equation via transitivities.
     # (starting with self=self).
     eq = TransRelUpdater(self, assumptions)
     
     # Work in reverse order so indices don't need to be updated.
     for rev_idx, operand in enumerate(reversed(self.operands)):
         if operand==one:
             idx = len(self.operands) - rev_idx - 1
             expr = eq.update(expr.oneElimination(idx, assumptions))
             if not isinstance(expr, Mult):
                 break # can't do an elimination if reduced to a single term.
     
     return eq.relation                
Beispiel #20
0
    def reduce_exprRange(self, assumptions=USE_DEFAULTS):
        '''
        Tries to reduce a decimal sequence containing an ExprRange.
        For example, reduce #(3 4 2 .. 4 repeats .. 2 3) to 3422223
        '''
        from proveit import TransRelUpdater
        from proveit.core_expr_types.tuples import n_repeats_reduction
        from proveit.numbers.numerals.decimals import deci_sequence_reduction_ER

        was_range_reduction_disabled = (
            ExprRange in defaults.disabled_auto_reduction_types)

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

        idx = 0
        for i, digit in enumerate(self.digits):
            # i is the current index in the original expression
            # idx is the current index in the transformed expression (eq.relation)
            if isinstance(digit, ExprRange) and isinstance(
                    digit.body, Numeral):
                import proveit.numbers.numerals.decimals

                # _m = expr.digits[:i].num_elements(assumptions)
                # _n = digit.end_index
                # _k = expr.digits[i + 1:].num_elements(assumptions)
                # _a = expr.digits[:i]
                # _b = digit.body
                # _d = expr.digits[i + 1:]

                _m = eq.relation.rhs.digits[:idx].num_elements(assumptions)
                _n = digit.end_index
                _k = expr.digits[i + 1:].num_elements(assumptions)
                _a = eq.relation.rhs.digits[:idx]
                _b = digit.body
                _d = expr.digits[i + 1:]

                # if digit.end_index.as_int() >= 10:
                # Automatically reduce an Expression range of
                # a single numeral to an Expression tuple
                # (3 .. 4 repeats.. 3) = 3333
                # #(2 3 4 (5 ..3 repeats.. 5) 6 7 8) = 234555678

                while _n.as_int() > 9:
                    _x = digit.body

                    _c = n_repeats_reduction.instantiate(
                        {
                            n: _n,
                            x: _x
                        }, assumptions=assumptions).rhs

                    eq.update(
                        deci_sequence_reduction_ER.instantiate(
                            {
                                m: _m,
                                n: _n,
                                k: _k,
                                a: _a,
                                b: _b,
                                c: _c,
                                d: _d
                            },
                            assumptions=assumptions))
                    _n = num(_n.as_int() - 1)
                    idx += 1

                #_n = digit.end_index
                len_thm = proveit.numbers.numerals.decimals \
                    .__getattr__('reduce_%s_repeats' % _n)
                _x = digit.body

                _c = len_thm.instantiate({x: _x}, assumptions=assumptions).rhs

                idx += _n.as_int()

                if _n == one:
                    # we have to disable ExprRange reduction in this instance
                    # because Prove-It knows that an ExprRange of one element is just that element
                    # but we need to preserve the left hand side of the equation without this reduction
                    defaults.disabled_auto_reduction_types.add(ExprRange)
                    eq.update(
                        deci_sequence_reduction_ER.instantiate(
                            {
                                m: _m,
                                n: _n,
                                k: _k,
                                a: _a,
                                b: _b,
                                c: _c,
                                d: _d
                            },
                            assumptions=assumptions))

                    if not was_range_reduction_disabled:
                        defaults.disabled_auto_reduction_types.remove(
                            ExprRange)

                else:
                    eq.update(
                        deci_sequence_reduction_ER.instantiate(
                            {
                                m: _m,
                                n: _n,
                                k: _k,
                                a: _a,
                                b: _b,
                                c: _c,
                                d: _d
                            },
                            assumptions=assumptions))
            else:
                idx += 1

        return eq.relation
Beispiel #21
0
    def factors_extraction(self, field=None, **defaults_config):
        '''
        Derive an equality between this VecSum and the result
        when all possible leading scalar factors have been extracted
        and moved to the front of the VecSum (for example, in the
        case where the summand of the VecSum is a ScalarMult) and
        all possible tensor product factors have been moved outside
        the VecSum (in front if possible, or afterward if necessary).
        For example, we could take the VecSum
            vec_sum = VecSum(ScalarMult(a, TensorProd(x, f(i), y))),
        where the index of summation is i, and call
            vec_sum.factor_extraction() to obtain:
            |- vec_sum = 
               ScalarMult(a, TensorProd(x, VecSum(f(i)), y))
        Note that any factors inside the summand that depend on the
        index of summation cannot be pulled out from inside the VecSum,
        and thus pose limitations on the result.
        Note that this method only works when self has a single
        index of summation, and only when self has a summand that is
        a ScalarMult or TensorProd.
        Later versions of this method should provide mechanisms to
        specify factors to extract from, and/or leave behind in, the
        VecSum.
        '''
        expr = self
        summation_index = expr.index
        assumptions = defaults.assumptions + expr.conditions.entries
        assumptions_with_conditions = (
                defaults.assumptions + expr.conditions.entries)

        # for convenience in updating our equation:
        # this begins with eq.relation as expr = expr
        eq = TransRelUpdater(expr)

        # If the summand is a ScalarMult, perform a
        # shallow_simplification(), which will remove nested
        # ScalarMults and multiplicative identities. This is
        # intended to simplify without changing too much the
        # intent of the user. This might even transform the
        # ScalarMult object into something else.
        from proveit.linear_algebra import ScalarMult, TensorProd
        if isinstance(expr.summand, ScalarMult):
            expr = eq.update(
                    expr.inner_expr().summand.shallow_simplification())
        if isinstance(expr.summand, ScalarMult):
            # had to re-check, b/c the shallow_simplification might
            # have transformed the ScalarMult into the scaled object
            tensor_prod_summand = False # not clearly useful; review please
            the_scalar = expr.summand.scalar
            
        elif isinstance(expr.summand, TensorProd):
            tensor_prod_summand = True # not clearly useful; review please

        if isinstance(expr.summand, ScalarMult):
            if summation_index not in free_vars(expr.summand.scalar):
                # it doesn't matter what the scalar is; the whole thing
                # can be pulled out in front of the VecSum
                from proveit.linear_algebra.scalar_multiplication import (
                    distribution_over_vec_sum)
                summand_in_vec_space = expr.summand.deduce_in_vec_space(
                        field=field, assumptions=assumptions_with_conditions)
                _V_sub = summand_in_vec_space.domain
                _K_sub = VecSpaces.known_field(_V_sub)
                _b_sub = expr.indices
                _j_sub = _b_sub.num_elements()
                _f_sub = Lambda(expr.indices, expr.summand.scaled)
                _Q_sub = Lambda(expr.indices, expr.condition)
                _k_sub = expr.summand.scalar
                imp = distribution_over_vec_sum.instantiate(
                        {V: _V_sub, K: _K_sub, b: _b_sub, j: _j_sub,
                         f: _f_sub, Q: _Q_sub, k: _k_sub},
                         assumptions=assumptions_with_conditions)
                expr = eq.update(imp.derive_consequent(
                    assumptions=assumptions_with_conditions).derive_reversed())
            else:
                # The scalar portion is dependent on summation index.
                # If the scalar itself is a Mult of things, go through
                # and pull to the front of the Mult all individual
                # factors that are not dependent on the summation index.
                if isinstance(expr.summand.scalar, Mult):
                    # Repeatedly pull index-independent factors #
                    # to the front of the Mult factors          #

                    # prepare to count the extractable and
                    # unextractable factors
                    _num_factored = 0
                    _num_unfactored = len(expr.summand.scalar.operands.entries)

                    # go through factors from back to front
                    for the_factor in reversed(
                            expr.summand.scalar.operands.entries):

                        if summation_index not in free_vars(the_factor):
                            expr = eq.update(
                                expr.inner_expr().summand.scalar.factorization(
                                    the_factor,
                                    assumptions=assumptions_with_conditions,
                                    preserve_all=True))
                            _num_factored += 1
                            _num_unfactored -= 1

                    # group the factorable factors
                    if _num_factored > 0:
                        expr = eq.update(
                            expr.inner_expr().summand.scalar.association(
                                0, _num_factored,
                                assumptions=assumptions_with_conditions,
                                preserve_all=True))
                    # group the unfactorable factors
                    if _num_unfactored > 1:
                        expr = eq.update(
                            expr.inner_expr().summand.scalar.association(
                                1, _num_unfactored,
                                assumptions=assumptions_with_conditions,
                                preserve_all=True))

                    # finally, extract any factorable scalar factors
                    if _num_factored > 0:
                        from proveit.linear_algebra.scalar_multiplication import (
                                distribution_over_vec_sum_with_scalar_mult)
                        # Mult._simplification_directives_.ungroup = False
                        # _V_sub = VecSpaces.known_vec_space(expr, field=field)
                        summand_in_vec_space = (
                                expr.summand.deduce_in_vec_space(
                                        field = field,
                                        assumptions =
                                        assumptions_with_conditions))
                        _V_sub = summand_in_vec_space.domain
                        _K_sub = VecSpaces.known_field(_V_sub)
                        _b_sub = expr.indices
                        _j_sub = _b_sub.num_elements()
                        _f_sub = Lambda(expr.indices, expr.summand.scaled)
                        _Q_sub = Lambda(expr.indices, expr.condition)
                        _c_sub = Lambda(expr.indices,
                                        expr.summand.scalar.operands[1])
                        _k_sub = expr.summand.scalar.operands[0]
                        # when instantiating, we set preserve_expr=expr;
                        # otherwise auto_simplification disassociates inside
                        # the Mult.
                        impl = distribution_over_vec_sum_with_scalar_mult.instantiate(
                                {V:_V_sub, K:_K_sub, b: _b_sub, j: _j_sub,
                                 f: _f_sub, Q: _Q_sub, c:_c_sub, k: _k_sub},
                                 preserve_expr=expr,
                                assumptions=assumptions_with_conditions)
                        expr = eq.update(impl.derive_consequent(
                                assumptions=assumptions_with_conditions).
                                derive_reversed())

                else:
                    # The scalar component is dependent on summation
                    # index but is not a Mult.
                    # Revert everything and return self = self.
                    print("Found summation index {0} in the scalar {1} "
                          "and the scalar is not a Mult object.".
                      format(summation_index, expr.summand.scalar))
                    eq = TransRelUpdater(self)

        # ============================================================ #
        # VECTOR FACTORS                                               #
        # ============================================================ #
        # After the scalar factors (if any) have been dealt with,
        # proceed with the vector factors in any remaining TensorProd
        # in the summand.
        # Notice that we are not guaranteed at this point that we even
        # have a TensorProd to factor, and if we do have a TensorProd
        # we have not identified the non-index-dependent factors to 
        # extract.
        # After processing above for scalar factors, we might now have
        # (1) expr = VecSum (we didn't find scalar factors to extract),
        # inside of which we might have a ScalarMult or a TensorProd;
        # or (2) expr = ScalarMult (we found some scalar factors to
        # extract), with a VecSum as the scaled component.

        if isinstance(expr, VecSum):
            expr = eq.update(expr.tensor_prod_factoring())
        elif isinstance(expr, ScalarMult) and isinstance(expr.scaled, VecSum):
            expr = eq.update(expr.inner_expr().scaled.tensor_prod_factoring())

        return eq.relation
Beispiel #22
0
    def factorization(self,
                      the_factors,
                      pull="left",
                      group_factors=True,
                      group_remainder=True,
                      **defaults_config):
        '''
        Return the proven factorization (equality with the factored
        form) from pulling the factor(s) from this division to the 
        "left" or "right".  If there are multiple occurrences, the first
        occurrence is used.
        If group_factors is True, the factors are
        grouped together as a sub-product.
        The group_remainder parameter is not relevant here but kept
        for consistency with other factorization methods.

        Examples:

            [(a*b)/(c*d)].factorization((a/c))
            proves (a*b)/(c*d) = (a/c)*(b/d)
            [(a*b)/(c*d)].factorization((1/c))
            proves (a*b)/(c*d) = (1/c)*(a*b/d)
            [(a*b)/(c*d)].factorization(a, pull='right')
            proves (a*b)/(c*d) = (b/(c*d))*a
            [a/(c*d)].factorization(a, pull='right')
            proves a/(c*d) = (1/(c*d))*a
            [(a*b)/d].factorization((a/d), pull='right')
            proves (a*b)/d = b*(a/d)
        '''
        from proveit.numbers import one, Mult
        from . import mult_frac_left, mult_frac_right, prod_of_fracs
        expr = self
        eq = TransRelUpdater(expr)
        if the_factors == self:
            return eq.relation  # self = self
        if isinstance(the_factors, Div):
            the_factor_numer = the_factors.numerator
            the_factor_denom = the_factors.denominator
        else:
            the_factor_numer = the_factors
            the_factor_denom = one
        replacements = []
        # Factor out a fraction.
        if expr.denominator == the_factor_denom:
            # Factor (x/z) from (x*y)/z.
            # x or y may be 1.
            if the_factor_numer not in (one, expr.numerator):
                expr = eq.update(expr.inner_expr().numerator.factorization(
                    the_factors.numerator,
                    pull=pull,
                    group_factors=True,
                    group_remainder=True,
                    preserve_all=True))
            if pull == 'left':
                # factor (x*y)/z into (x/z)*y
                thm = mult_frac_left
                if the_factor_numer == one:
                    # factor y/z into (1/z)*y
                    _x = one
                    _y = expr.numerator
                    replacements.append(
                        Mult(_x, _y).one_elimination(0, preserve_all=True))
            else:
                # factor (x*y)/z into x*(y/z)
                thm = mult_frac_right
                if the_factor_numer == one:
                    # factor x/z into x*(1/z)
                    _x = expr.numerator
                    _y = one
                    replacements.append(
                        Mult(_x, _y).one_elimination(1, preserve_all=True))
            if the_factor_numer != one:
                assert expr.numerator.operands.num_entries() == 2
                _x = expr.numerator.operands.entries[0]
                _y = expr.numerator.operands.entries[1]
            _z = expr.denominator
            eq.update(
                thm.instantiate({
                    x: _x,
                    y: _y,
                    z: _z
                },
                                replacements=replacements))
        else:
            # Factor (x*y)/(z*w) into (x/z)*(y/w).
            thm = prod_of_fracs
            if the_factor_denom not in (one, expr.denominator):
                expr = eq.update(expr.inner_expr().denominator.factorization(
                    the_factor_denom,
                    pull=pull,
                    group_factors=True,
                    preserve_all=True))
                assert expr.denominator.operands.num_entries() == 2
                _z = expr.denominator.operands.entries[0]
                _w = expr.denominator.operands.entries[1]
            elif (pull == 'left') == (the_factor_denom == one):
                # Factor (x*y)/w into x*(y/w).
                _z = one
                _w = expr.denominator
                replacements.append(
                    Mult(_z, _w).one_elimination(0, preserve_all=True))
            else:
                # Factor (x*y)/z into (x/z)*y.
                _z = expr.denominator
                _w = one
                replacements.append(
                    Mult(_z, _w).one_elimination(1, preserve_all=True))
            # Factor the numerator parts unless there is a 1 numerator.
            if the_factor_numer not in (one, expr.numerator):
                expr = eq.update(expr.inner_expr().numerator.factorization(
                    the_factor_numer,
                    pull=pull,
                    group_factors=True,
                    group_remainder=True,
                    preserve_all=True))
                assert expr.numerator.operands.num_entries() == 2
                # Factor (x*y)/(z*w) into (x/z)*(y/w)
                _x = expr.numerator.operands.entries[0]
                _y = expr.numerator.operands.entries[1]
            elif (pull == 'left') == (the_factor_numer == one):
                # Factor y/(z*w) into (1/z)*(y/w)
                _x = one
                _y = expr.numerator
                replacements.append(
                    Mult(_x, _y).one_elimination(0, preserve_all=True))
            else:
                # Factor x/(y*z) into (x/y)*(1/z)
                _x = expr.numerator
                _y = one
                replacements.append(
                    Mult(_x, _y).one_elimination(1, preserve_all=True))
            # create POSSIBLE replacements for inadvertently generated
            # fractions of the form _x/1 (i.e. _z = 1)
            # or _y/1 (i.e. _w = 1):
            if _z == one:
                replacements.append(
                    frac(_x, _z).divide_by_one_elimination(preserve_all=True))
            if _w == one:
                replacements.append(
                    frac(_y, _w).divide_by_one_elimination(preserve_all=True))

            eq.update(
                thm.instantiate({
                    x: _x,
                    y: _y,
                    z: _z,
                    w: _w
                },
                                replacements=replacements,
                                preserve_expr=expr))

        return eq.relation
Beispiel #23
0
def generic_permutation(expr,
                        new_order=None,
                        cycles=None,
                        assumptions=USE_DEFAULTS):
    '''
    Deduce that the expression expr is equal to a new_expr which is
    the same class and in which the operands at indices 0, 1, …, n-1
    have been reordered as specified EITHER by the new_order list OR
    by the cycles list parameter. For example, if expr were the
    enumerated Set S = {a, b, c, d}, then:
        generic_permutation(S, new_order=[0, 2, 3, 1]) and
        generic_permutation(S, cycles=[(1, 2, 3)]) would both
    return |- {a, b, c, d} = {a, c, d, b}.
    This generic_permutation method can work with any expression type
    which enjoys commutativity of its operands and has established
    the corresponding permutation_move() method and related axioms or
    theorems.
    '''
    # check validity of default param usage: should have new_order list
    # OR list of cycles, but not both
    if new_order is None and cycles is None:
        raise ValueError("Need to specify either a new ordering in "
                         "the form of new_order = list OR "
                         "cycles = list of tuples.")
    if new_order is not None and cycles is not None:
        raise ValueError("Need to specify EITHER new_order OR cycles, "
                         "but not both.")

    # check validity of provided index values:
    # (1) each index i needs to be 0 ≤ i ≤ n-1;
    # (2) set of indices provided in new_order needs to be complete,
    #     i.e. consisting of ints 0, 1, 2, …, n-1 (but we allow cycle
    #     notation to omit length-1 cycles)
    # (3) cannot have repeated indices
    expected_number_of_indices = expr.operands.num_entries()
    expected_indices_set = set(range(0, expected_number_of_indices))
    # might be able to condense some of new_order and cycles together
    # later, but somewhat challenging if we allow cycles to omit
    # length-1 cycles
    if new_order:
        given_indices_list = new_order
        given_indices_set = set(new_order)
        if len(given_indices_list) > expected_number_of_indices:
            raise ValueError("new_order specification contains too "
                             "many items, listing {2} indices when "
                             "it should list {0} indices from 0 to {1}".format(
                                 expected_number_of_indices,
                                 expected_number_of_indices - 1,
                                 len(new_order)))
        unexpected_indices_set = given_indices_set - expected_indices_set
        if len(unexpected_indices_set) != 0:
            raise IndexError("Index or indices out of bounds: {0}. "
                             "new_order should be a list of indices "
                             "i such that 0 ≤ i ≤ {1}.".format(
                                 unexpected_indices_set,
                                 expected_number_of_indices - 1))
        missing_indices_set = expected_indices_set - given_indices_set
        if len(missing_indices_set) != 0:
            raise ValueError("new_order specification is missing "
                             "indices: {}.".format(missing_indices_set))

    if cycles:
        # collect the indices from the cycles
        given_indices_list = []
        for cycle in cycles:
            for i in cycle:
                given_indices_list.append(i)
        given_indices_set = set(given_indices_list)
        if len(given_indices_list) > expected_number_of_indices:
            raise ValueError("cycles specification contains too "
                             "many items, providing {0} indices when "
                             "it should provide no more than {1} "
                             "indices with values from 0 to {2}".format(
                                 len(given_indices_list),
                                 expected_number_of_indices,
                                 expected_number_of_indices - 1))
        unexpected_indices_set = given_indices_set - expected_indices_set
        if len(unexpected_indices_set) != 0:
            raise IndexError("Index or indices out of bounds: {0}. "
                             "cycles should only contain indices "
                             "i such that 0 ≤ i ≤ {1}.".format(
                                 unexpected_indices_set,
                                 expected_number_of_indices - 1))
        # For cycles, we allow user to omit length-1 cycles
        # so we do NOT need to check for missing indices.
        # Instead, once cycles have passed all the tests,
        # convert to new_order list
        new_order = list(range(0, expected_number_of_indices))
        for cycle in cycles:
            temp_cycle_length = len(cycle)
            for i in range(0, temp_cycle_length):
                new_order[cycle[i]] = cycle[(i + 1) % temp_cycle_length]
        # quick check for duplicates in new_order (can't check it
        # earlier because there might be legitimate duplicates during
        # the cycles-to-new_order construction process):
        if len(new_order) != len(set(new_order)):
            raise IndexError("Index or indices duplicated in cycles "
                             "specification. cycles should contain "
                             "only a single instance of any specific "
                             "index value.")

    # if user-supplied args check out, then we can continue,
    # regardless of whether user has supplied new_order or cycles
    from proveit import TransRelUpdater

    current_order = list(range(0, expected_number_of_indices))
    desired_order = new_order  # notice this isn't a deep copy

    # No need to explicitly check for a trivial (non)-permutation;
    # the eq.relation will hold this for us

    # for convenience while updating our equation
    eq = TransRelUpdater(expr, assumptions)

    while current_order != desired_order:

        # Use set comprehension to find 1st index where the
        # current_order and desired_order lists differ and determine
        # the desired_order value at that location
        temp_order_diff_info = next(
            (idx, x, y)
            for idx, (x, y) in enumerate(zip(current_order, desired_order))
            if x != y)
        # extract the init and final indices for the permutation
        init_idx = current_order.index(temp_order_diff_info[2])
        final_idx = temp_order_diff_info[0]
        expr = eq.update(
            expr.permutation_move(init_idx, final_idx,
                                  assumptions=assumptions))
        # update current_order to reflect step-wise change
        current_order.remove(temp_order_diff_info[2])
        current_order.insert(final_idx, temp_order_diff_info[2])

    return eq.relation
Beispiel #24
0
    def cancelation(self, term_to_cancel, assumptions=USE_DEFAULTS):
        '''
        Deduce and return an equality between self and a form in which
        the given operand has been canceled on the numerator and
        denominator.  For example,
        [(a*b)/(b*c)].cancelation(b) would return
        (a*b)/(b*c) = a / c
        '''
        from proveit.numbers import Mult
        expr = self
        eq = TransRelUpdater(expr, assumptions)

        if self.numerator == self.denominator == term_to_cancel:
            # x/x = 1
            from . import frac_cancel_complete
            return frac_cancel_complete.instantiate({
                x: term_to_cancel
            }).checked(assumptions)

        if term_to_cancel != self.numerator:
            if (not isinstance(self.numerator, Mult)
                    or term_to_cancel not in self.numerator.operands):
                raise ValueError("%s not in the denominator of %s" %
                                 (term_to_cancel, self))
            # Factor the term_to_cancel from the numerator to the left.
            expr = eq.update(expr.inner_expr().numerator.factorization(
                term_to_cancel,
                group_factor=True,
                group_remainder=True,
                assumptions=assumptions))
        if term_to_cancel != self.denominator:
            if (not isinstance(self.denominator, Mult)
                    or term_to_cancel not in self.denominator.operands):
                raise ValueError("%s not in the denominator of %s" %
                                 (term_to_cancel, self))
            # Factor the term_to_cancel from the denominator to the left.
            expr = eq.update(expr.inner_expr().denominator.factorization(
                term_to_cancel,
                group_factor=True,
                group_remainder=True,
                assumptions=assumptions))
        if term_to_cancel == self.numerator:
            from . import frac_cancel_numer_left
            assert len(expr.denominator.operands) == 2, "Should be grouped"
            expr = eq.update(
                frac_cancel_numer_left.instantiate(
                    {
                        x: term_to_cancel,
                        y: expr.denominator.operands[1]
                    },
                    assumptions=assumptions))
            return eq.relation
        elif term_to_cancel == self.denominator:
            from . import frac_cancel_denom_left
            assert len(expr.numerator.operands) == 2, "Should be grouped"
            expr = eq.update(
                frac_cancel_denom_left.instantiate(
                    {
                        x: term_to_cancel,
                        y: expr.numerator.operands[1]
                    },
                    assumptions=assumptions))
            return eq.relation
        else:
            from . import frac_cancel_left
            expr = eq.update(
                frac_cancel_left.instantiate(
                    {
                        x: term_to_cancel,
                        y: expr.numerator.operands[1],
                        z: expr.denominator.operands[1]
                    },
                    assumptions=assumptions))
            return eq.relation
Beispiel #25
0
    def shallow_simplification(self,
                               *,
                               must_evaluate=False,
                               **defaults_config):
        '''
        Returns a proven simplification equation for this Divide
        expression assuming the operands have been simplified.

        Specifically, cancels common factors and eliminates ones.
        '''
        from proveit.logic import is_irreducible_value
        from proveit.numbers import one, Neg
        expr = self
        # for convenience updating our equation
        eq = TransRelUpdater(expr)

        # perform cancelations where possible
        expr = eq.update(expr.cancelations(preserve_all=True))
        if not isinstance(expr, Div):
            # complete cancelation.
            return eq.relation

        if self.is_irreducible_value():
            # already irreducible
            return Equals(self, self).conclude_via_reflexivity()

        if must_evaluate and not all(
                is_irreducible_value(operand) for operand in self.operands):
            for operand in self.operands:
                if not is_irreducible_value(operand):
                    # The simplification of the operands may not have
                    # worked hard enough.  Let's work harder if we
                    # must evaluate.
                    operand.evaluation()
            return self.evaluation()

        if expr.denominator == one:
            # eliminate division by one
            expr = eq.update(expr.divide_by_one_elimination(preserve_all=True))

        if (isinstance(expr, Div)
                and Div._simplification_directives_.factor_negation
                and isinstance(expr.numerator, Neg)):
            # we have something like (-a)/b but want -(a/b)
            expr = eq.update(expr.neg_extraction())
            # return eq.relation  # no more division simplifications.

        if (Div._simplification_directives_.reduce_zero_numerator
                and isinstance(expr, Div)):
            if ((expr.numerator == zero
                 or Equals(expr.numerator, zero).proven())
                    and NotEquals(expr.denominator, zero).proven()):
                # we have something like 0/x so reduce to 0
                expr = eq.update(expr.zero_numerator_reduction())

        # finally, check if we have something like (x/(y/z))
        # but! remember to check here if we still even have a Div expr
        # b/c some of the work above might have changed it to
        # something else!
        if (isinstance(expr, Div) and isinstance(expr.denominator, Div)
                and NotEquals(expr.denominator.numerator, zero).proven()
                and NotEquals(expr.denominator.denominator, zero).prove()):
            expr = eq.update(expr.div_in_denominator_reduction())

        return eq.relation
Beispiel #26
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)
Beispiel #27
0
    def factorization(self,
                      the_factor,
                      pull="left",
                      group_factors=True,
                      field=None,
                      **defaults_config):
        '''
        Deduce an equality between this VecAdd expression and a
        version in which either:
        (1) the scalar factor the_factor has been factored out in
            front (or possibly out behind) to produce a new ScalarMult;
        OR
        (2) the tensor product factor the_factor has been factored
            out in front (or possible out behind) to produce a new
            TensorProd.
        For example, if
            x = VecAdd(ScalarMult(a, v1), ScalarMult(a, v2))
        then x.factorization(a) produces:
            |- x = ScalarMult(a, VecAdd(v1, v2)).
        Prove-It will need to know or be able to derive a vector space
        in which the vectors live.
        This method only works if the terms of the VecAdd are all
        ScalarMult objects or all TensorProd objects.
        In the case of all ScalarMult objects, any nested ScalarMult
        objects are first flattened if possible.
        Note: In the case of a VecAdd of all TensorProd objects,
        the lack of commutativity for tensor products limits any
        factorable tensor product factors to those occurring on the
        far left or far right of each tensor product term. Thus, for
        example, if
        x = VecAdd(TensorProd(v1, v2, v3), TensorProd(v1, v4, v5))
        we can call x.factorization(v1) to obtain
        |- x =
        TensorProd(v1, VecAdd(TensorProd(v2, v3), TensorProd(v4, v5))),
        but we cannot factor v1 our of the expression
        y = VecAdd(TensorProd(v2, v1, v3), TensorProd(v4, v1, v5))
        '''

        expr = self
        eq = TransRelUpdater(expr)

        replacements = list(defaults.replacements)

        from proveit.linear_algebra import ScalarMult, TensorProd
        from proveit.numbers import one, Mult

        # Case (1) VecAdd(ScalarMult, ScalarMult, ..., ScalarMult)
        if all(isinstance(op, ScalarMult) for op in self.operands):
            # look for the_factor in each scalar;
            # code based on Add.factorization()
            _b = []
            for _i in range(expr.terms.num_entries()):
                # remove nesting of ScalarMults
                term = expr.terms[_i].shallow_simplification().rhs
                expr = eq.update(
                    expr.inner_expr().terms[_i].shallow_simplification())
                # simplify the scalar part of the ScalarMult
                term = term.inner_expr().scalar.shallow_simplification().rhs
                expr = eq.update(expr.inner_expr().terms[_i].scalar.
                                 shallow_simplification())
                if hasattr(term.scalar, 'factorization'):
                    term_scalar_factorization = term.scalar.factorization(
                        the_factor,
                        pull,
                        group_factors=group_factors,
                        group_remainder=True,
                        preserve_all=True)
                    if not isinstance(term_scalar_factorization.rhs, Mult):
                        raise ValueError(
                            "Expecting right hand side of each factorization "
                            "to be a product. Instead obtained: {}".format(
                                term_scalar_factorization.rhs))
                    if pull == 'left':
                        # the grouped remainder on the right
                        _b.append(
                            ScalarMult(
                                term_scalar_factorization.rhs.operands[-1],
                                term.scaled))
                    else:
                        # the grouped remainder on the left
                        _b.append(
                            ScalarMult(
                                term_scalar_factorization.rhs.operands[0],
                                term.scaled))
                    # substitute in the factorized term
                    expr = eq.update(
                        term_scalar_factorization.substitution(
                            expr.inner_expr().terms[_i].scalar,
                            preserve_all=True))
                else:
                    if term.scalar != the_factor:
                        raise ValueError(
                            "Factor, %s, is not present in the term at "
                            "index %d of %s!" % (the_factor, _i, self))
                    if pull == 'left':
                        replacements.append(
                            Mult(term.scalar, one).one_elimination(1))
                    else:
                        replacements.append(
                            Mult(one, term.scalar).one_elimination(0))
                    _b.append(ScalarMult(one, term.scaled))

            if not group_factors and isinstance(the_factor, Mult):
                factor_sub = the_factor.operands
            else:
                factor_sub = ExprTuple(the_factor)

            # pull left/right not really relevant for the ScalarMult
            # cases; this simplification step still seems relevant
            if defaults.auto_simplify:
                # Simplify the remainder of the factorization if
                # auto-simplify is enabled.
                replacements.append(VecAdd(*_b).simplification())

            from proveit import K, i, k, V, a
            # Perhaps here we could search through the operands to find
            # an appropriate VecSpace? Or maybe it doesn't matter?
            vec_space_membership = expr.operands[0].deduce_in_vec_space(
                field=field)
            _V_sub = vec_space_membership.domain
            _K_sub = VecSpaces.known_field(_V_sub)
            _i_sub = expr.operands.num_elements()
            _k_sub = the_factor
            _a_sub = ExprTuple(*_b)

            from proveit.linear_algebra.scalar_multiplication import (
                distribution_over_vectors)
            distribution = distribution_over_vectors.instantiate(
                {
                    V: _V_sub,
                    K: _K_sub,
                    i: _i_sub,
                    k: _k_sub,
                    a: _a_sub
                },
                replacements=replacements)

            # need to connect the distributed version back to the
            # original self, via a shallow_simplification() of
            # each of the ScalarMult terms resulting in the distribution
            for _i in range(len(distribution.rhs.operands.entries)):
                distribution = (distribution.inner_expr().rhs.operands[_i].
                                shallow_simplify())

            eq.update(distribution.derive_reversed())

        # Case (2) VecAdd(TensorProd, TensorProd, ..., TensorProd)
        elif all(isinstance(op, TensorProd) for op in self.operands):
            # if hasattr(the_factor, 'operands'):
            #     print("the_factor has operands: {}".format(the_factor.operands))
            #     the_factor_tuple = the_factor.operands.entries
            # else:
            #     print("the_factor does not have operands: {}".format(the_factor))
            #     the_factor_tuple = (the_factor,)
            if isinstance(the_factor, TensorProd):
                the_factor_tuple = the_factor.operands.entries
            else:
                the_factor_tuple = (the_factor, )
            # Setting the default_field here because the field
            # used manually in the association step somehow gets lost
            VecSpaces.default_field = field
            # look for the_factor in each TensorProd appearing in
            # the VecAdd operands, looking at the left vs. right
            # sides depending on the 'pull' direction specified
            _b = []  # to hold factors left behind
            for _i in range(expr.terms.num_entries()):
                # Notice we're not ready to deal with ExprRange
                # versions of Add operands here!
                # We are also implicitly assuming that each TensorProd
                # has at least two operands
                term = expr.terms[_i]
                if hasattr(term, 'operands'):
                    term_tuple = term.operands.entries
                else:
                    term_tuple = (term, )
                if pull == 'left':
                    # look for factor at left-most-side
                    if the_factor_tuple != term_tuple[0:len(the_factor_tuple)]:
                        raise ValueError(
                            "VecAdd.factorization() expecting the_factor "
                            "{0} to appear at the leftmost side of each "
                            "addend, but {0} does not appear at the "
                            "leftmost side of the addend {1}.".format(
                                the_factor, term))
                    else:
                        # we're OK, so save away the remainder of
                        # factors from the rhs of the term,
                        # and group any multi-term factor on the left
                        if len(term_tuple[len(the_factor_tuple):]) == 1:
                            _b.append(term_tuple[-1])
                        else:
                            _b.append(
                                TensorProd(
                                    *term_tuple[len(the_factor_tuple):]))
                            # then create an associated version of the
                            # expr to match the eventual thm instantiation
                            # ALSO NEED TO DO THIS FOR THE RIGHT CASE
                            expr = eq.update(
                                expr.inner_expr().operands[_i].association(
                                    len(the_factor_tuple),
                                    len(term_tuple) - len(the_factor_tuple),
                                    preserve_all=True))
                        # perhaps we actually don't need the assoc step?
                        # if len(the_factor_tuple) != 1:
                        #     expr = eq.update(expr.inner_expr().operands[_i].
                        #             association(0, len(the_factor_tuple),
                        #                     preserve_all=True))

                elif pull == 'right':
                    # look for factor at right-most-side
                    if the_factor_tuple != term_tuple[-(
                            len(the_factor_tuple)):]:
                        raise ValueError(
                            "VecAdd.factorization() expecting the_factor "
                            "{0} to appear at the rightmost side of each "
                            "addend, but {0} does not appear at the "
                            "rightmost side of the addend {1}.".format(
                                the_factor, term))
                    else:
                        # we're OK, so save away the remainder of
                        # factors from the lhs of the term,
                        # and group any multi-term factor on the right
                        if len(term_tuple[0:-(len(the_factor_tuple))]) == 1:
                            _b.append(term_tuple[0])
                        else:
                            _b.append(
                                TensorProd(
                                    *term_tuple[0:-(len(the_factor_tuple))]))
                            # then create an associated version of the
                            # expr to match the eventual thm instantiation
                            expr = eq.update(
                                expr.inner_expr().operands[_i].association(
                                    0,
                                    len(term_tuple) - len(the_factor_tuple),
                                    preserve_all=True))
                        # perhaps we actually don't need the assoc step?
                        # if len(the_factor_tuple) != 1:
                        #     expr = eq.update(expr.inner_expr().operands[_i].
                        #             association(
                        #                 len(term_tuple)-len(the_factor_tuple),
                        #                 len(the_factor_tuple),
                        #                 preserve_all=True))

                else:
                    raise ValueError(
                        "VecAdd.factorization() requires 'pull' argument "
                        "to be specified as either 'left' or 'right'.")

            # now ready to instantiate the TensorProd/VecAdd
            # theorem: tensor_prod_distribution_over_add
            # and derive it's reversed result
            from proveit.linear_algebra.tensors import (
                tensor_prod_distribution_over_add)
            from proveit import a, b, c, i, j, k, K, V
            from proveit.numbers import zero, one, num
            # useful to get ahead of time the num of operands
            # in the_factor and define the replacement
            # if hasattr(the_factor, 'operands'):
            #     num_factor_entries = num(the_factor.operands.num_entries())
            #     factor_entries = the_factor.operands.entries
            # else:
            #     num_factor_entries = one
            #     factor_entries = (the_factor,)
            # useful to get ahead of time the num of operands
            # in the_factor and define the replacement
            if isinstance(the_factor, TensorProd):
                num_factor_entries = num(the_factor.operands.num_entries())
                factor_entries = the_factor.operands.entries
            else:
                num_factor_entries = one
                factor_entries = (the_factor, )
            # call deduce_in_vec_space() on the original self
            # instead of the current expr, otherwise we can run into
            # compications due to the associated sub-terms
            vec_space_membership = self.operands[0].deduce_in_vec_space(
                field=field)
            _V_sub = vec_space_membership.domain
            _K_sub = VecSpaces.known_field(_V_sub)
            if pull == 'left':
                # num of operands in left the_factor
                _i_sub = num_factor_entries
                # num of operands in right factor
                _k_sub = zero
                # the actual factor operands
                _a_sub = factor_entries
                # the other side is empty
                _c_sub = ()
            elif pull == 'right':
                # left side is empty
                _i_sub = zero
                # right side has the factor
                _k_sub = num_factor_entries
                # left side is empty
                _a_sub = ()
                # right side has the factor
                _c_sub = factor_entries
            _j_sub = num(len(_b))
            _b_sub = ExprTuple(*_b)

            from proveit.linear_algebra.tensors import (
                tensor_prod_distribution_over_add)
            impl = tensor_prod_distribution_over_add.instantiate(
                {
                    V: _V_sub,
                    K: _K_sub,
                    i: _i_sub,
                    j: _j_sub,
                    k: _k_sub,
                    a: _a_sub,
                    b: _b_sub,
                    c: _c_sub
                },
                preserve_all=True)

            conseq = impl.derive_consequent()

            eq.update(conseq.derive_reversed())

        else:
            print("Not yet an identified case. Sorry!")

        return eq.relation
Beispiel #28
0
    def tensor_prod_factoring(self, idx=None, idx_beg=None, idx_end=None,
                              field=None, **defaults_config):
        '''
        For a VecSum with a TensorProd summand or ScalarMult summand
        with a scaled attribute being a TensorProd, factor out from
        the VecSum the TensorProd vectors other than the ones indicated
        by the (0-based) idx, or idx_beg and idx_end pair and return
        an equality between the original VecSum and the new TensorProd.
        For example, we could take the VecSum defined by
        vec_sum = VecSum(TensorProd(x, f(i), y, z))
        and call vec_sum.tensor_prod_factoring(idx_beg=1, idx_end=2)
        to obtain:

            |- VecSum(TensorProd(x, f(i), y, z)) = 
               TensorProd(x, VecSum(TensorProd(f(i), y)), z)

        This method should work even if the summand is a nested
        ScalarMult. Note that any vectors inside the TensorProd that
        depend on the index of summation cannot be pulled out of the
        VecSum and thus will cause the method to fail if not chosen
        to remain inside the VecSum. If all idx args are 'None',
        method will factor out all possible vector factors, including
        the case where all factors could be removed and the VecSum
        eliminated entirely.
        Note that this method only works when self has a single
        index of summation.
        '''
        expr = self
        the_summand = self.summand

        eq = TransRelUpdater(expr)

        # Check that 
        #    (1) the_summand is a TensorProd
        # or (2) the_summand is a ScalarMult;
        # otherwise, this method does not apply
        from proveit.linear_algebra import ScalarMult, TensorProd
        if isinstance(the_summand, ScalarMult):
            # try shallow simplification first to remove nested
            # ScalarMults and multiplicative identities
            expr = eq.update(expr.inner_expr().summand.shallow_simplification())
            the_summand = expr.summand
        if isinstance(the_summand, TensorProd):
            tensor_prod_expr = the_summand
            tensor_prod_summand = True
            tensor_prod_factors_list = list(
                    the_summand.operands.entries)
        elif (isinstance(the_summand, ScalarMult)
              and isinstance(the_summand.scaled, TensorProd)):
            tensor_prod_expr = the_summand.scaled
            tensor_prod_summand = False
            tensor_prod_factors_list = list(
                    the_summand.scaled.operands.entries)
        else:
            raise ValueError(
                "tensor_prod_factoring() requires the VecSum summand "
                "to be a TensorProd or a ScalarMult (with its 'scaled' "
                "attribute a TensorProd); instead the "
                "summand is {}".format(self.instance_expr))

        if idx is None and idx_beg is None and idx_end is None:
            # prepare to take out all possible factors, including
            # the complete elimination of the VecSum if possible
            if expr.index not in free_vars(expr.summand):
                # summand does not depend on index of summation
                # so we can eliminate the VecSum entirely
                return expr.vec_sum_elimination(field=field)
            if expr.index in free_vars(tensor_prod_expr):
                # identify the extractable vs. non-extractable
                # TensorProd factors (and there must be at least
                # one such non-extractable factor)
                
                idx_beg = -1
                idx_end = -1
                for i in range(len(expr.summand.operands.entries)):
                    if expr.index in free_vars(tensor_prod_expr.operands[i]):
                        if idx_beg == -1:
                            idx_beg = i
                            idx_end = idx_beg
                        else:
                            idx_end = i
            else:
                # The alternative is that the summand is
                # a ScalarMult with the scalar (but not the scaled)
                # being dependent on the index of summation. It's not
                # obvious what's best to do in this case, but we set
                # things up to factor out all but the last of the
                # TensorProd factors (so we'll factor out at least
                # 1 factor)
                idx_beg = len(tensor_prod_expr.operands.entries) - 1
                idx_end = idx_beg


        # Check that the provided idxs are within bounds
        # (it should refer to an actual TensorProd operand)

        num_vec_factors = len(tensor_prod_factors_list)
        if idx is not None and idx >= num_vec_factors:
            raise ValueError(
                    "idx value {0} provided for tensor_prod_factoring() "
                    "method is out-of-bounds; the TensorProd summand has "
                    "{1} factors: {2}, and thus possibly indices 0-{3}".
                    format(idx, len(tensor_prod_factors_list),
                           tensor_prod_factors_list,
                           len(tensor_prod_factors_list)-1))
        if idx_beg is not None and idx_end is not None:
            if (idx_end < idx_beg or idx_beg >= num_vec_factors or
                idx_end >= num_vec_factors):
                raise ValueError(
                    "idx_beg value {0} or idx_end value {1} (or both) "
                    "provided for tensor_prod_factoring() "
                    "method is/are out-of-bounds; the TensorProd summand "
                    "has {2} factors: {3}, and thus possibly indices 0-{3}".
                    format(idx_beg, idx_end, num_vec_factors,
                           tensor_prod_factors_list,num_vec_factors-1))
        if idx is not None:
            # take single idx as the default
            idx_beg = idx
            idx_end = idx

        # Check that the TensorProd factors to be factored out do not
        # rely on the VecSum index of summation
        summation_index = expr.index
        for i in range(num_vec_factors):
            if i < idx_beg or i > idx_end:
                the_factor = tensor_prod_factors_list[i]
                if summation_index in free_vars(the_factor):
                    raise ValueError(
                            "TensorProd factor {0} cannot be factored "
                            "out of the given VecSum summation because "
                            "it is a function of the summation index {1}.".
                            format(the_factor, summation_index))
        
        # Everything checks out as best we can tell, so prepare to
        # import and instantiate the appropriate theorem,
        # depending on whether:
        # (1) the_summand is a TensorProd, or
        # (2) the_summand is a ScalarMult (with a TensorProd 'scaled')
        if tensor_prod_summand:
            from proveit.linear_algebra.tensors import (
                tensor_prod_distribution_over_summation)
        else:
            from proveit.linear_algebra.tensors import (
                tensor_prod_distribution_over_summation_with_scalar_mult)
        if idx_beg != idx_end:
            # need to associate the elements and change idx value
            # but process is slightly different in the two cases
            if tensor_prod_summand:
                expr = eq.update(expr.inner_expr().summand.association(
                        idx_beg, idx_end-idx_beg+1))
                tensor_prod_expr = expr.summand
            else:
                expr = eq.update(expr.inner_expr().summand.scaled.association(
                        idx_beg, idx_end-idx_beg+1))
                tensor_prod_expr = expr.summand.scaled
        idx = idx_beg

        from proveit import K, f, Q, i, j, k, V, a, b, c, s
        # actually, maybe it doesn't matter and we can deduce the 
        # vector space regardless: (Adding this temp 12/26/21)
        vec_space_membership = expr.summand.deduce_in_vec_space(
            field=field,
            assumptions = defaults.assumptions + expr.conditions.entries)
        _V_sub = vec_space_membership.domain
        # Substitutions regardless of Case
        _K_sub = VecSpaces.known_field(_V_sub)
        _b_sub = expr.indices
        _j_sub = _b_sub.num_elements()
        _Q_sub = Lambda(expr.indices, expr.condition)
        # Case-specific substitutions, using updated tensor_prod_expr:
        _a_sub = tensor_prod_expr.operands[:idx]
        _c_sub = tensor_prod_expr.operands[idx+1:]
        _f_sub = Lambda(expr.indices, tensor_prod_expr.operands[idx])
        if not tensor_prod_summand:
            _s_sub = Lambda(expr.indices, expr.summand.scalar)
        # Case-dependent substitutions:
        _i_sub = _a_sub.num_elements()
        _k_sub = _c_sub.num_elements()

        if tensor_prod_summand:
            impl = tensor_prod_distribution_over_summation.instantiate(
                    {K:_K_sub, f:_f_sub, Q:_Q_sub, i:_i_sub, j:_j_sub,
                     k:_k_sub, V:_V_sub, a:_a_sub, b:_b_sub, c:_c_sub},
                     preserve_expr=expr)
        else:
            impl = (tensor_prod_distribution_over_summation_with_scalar_mult.
                   instantiate(
                    {K:_K_sub, f:_f_sub, Q:_Q_sub, i:_i_sub, j:_j_sub,
                     k:_k_sub, V:_V_sub, a:_a_sub, b:_b_sub, c:_c_sub,
                     s: _s_sub}, preserve_expr=expr))

        expr = eq.update(impl.derive_consequent(
                assumptions = defaults.assumptions + expr.conditions.entries).
                derive_reversed())

        return eq.relation
Beispiel #29
0
    def digit_repetition_reduction(self, **defaults_config):
        '''
        Tries to reduce a decimal sequence containing an ExprRange.
        For example, reduce #(3 4 2 .. 4 repeats .. 2 3) to 3422223
        '''
        from proveit import TransRelUpdater
        from proveit.core_expr_types.tuples import n_repeats_reduction
        from proveit.numbers.numerals.decimals import deci_sequence_reduction_ER

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

        idx = 0
        for i, digit in enumerate(self.digits):
            # i is the current index in the original expression
            # idx is the current index in the transformed expression (eq.relation)
            if (isinstance(digit, ExprRange)
                    and isinstance(digit.body, Numeral)):
                import proveit.numbers.numerals.decimals

                # _m = expr.digits[:i].num_elements(assumptions)
                # _n = digit.true_end_index
                # _k = expr.digits[i + 1:].num_elements(assumptions)
                # _a = expr.digits[:i]
                # _b = digit.body
                # _d = expr.digits[i + 1:]

                _m = eq.relation.rhs.digits[:idx].num_elements()
                _n = digit.true_end_index
                _k = expr.digits[i + 1:].num_elements()
                _a = eq.relation.rhs.digits[:idx]
                _b = digit.body
                _d = expr.digits[i + 1:]

                # if digit.true_end_index.as_int() >= 10:
                # Automatically reduce an Expression range of
                # a single numeral to an Expression tuple
                # (3 .. 4 repeats.. 3) = 3333
                # #(2 3 4 (5 ..3 repeats.. 5) 6 7 8) = 234555678

                while _n.as_int() > 9:
                    _x = digit.body

                    _c = n_repeats_reduction.instantiate({
                        n: _n,
                        x: _x
                    },
                                                         preserve_all=True).rhs

                    eq.update(
                        deci_sequence_reduction_ER.instantiate(
                            {
                                m: _m,
                                n: _n,
                                k: _k,
                                a: _a,
                                b: _b,
                                c: _c,
                                d: _d
                            },
                            preserve_all=True))
                    _n = num(_n.as_int() - 1)
                    idx += 1

                #_n = digit.true_end_index
                len_thm = proveit.numbers.numerals.decimals \
                    .__getattr__('reduce_%s_repeats' % _n)
                _x = digit.body

                _c = len_thm.instantiate({x: _x}).rhs

                idx += _n.as_int()

                eq.update(
                    deci_sequence_reduction_ER.instantiate(
                        {
                            m: _m,
                            n: _n,
                            k: _k,
                            a: _a,
                            b: _b,
                            c: _c,
                            d: _d
                        },
                        preserve_all=True))
            else:
                idx += 1

        return eq.relation
Beispiel #30
0
    def cancelation(self, term_to_cancel, **defaults_config):
        '''
        Deduce and return an equality between self and a form in which
        the given operand has been canceled on the numerator and
        denominator.  For example,
        [(a*b)/(b*c)].cancelation(b) would return
        (a*b)/(b*c) = a / c.
        Assumptions or previous work might be required to establish
        that the term_to_cancel is non-zero.
        '''
        from proveit.numbers import Mult, one
        expr = self
        eq = TransRelUpdater(expr)

        if self.numerator == self.denominator == term_to_cancel:
            # x/x = 1
            from . import frac_cancel_complete
            return frac_cancel_complete.instantiate({x: term_to_cancel})

        if term_to_cancel != self.numerator:
            # try to catch Exp objects here as well?
            # after all, Exp(term_to_cancel, n) has factors!
            if (not isinstance(self.numerator, Mult)
                    or term_to_cancel not in self.numerator.operands):
                raise ValueError("%s not in the numerator of %s" %
                                 (term_to_cancel, self))
            # Factor the term_to_cancel from the numerator to the left.
            expr = eq.update(expr.inner_expr().numerator.factorization(
                term_to_cancel,
                group_factors=True,
                group_remainder=True,
                preserve_all=True))
        if term_to_cancel != self.denominator:
            if (not isinstance(self.denominator, Mult)
                    or term_to_cancel not in self.denominator.operands):
                raise ValueError("%s not in the denominator of %s" %
                                 (term_to_cancel, self))
            # Factor the term_to_cancel from the denominator to the left.
            expr = eq.update(expr.inner_expr().denominator.factorization(
                term_to_cancel,
                group_factors=True,
                group_remainder=True,
                preserve_all=True))
        if expr.numerator == expr.denominator == term_to_cancel:
            # Perhaps it reduced to the trivial x/x = 1 case via
            # auto-simplification.
            expr = eq.update(expr.cancelation(term_to_cancel))
            return eq.relation
        else:
            # (x*y) / (x*z) = y/z with possible automatic reductions
            # via 1 eliminations.
            from . import frac_cancel_left
            replacements = list(defaults.replacements)
            if expr.numerator == term_to_cancel:
                numer_prod = Mult(term_to_cancel, one)
                _y = one
                replacements.append(
                    numer_prod.one_elimination(1,
                                               preserve_expr=term_to_cancel))
            else:
                _y = expr.numerator.operands[1]
            if expr.denominator == term_to_cancel:
                denom_prod = Mult(term_to_cancel, one)
                _z = one
                replacements.append(
                    denom_prod.one_elimination(1,
                                               preserve_expr=term_to_cancel))
            else:
                _z = expr.denominator.operands[1]
            expr = eq.update(
                frac_cancel_left.instantiate({
                    x: term_to_cancel,
                    y: _y,
                    z: _z
                },
                                             replacements=replacements,
                                             preserve_expr=expr))
            return eq.relation