Exemple #1
0
 def deduce_in_number_set(self, number_set, assumptions=USE_DEFAULTS):
     from . import (summation_nat_closure, summation_int_closure,
                    summation_real_closure, summation_complex_closure)
     _x = self.instance_param
     P_op, _P_op = Function(P, _x), self.instance_expr
     Q_op, _Q_op = Function(Q, _x), self.condition
     self.summand
     if number_set == Natural:
         thm = summation_nat_closure
     elif number_set == Integer:
         thm = summation_int_closure
     elif number_set == Real:
         thm = summation_real_closure
     elif number_set == Complex:
         thm = summation_complex_closure
     else:
         raise ProofFailure(
             InSet(self, number_set), assumptions,
             ("'deduce_in_number_set' not implemented for the %s set" %
              str(number_set)))
     impl = thm.instantiate({
         x: _x,
         P_op: _P_op,
         Q_op: _Q_op
     },
                            assumptions=assumptions)
     return impl.derive_consequent(assumptions=assumptions)
Exemple #2
0
 def conclude_via_domain_inclusion(self,
                                   subset_domain,
                                   assumptions=USE_DEFAULTS):
     '''
     Conclude this exists statement from a similar exists statement
     over a narrower domain.  For example, conclude
     exists_{x in B} P(x) from exists_{x in A} P(x)
     given A subset_eq B.
     '''
     from proveit.logic.sets.inclusion import (
         inclusive_existential_quantification)
     if not (self.has_domain() and self.instance_params.is_single
             and self.conditions.is_single()):
         raise ValueError("May only call conclude_via_domain_inclusion "
                          "on a Forall expression with a single instance "
                          "variable over a domain and no other conditions.")
     _x = self.instance_param
     P_op, _P_op = Function(P, _x), self.instance_expr
     return inclusive_existential_quantification.instantiate(
         {
             x: _x,
             P_op: _P_op,
             A: subset_domain,
             B: self.domain
         },
         assumptions=assumptions).derive_consequent(assumptions)
Exemple #3
0
 def shallow_simplification(self, *, must_evaluate=False,
                            **defaults_config):
     '''
     Returns a proven simplification equation for this VecSum
     expression assuming the operands have been simplified.
     For the trivial case of summing over only one item (currently
     implemented just for a Interval where the endpoints are equal),
     derive and return this vector summation expression equated with
     the simplified form of the single term.
     Also reduce the VecSum to a number Sum if applicable.
     '''
     from proveit.numbers import Complex
     from . import vec_sum_single
     if (isinstance(self.domain,Interval) and
             self.domain.lower_bound == self.domain.upper_bound):
         # Reduce singular summation.
         if hasattr(self, 'index'):
             return vec_sum_single.instantiate(
                 {Function(v, self.index): self.summand,
                  a: self.domain.lower_bound})
     inner_assumptions = defaults.assumptions + self.conditions.entries
     if hasattr(self.summand, 'deduce_in_number_set'):
         # Maybe we can reduce the VecSum to a number Sum.
         self.summand.deduce_in_number_set(
                 Complex, assumptions=inner_assumptions)
     if InSet(self.summand, Complex).proven(assumptions=inner_assumptions):
         # If the operands are all complex numbers, this
         # VecAdd will reduce to number Add.
         return self.number_sum_reduction()
     return GroupSum.shallow_simplification(
             self, must_evaluate=must_evaluate)
Exemple #4
0
 def deduce_in_bool(self, assumptions=USE_DEFAULTS):
     '''
     Deduce, then return, that this exists expression is in the set of BOOLEANS as
     all exists expressions are (they are taken to be false when not true).
     '''
     raise NotImplementedError("Need to update")
     from . import exists_is_bool
     P_op, P_op_sub = Function(P, self.instance_vars), self.instance_expr
     Q_op, Q_op_sub = Function(Qmulti, self.instance_vars), self.conditions
     return exists_is_bool.instantiate(
         {
             P_op: P_op_sub,
             Q_op: Q_op_sub,
             S: self.domain
         },
         relabel_map={x_multi: self.instance_vars},
         assumptions=assumptions)
Exemple #5
0
    def partitioning(self, split_index, side='after', **defaults_config):
        r'''
        Partition or split a vector summation over one integral
        Interval {a ... c} into two vector summations and return the
        equality between the original and the VecAdd sum of the two
        new vector summations.
        If side == 'after', it splits into a vector summation over
        {a ... split_index} plus a vector summation over
        {split_index+1 ... c}.
        If side == 'before', it splits into a vector summation over
        {a ... split_index-1} plus a vector summation over
        {split_index ... c},
        deducing and returning the equivalence of the original vector
        summation with the split version. When the simplify_idx is True,
        a shallow simplification is applied to the new indices (for
        example, a new index of i = 4 + 1 may be expressed as i = 5).
        Eventually plan to accept and act on user-supplied reductions
        as well, but not implemented at this time.
        This partitioning() method is implemented only for a VecSum with
        a single index and only when the domain is an integer Interval.
        Eventually this should also be implemented for domains of
        Natural, NaturalPos, etc.
        As special cases, split_index==a with side == 'after' splits
        off the first single term.  Also, split_index==c with
        side == 'before' splits off the last single term.
        Example usage: Let S = VecSum(i, Vec(i+2), Interval(0, 10)).
        Then S.partition(four, side='after') will return
        |- S = VecSum(i, Vec(i+2), Interval(0, 4)) +
               VecSum(i, i+2, Interval(5, 10))
        '''
        # The following constraint can eventually be modified to allow
        # a domain like Natural or NaturalPos, but for now limited
        # to integer Interval domain.
        if (not isinstance(self.domain, Interval) or
                not hasattr(self, 'index')):
            raise NotImplementedError(
                "VecSum.partition() only implemented for summations with "
                "a single index over an integer Interval. The sum {} has "
                "indices {} and domain {}."
                .format(self, self.indices, self.domain))

        # Special cases: splitting off last or first item
        if side == 'before' and self.domain.upper_bound == split_index:
            return self.partitioning_last()
        if side == 'after' and self.domain.lower_bound == split_index:
            return self.partitioning_first()

        _i_sub = self.index
        _a_sub = self.domain.lower_bound
        _b_sub = split_index
        _c_sub = self.domain.upper_bound
        _v_op, _v_op_sub = Function(v, self.index), self.summand

        from . import vec_sum_split_after, vec_sum_split_before
        sum_split = (
            vec_sum_split_after if side == 'after' else vec_sum_split_before)
        return sum_split.instantiate(
                {_v_op: _v_op_sub, a: _a_sub, b: _b_sub, c: _c_sub, i: _i_sub})
Exemple #6
0
 def elim_domain(self, assumptions=USE_DEFAULTS):
     '''
     From [exists_{x in S | Q(x)} P(x)], derive and return [exists_{x | Q(x)} P(x)],
     eliminating the domain which is a weaker form.
     '''
     raise NotImplementedError("Need to update")
     from . import exists_in_general
     P_op, P_op_sub = Function(P, self.instance_vars), self.instance_expr
     Q_op, Q_op_sub = Function(Qmulti, self.instance_vars), self.conditions
     return exists_in_general.instantiate(
         {
             P_op: P_op_sub,
             Q_op: Q_op_sub,
             S: self.domain
         },
         assumptions=assumptions,
         relabel_map={
             x_multi: self.instance_vars,
             y_multi: self.instance_vars
         }).derive_consequent(assumptions)
Exemple #7
0
 def prove_by_cases(self, forall_stmt, **defaults_config):
     '''
     Given forall_{A in Boolean} P(A), conclude and return it from
     [P(TRUE) and P(FALSE)].
     '''
     from proveit.logic import Forall, And
     from . import forall_over_bool_by_cases, conditioned_forall_over_bool_by_cases
     from . import Boolean
     assert(isinstance(forall_stmt, Forall)), (
             "May only apply prove_by_cases method of Boolean to a "
             "forall statement")
     assert(forall_stmt.domain == Boolean), (
             "May only apply prove_by_cases method of Boolean "
             "to a forall statement with the Boolean domain")
     if forall_stmt.conditions.num_entries() > 1:
         if forall_stmt.conditions.is_double():
             condition = forall_stmt.conditions[1]
         else:
             condition = And(*forall_stmt.conditions[1:].entries)
         Qx = Function(Q, forall_stmt.instance_param)
         _Qx = condition
         Px = Function(P, forall_stmt.instance_param)
         _Px = forall_stmt.instance_expr
         _A = forall_stmt.instance_param
         # We may need to auto-simplify in order to flatten the
         # conditions (if there are multiple conditions beyond the
         # domain condition), but we must preserve the different
         # parts.
         preserved_exprs = {forall_stmt, forall_stmt.instance_expr}
         preserved_exprs.update(forall_stmt.conditions)    
         return conditioned_forall_over_bool_by_cases.instantiate(
                 {Qx: _Qx, Px: _Px, A: _A}, num_forall_eliminations=1, 
                 preserved_exprs=preserved_exprs, auto_simplify=True)
     else:
         # forall_{A in Boolean} P(A), assuming P(TRUE) and P(FALSE)
         Px = Function(P, forall_stmt.instance_param)
         _Px = forall_stmt.instance_expr
         _A = forall_stmt.instance_param
         return forall_over_bool_by_cases.instantiate(
             {Px: _Px, A: _A}, num_forall_eliminations=1,
             preserve_expr=forall_stmt)
Exemple #8
0
 def conclude_via_example(self, example_instance, assumptions=USE_DEFAULTS):
     '''
     Conclude and return this [exists_{..y.. in S | ..Q(..x..)..} P(..y..)] from P(..x..) and Q(..x..) and ..x.. in S, where ..x.. is the given example_instance.
     '''
     raise NotImplementedError("Need to update")
     from . import existence_by_example
     from proveit.logic import InSet
     if self.instance_vars.num_entries() > 1 and (
             not isinstance(example_instance, ExprTuple) or
         (example_instance.num_entries() !=
          self.instance_vars.num_entries())):
         raise Exception(
             'Number in example_instance list must match number of instance variables in the Exists expression'
         )
     P_op, P_op_sub = Function(P, self.instance_vars), self.instance_expr
     Q_op, Q_op_sub = Function(Qmulti, self.instance_vars), self.conditions
     # P(..x..) where ..x.. is the given example_instance
     example_mapping = {
         instance_var: example_instance_elem
         for instance_var, example_instance_elem in zip(
             self.instance_vars, example_instance if isinstance(
                 example_instance, ExpressionList) else [example_instance])
     }
     example_expr = self.instance_expr.substituted(example_mapping)
     # ..Q(..x..).. where ..x.. is the given example_instance
     example_conditions = self.conditions.substituted(example_mapping)
     if self.has_domain():
         for i_var in self.instance_vars:
             example_conditions.append(InSet(i_var, self.domain))
     # exists_{..y.. | ..Q(..x..)..} P(..y..)]
     return existence_by_example.instantiate(
         {
             P_op: P_op_sub,
             Q_op: Q_op_sub,
             S: self.domain
         },
         assumptions=assumptions,
         relabel_map={
             x_multi: example_instance,
             y_multi: self.instance_vars
         }).derive_consequent(assumptions=assumptions)
Exemple #9
0
 def substitute_domain(self, superset, assumptions=USE_DEFAULTS):
     '''
     Substitute the domain with a superset.
     From [exists_{x in A| Q(x)} P(x)], derive and return [exists_{x in B| Q(x)} P(x)]
     given A subseteq B.
     '''
     raise NotImplementedError("Need to update")
     from . import exists_in_superset
     P_op, P_op_sub = Function(P, self.instance_vars), self.instance_expr
     Q_op, Q_op_sub = Function(Qmulti, self.instance_vars), self.conditions
     return exists_in_superset.instantiate(
         {
             P_op: P_op_sub,
             Q_op: Q_op_sub,
             A: self.domain,
             B: superset
         },
         assumptions=assumptions,
         relabel_map={
             x_multi: self.instance_vars,
             y_multi: self.instance_vars
         }).derive_consequent(assumptions)
Exemple #10
0
 def derive_negated_forall(self, assumptions=USE_DEFAULTS):
     '''
     From [exists_{x | Q(x)} Not(P(x))], derive and return Not(forall_{x | Q(x)} P(x)).
     From [exists_{x | Q(x)} P(x)], derive and return Not(forall_{x | Q(x)} (P(x) != TRUE)).
     '''
     raise NotImplementedError("Need to update")
     from . import exists_def
     from . import exists_not_implies_not_forall
     from proveit.logic import Not
     Q_op, Q_op_sub = Function(Qmulti, self.instance_vars), self.conditions
     if isinstance(self.instance_expr, Not):
         P_op, P_op_sub = Function(
             P, self.instance_vars), self.instance_expr.operand
         return exists_not_implies_not_forall.instantiate(
             {
                 P_op: P_op_sub,
                 Q_op: Q_op_sub,
                 S: self.domain
             },
             assumptions=assumptions,
             relabel_map={
                 x_multi: self.instance_vars
             }).derive_consequent(assumptions)
     else:
         P_op, P_op_sub = Function(P,
                                   self.instance_vars), self.instance_expr
         return exists_def.instantiate(
             {
                 P_op: P_op_sub,
                 Q_op: Q_op_sub,
                 S: self.domain
             },
             assumptions=assumptions,
             relabel_map={
                 x_multi: self.instance_vars
             }).derive_right_via_equality(assumptions)
Exemple #11
0
 def unfold_forall(self, forall_stmt, **defaults_config):
     '''
     Given forall_{A in Boolean} P(A), derive and return [P(TRUE) and P(FALSE)].
     '''
     from proveit.logic import Forall
     from . import unfold_forall_over_bool
     from . import Boolean
     assert(isinstance(forall_stmt, Forall)
            ), "May only apply unfold_forall method of Boolean to a forall statement"
     assert(forall_stmt.domain ==
            Boolean), "May only apply unfold_forall method of Boolean to a forall statement with the Boolean domain"
     Px = Function(P, forall_stmt.instance_var)
     _Px = forall_stmt.instance_expr
     _A = forall_stmt.instance_var
     return unfold_forall_over_bool.instantiate(
         {Px: _Px, A: _A}).derive_consequent()
Exemple #12
0
    def shifting(self, shift_amount, **defaults_config):
        '''
        Shift the summation indices by the shift_amount, and shift
        the summand by a corresponding compensating amount, deducing
        and returning the equivalence of this summation with the
        index-shifted version.
        This shift() method is implemented only for a VecSum with a
        single index and only when the domain is an integer Interval.
        Eventually this should also be implemented for domains of
        Natural, NaturalPos, etc.
        Example: Let S = VecSum(i, Vec(k), Interval(0, 10)).
        Then S.shift(one) will return the equality
        |- S = VecSum(i, Vec(k-1), Interval(1, 11)),
        where we are using Vec(i) to denote some vector as a function
        of the index i (it might for example be a Ket(i) or similar
        object).
        '''
        if not hasattr(self, 'index'):
            raise NotImplementedError(
                "VecSum.shifting() only implemented for vector summations "
                "with a single index over an Integer Interval. The sum {0} "
                "has indices {1}.".format(self, self.indices))
        # The following constraint can eventually be modified to deal
        # with a domain like all Natural … but for now limited to
        # integer Interval domain.
        if not isinstance(self.domain, Interval):
            raise NotImplementedError(
                "VecSum.shifting() only implemented for vector summations "
                "with a single index over an Integer Interval. The sum {0} "
                "has domain {1}."
                .format(self, self.domain))

        from . import vec_sum_index_shift

        _v, _a, _b, _c = vec_sum_index_shift.all_instance_params()
        _i = vec_sum_index_shift.instance_expr.instance_expr.lhs.index

        _i_sub = self.index
        _a_sub = self.domain.lower_bound
        _b_sub = self.domain.upper_bound
        _c_sub = shift_amount

        _v_op, _v_op_sub = Function(_v, self.index), self.summand

        return vec_sum_index_shift.instantiate(
            {_v_op: _v_op_sub, _i: _i_sub, _a: _a_sub, _b: _b_sub, _c: _c_sub})
Exemple #13
0
 def deduce_in_bool(self, assumptions=USE_DEFAULTS):
     '''
     Attempt to deduce, then return, that this forall expression
     is in the set of BOOLEANS, as all forall expressions are
     (they are taken to be false when not true).
     '''
     from proveit.numbers import one
     from . import forall_in_bool
     _x = self.instance_params
     P_op, _P_op = Function(P, _x), self.instance_expr
     _n = _x.num_elements(assumptions)
     x_1_to_n = ExprTuple(ExprRange(k, IndexedVar(x, k), one, _n))
     return forall_in_bool.instantiate({
         n: _n,
         P_op: _P_op,
         x_1_to_n: _x
     },
                                       assumptions=assumptions)
Exemple #14
0
    def partitioning_last(self, **defaults_config):
        '''
        Split a vector summation over an integral Interval {a ... c}
        into a vector sum of: a new summation over the integral
        Interval {a ... (c-1)} and the final term evaluated at the
        upper bound, deducing and returning the equivalence of the
        original vector summation with the new split version.
        The default uses auto_simplify=True to apply a shallow
        simplification to the new indices (for example,
        a new index of i = 4 + 1 may be expressed as i = 5) and to the
        upper term that has been peeled off by itself.
        Eventually plan to accept and act on user-supplied reductions
        as well, but not implemented at this time.
        This partitioning_last() method is implemented only for a VecSum
        with a single index and only when the domain is an integer
        Interval. Eventually this should also be implemented for
        domains of Natural, NaturalPos, etc.
        VecSum.partitioning_last() is called from VecSum.partitioning()
        for special cases.
        Example usage: Let S = VecSum(i, Vec(i+2), Interval(0, 10)).
        Then S.partitioning_last() will return the equality judgment
        |- S = VecSum(i, i+2, Interval(0, 9)) + Vec(12)
        '''
        if isinstance(self.domain, Interval) and hasattr(self, 'index'):

            from . import vec_sum_split_last

            _v, _a, _b = vec_sum_split_last.all_instance_params()
            _i = vec_sum_split_last.instance_expr.instance_expr.lhs.index

            _i_sub = self.index
            _a_sub = self.domain.lower_bound
            _b_sub = self.domain.upper_bound
            _v_op, _v_op_sub = Function(_v, self.index), self.summand

            return vec_sum_split_last.instantiate(
                {_v_op: _v_op_sub, _a: _a_sub, _b: _b_sub, _i: _i_sub})

        raise UnsatisfiedPrerequisites(
                "VecSum.partitioning_last() only implemented for vector"
                "summations with a single index over an integer Interval. "
                "The VecSum {0} has index or indices {1} and domain {2}."
                .format(self, self.indices, self.domain))
Exemple #15
0
    def shallow_simplification(self,
                               *,
                               must_evaluate=False,
                               **defaults_config):
        '''
        Returns a proven simplification equation for this Sum
        expression assuming the operands have been simplified.

        For the trivial case of summing over only one item (currently
        implemented just for a Interval where the endpoints are equal),
        derive and return this summation expression equated with the
        simplified form of the single term.
        Assumptions may be necessary to deduce necessary conditions
        for the simplification.
        NEEDS UPDATING
        '''
        from proveit.logic import TRUE, SimplificationError
        from . import sum_single, trivial_sum
        if (isinstance(self.domain, Interval)
                and self.domain.lower_bound == self.domain.upper_bound):
            if hasattr(self, 'index'):
                return sum_single.instantiate({
                    Function(f, self.index): self.summand,
                    a: self.domain.lower_bound
                })
        if (isinstance(self.domain, Interval)
                and self.instance_param not in free_vars(self.summand)
                and self.non_domain_condition() == TRUE):
            # Trivial sum: summand independent of parameter.
            _a = self.domain.lower_bound
            _b = self.domain.upper_bound
            _x = self.summand
            return trivial_sum.instantiate({a: _a, b: _b, x: _x})
        raise SimplificationError(
            "Sum simplification only implemented for a summation over an "
            "integer Interval of one instance variable where the upper "
            "and lower bounds are the same.")
Exemple #16
0
 def forall_evaluation(self, forall_stmt, **defaults_config):
     '''
     Given a forall statement over the BOOLEAN domain, evaluate to 
     TRUE or FALSE if possible.
     '''
     from proveit.logic import Forall, Equals, SimplificationError
     from . import false_eq_false, true_eq_true
     from . import forall_bool_eval_true, forall_bool_eval_false_via_t_f, \
         forall_bool_eval_false_via_f_f, forall_bool_eval_false_via_f_t
     from . import TRUE, FALSE, Boolean
     from .conjunction import compose
     assert(isinstance(forall_stmt, Forall)
            ), "May only apply forall_evaluation method of BOOLEAN to a forall statement"
     assert(forall_stmt.domain ==
            Boolean), "May only apply forall_evaluation method of BOOLEAN to a forall " \
                      "statement with the BOOLEAN domain"
     with defaults.temporary() as temp_defaults:
         temp_defaults.preserved_exprs = defaults.preserved_exprs.union([forall_stmt.inner_expr])
         instance_list = list(forall_stmt.instance_param_lists())
         instance_var = instance_list[0][0]
         instance_expr = forall_stmt.instance_expr
         P_op = Function(P, instance_var)
         true_instance = instance_expr.basic_replaced(
                 {instance_var: TRUE})
         false_instance = instance_expr.basic_replaced(
                 {instance_var: FALSE})
         temp_defaults.auto_simplify = False
         if true_instance == TRUE and false_instance == FALSE:
             # special case of Forall_{A in BOOLEAN} A
             false_eq_false  # FALSE = FALSE
             true_eq_true  # TRUE = TRUE
             return forall_bool_eval_false_via_t_f.instantiate(
                 {P_op: instance_expr}).derive_conclusion()
         else:
             # must evaluate for the TRUE and FALSE case separately
             eval_true_instance = true_instance.evaluation()
             eval_false_instance = false_instance.evaluation()
             if not isinstance(
                     eval_true_instance.expr,
                     Equals) or not isinstance(
                     eval_false_instance.expr,
                     Equals):
                 raise SimplificationError(
                     'Quantified instances must produce equalities as '
                     'evaluations')
             # proper evaluations for both cases (TRUE and FALSE)
             true_case_val = eval_true_instance.rhs
             false_case_val = eval_false_instance.rhs
             if true_case_val == TRUE and false_case_val == TRUE:
                 # both cases are TRUE, so the forall over the
                 # boolean set is TRUE
                 compose([eval_true_instance.derive_via_boolean_equality(), 
                          eval_false_instance.derive_via_boolean_equality()])
                 return forall_bool_eval_true.instantiate(
                         {P_op: instance_expr, A: instance_var})
             else:
                 # one case is FALSE, so the forall over the boolean set is
                 # FALSE
                 compose([eval_true_instance, eval_false_instance])
                 if true_case_val == FALSE and false_case_val == FALSE:
                     impl = forall_bool_eval_false_via_f_f.instantiate(
                         {P_op: instance_expr, A: instance_var})
                 elif true_case_val == FALSE and false_case_val == TRUE:
                     impl = forall_bool_eval_false_via_f_t.instantiate(
                         {P_op: instance_expr, A: instance_var})
                 elif true_case_val == TRUE and false_case_val == FALSE:
                     impl = forall_bool_eval_false_via_t_f.instantiate(
                         {P_op: instance_expr, A: instance_var})
                 else:
                     raise SimplificationError(
                         'Quantified instance evaluations must be TRUE or FALSE')
                 return impl.derive_conclusion()
Exemple #17
0
    def joining(self, second_summation, **defaults_config):
        '''
        Join the "second summation" with "this" (self) summation,
        deducing and returning the equivalence of the sum of the self
        and second_summation with the joined summation.
        Both summations must be over integer Intervals.
        The relation between the first summation upper bound, UB1,
        and the second summation lower bound, LB2, must be *explicitly*
        either UB1 = LB2-1 or LB2=UB1+1 *or* easily-derivable
        mathematical equivalents of those equalities.
        Example usage: let S1 = Sum(i, i^2, Interval(1,10)) and
        S2 = Sum(i, i^2, Interval(1,10)). Then S1.join(S2) returns
        |- S1 + S2 = Sum(i, i^2, Interval(1,20))
        '''

        if (not isinstance(self.domain, Interval)
                or not isinstance(second_summation.domain, Interval)):
            raise NotImplementedError(
                "Sum.join() is only implemented for summations with a "
                "single index over an integer Interval. The sum {0} has "
                "indices {1} and domain {2}; the sum {3} has indices "
                "{4} and domain {5}.".format(self, self.indices, self.domain,
                                             second_summation,
                                             second_summation.indices,
                                             second_summation.domain))

        if self.summand != second_summation.summand:
            raise ValueError(
                "Sum joining using Sum.join() is only allowed when the "
                "summands are identical. The sum {0} has summand {1} "
                "while the sum {2} has summand {3}. If the summands are "
                "equal but do not appear identical, you will have to "
                "establish an appropriate substituion before calling the "
                "Sum.join() method.".format(self, self.summand,
                                            second_summation,
                                            second_summation.summand))

        from . import sum_split_after, sum_split_before
        from proveit import a

        _i = self.index
        _a1 = self.domain.lower_bound
        _b1 = self.domain.upper_bound
        _a2 = second_summation.domain.lower_bound
        _b2 = second_summation.domain.upper_bound
        f_op, f_op_sub = Function(f, self.index), self.summand

        # Create low-effort, simplified versions of transition index
        # values, if possible
        _b1_plus_1_simplified = Add(_b1, one).shallow_simplified()
        _a2_minus_1_simplified = subtract(_a2, one).shallow_simplified()

        # This breaks into four cases (despite the temptation to
        # combine some of the cases):
        if (_b1 == subtract(_a2, one)):
            # UB1 == LB2 - 1 (literally)
            sum_split = sum_split_before
            split_index = _a2
        elif (_a2 == Add(_b1, one)):
            # LB2 == UB1 + 1 (literally)
            sum_split = sum_split_after
            split_index = _b1
        elif (_b1 == _a2_minus_1_simplified):
            # UB1 == LB2 - 1 (after simplification)
            sum_split = sum_split_before
            split_index = _a2
        elif (_a2 == _b1_plus_1_simplified):
            # LB2 == UB1 + 1 (after simplification)
            sum_split = sum_split_after
            split_index = _b1
        else:
            raise UnsatisfiedPrerequisites(
                "Sum joining using Sum.join() only implemented for when "
                "there is an explicit (or easily verified) increment "
                "of one unit from the first summation's upper bound "
                "to the second summation's lower bound (or decrement "
                "of one unit from second summation's lower bound to "
                "first summation's upper bound). We have first "
                "summation upper bound of {0} with the second summation "
                "lower bound of {1}. If these appear to have the "
                "necessary relationship, you might need to prove this "
                "before calling the Sum.join() method.".format(_b1, _a2))

        # Preserve the original summations that will be on the
        # left side of the equation.
        preserved_exprs = set(defaults.preserved_exprs)
        preserved_exprs.add(self)
        preserved_exprs.add(second_summation)

        return sum_split.instantiate(
            {
                f_op: f_op_sub,
                a: _a1,
                b: split_index,
                c: _b2,
                x: _i
            },
            preserved_exprs=preserved_exprs).derive_reversed()
Exemple #18
0
    def substitute_instances(self, universality, assumptions=USE_DEFAULTS):
        '''
        Derive from this Exists operation, Exists_{..x.. in S | ..Q(..x..)..} P(..x..),
        one that substitutes instance expressions given some
        universality = forall_{..x.. in S | P(..x..), ..Q(..x..)..} R(..x..).
                                            or forall_{..x.. in S | ..Q(..x..)..} P(..x..) = R(..x..).
        Either is allowed in the theory of the existential quantifier.
        Derive and return the following type of existential operation assuming universality:
        Exists_{..x.. in S | ..Q(..x..)..} R(..x..)
        Works also when there is no domain S and/or no conditions ..Q...
        '''
        raise NotImplementedError("Need to update")
        from . import existential_implication, no_domain_existential_implication
        from proveit import Etcetera
        from proveit.logic import Forall
        from proveit._generic_ import InstanceSubstitutionException
        from proveit import n, Qmulti, x_multi, y_multi, z_multi, S
        if isinstance(universality, Judgment):
            universality = universality.expr
        if not isinstance(universality, Forall):
            raise InstanceSubstitutionException(
                "'universality' must be a forall expression", self,
                universality)

        if self.instance_expr in universality.conditions:
            # map from the forall instance variables to self's instance
            # variables
            i_var_substitutions = {
                forall_ivar: self_ivar
                for forall_ivar, self_ivar in zip(universality.instance_vars,
                                                  self.instance_vars)
            }
            first_condition = universality.conditions[0].substituted(
                i_var_substitutions)
            if first_condition != self.instance_expr:
                raise InstanceSubstitutionException(
                    "The first condition of the 'universality' must match the instance expression of the Exists operation having instances substituted",
                    self, universality)
            if (universality.instance_vars.num_entries() !=
                    self.instance_vars.num_entries()):
                raise InstanceSubstitutionException(
                    "'universality' must have the same number of variables as the Exists operation having instances substituted",
                    self, universality)
            if universality.domain != self.domain:
                raise InstanceSubstitutionException(
                    "'universality' must have the same domain as the Exists having instances substituted",
                    self, universality)
            if ExpressionList(universality.conditions[1:]).substituted(
                    i_var_substitutions) != self.conditions:
                raise InstanceSubstitutionException(
                    "'universality' must have the same conditions as the Exists operation having instances substituted, in addition to the Exists instance expression",
                    self, universality)
            P_op, P_op_sub = Function(P,
                                      self.instance_vars), self.instance_expr
            Q_op, Q_op_sub = Function(Qmulti,
                                      self.instance_vars), self.conditions
            R_op, R_op_sub = Function(
                R, self.instance_vars), universality.instance_expr.substituted(
                    i_var_substitutions)
            if self.has_domain():
                return existential_implication.instantiate(
                    {
                        S: self.domain,
                        P_op: P_op_sub,
                        Q_op: Q_op_sub,
                        R_op: R_op_sub
                    },
                    relabel_map={
                        x_multi: universality.instance_vars,
                        y_multi: self.instance_vars,
                        z_multi: self.instance_vars
                    },
                    assumptions=assumptions).derive_consequent(
                        assumptions).derive_consequent(assumptions)
            else:
                return no_domain_existential_implication.instantiate(
                    {
                        P_op: P_op_sub,
                        Q_op: Q_op_sub,
                        R_op: R_op_sub
                    },
                    relabel_map={
                        x_multi: universality.instance_vars,
                        y_multi: self.instance_vars,
                        z_multi: self.instance_vars
                    },
                    assumptions=assumptions).derive_consequent(
                        assumptions).derive_consequent(assumptions)
        # Default to the OperationOverInstances version which works with
        # universally quantified equivalences.
        return OperationOverInstances.substitute(self,
                                                 universality,
                                                 assumptions=assumptions)
Exemple #19
0
    def partition(self, split_index, side='after', **defaults_config):
        r'''
        Split summation over one integral Interval {a ... c} into two
        summations. If side == 'after', it splits into a summation over
        {a ... split_index} plus a summation over {split_index+1 ... c}.
        If side == 'before', it splits into a summation over
        {a ... split_index-1} plus a summation over {split_index ... c},
        deducing and returning the equivalence of this summation with
        the split version. When the simplify_idx is True, a shallow
        simplification is applied to the new indices (for example,
        a new index of i = 4 + 1 may be expressed as i = 5).
        Eventually plan to accept and act on user-supplied reductions
        as well, but not implemented at this time.
        This split() method is implemented only for a Sum with a single
        index and only when the domain is an integer Interval.
        Eventually this should also be implemented for domains of
        Natural, NaturalPos, etc.
        As special cases, split_index==a with side == 'after' splits
        off the first single term.  Also, split_index==c with
        side == 'before' splits off the last single term.
        Example usage: Let S = Sum(i, i+2, Interval(0, 10)). Then
        S.split(four, side='after') will return
        |- S = Sum(i, i+2, Interval(0, 4)) +
               Sum(i, i+2, Interval(5, 10))
        '''
        # The following constraint can eventually be modified to allow
        # a domain like Natural or NaturalPos, but for now limited
        # to integer Interval domain.
        if (not isinstance(self.domain, Interval)
                or not hasattr(self, 'index')):
            raise NotImplementedError(
                "Sum.partition() only implemented for summations with a single "
                "index over an integer Interval. The sum {} has indices {} "
                "and domain {}.".format(self, self.indices, self.domain))

        # Special cases: splitting off last or first item
        if side == 'before' and self.domain.upper_bound == split_index:
            return self.partition_last()
        if side == 'after' and self.domain.lower_bound == split_index:
            return self.partition_first()

        _i = self.index
        _a = self.domain.lower_bound
        _b = split_index
        _c = self.domain.upper_bound
        f_op, f_op_sub = Function(f, self.index), self.summand
        """
        # SHOULD BE HANDLED VIA AUTO-SIMPLIFICATION NOW.

        # Create a (possible) reduction formula for the split
        # components' index expression, which will then be passed
        # through to the instantiation as a "reduction" for simpifying
        # the final form of the indices. If the (supposed) reduction is
        # trivial (like |– x = x), the eventual instantiation process
        # will ignore/eliminate it.
        if (simplify_idx):
            # 2 Cases to consider: side = 'after' vs. side = 'before'
            if side == 'after':
                # simplify lower index expr for 2nd sum of split
                new_idx = Add(_b, one).simplification(
                        shallow=True, assumptions=assumptions)
            else:
                # simplify upper index expr for 1st sum of split
                new_idx = subtract(_b, one).simplification(
                        shallow=True, assumptions=assumptions)
            user_reductions = [*user_reductions, new_idx]
        """

        from . import sum_split_after, sum_split_before
        sum_split = sum_split_after if side == 'after' else sum_split_before
        return sum_split.instantiate({
            f_op: f_op_sub,
            a: _a,
            b: _b,
            c: _c,
            x: _i
        })
Exemple #20
0
    def partition_first(self, **defaults_config):
        '''
        Split a summation over an integral Interval {a ... c} into a
        sum of: the first term in the sum and a new summation over the
        integral Interval {a+1 ... c}, deducing and
        returning the equivalence of this summation with
        the new split version. When the simplify_idx is True, a shallow
        simplification is applied to the new indices (for example,
        a new index of i = 4 + 1 may be expressed as i = 5). When the
        simplify_summand = True, a shallow simplification is applied to
        the lower term that has been peeled off by itself.
        Eventually plan to accept and act on user-supplied reductions
        as well, but not implemented at this time.
        This split_off_last() method is implemented only for a Sum
        with a single index and only when the domain is an integer
        Interval. Eventually this should also be implemented for
        domains of Natural, NaturalPos, etc. split_off_last() is called
        from Sum.split() for special cases.
        Example usage: Let S = Sum(i, i+2, Interval(1, 10)). Then
        S.split_off_first() will return
        |- S = 3 + Sum(i, i+2, Interval(2, 10))
        '''

        if isinstance(self.domain, Interval) and hasattr(self, 'index'):

            from . import sum_split_first

            _i = self.index
            _a = self.domain.lower_bound
            _b = self.domain.upper_bound
            f_op, f_op_sub = Function(f, self.index), self.summand
            """
            # SHOULD BE HANDLED VIA AUTO-SIMPLIFICATION NOW.

            # Create (possible) reduction formulas for the lower
            # index expression of the resulting sum, and for the
            # resulting first term extracted from the sum, which will
            # then be passed through to the instantiation as reductions
            # for simpifying the final form of the indices and split-off
            # term. If any (supposed) reduction is trivial
            # (like |– x = x), the eventual instantiation process will
            # ignore/eliminate it.
            if simplify_idx:
                # Just 1 case to address:
                # simplify lower index of resulting remaining sum
                new_idx = Add(_a, one).simplification(
                            shallow=True, assumptions=assumptions)
                user_reductions = [*user_reductions, new_idx]
            if simplify_summand:
                # Simplify the summand for the first item
                new_summand = f_op_sub.basic_replaced({_i: _a})
                new_summand = new_summand.simplification(shallow=True,
                        assumptions=assumptions)
                user_reductions = [*user_reductions, new_summand]
            """

            return sum_split_first.instantiate({
                f_op: f_op_sub,
                a: _a,
                b: _b,
                x: _i
            })

        raise NotImplementedError(
            "Sum.split_off_first() only implemented for summations with a "
            "single index over an integer Interval. The sum {} has "
            "index or indices {} and domain {}.".format(
                self, self.indices, self.domain))
Exemple #21
0
    def range_expansion(self, **defaults_config):
        '''
        For self an ExprTuple with a single entry that is an ExprRange
        of the form f(i),...,f(j), where 0 <= (j-i) <= 9 (i.e. the
        ExprRange represents 1 to 10 elements), derive and
        return an equality between self and an ExprTuple with explicit
        entries replacing the ExprRange. For example, if
            self = ExprTuple(f(3),...,f(6)),
        then self.range_expansion() would return:
        |- ExprTuple(f(3),...,f(6)) = ExprTuple(f(3), f(4), f(5), f(6))
        '''

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

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

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

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

        _the_expr_range = self[0]

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

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

        # instantiate and return the identified expansion theorem
        _f, _i, _j = expansion_thm.instance_vars
        _safe_var = self.safe_dummy_var()
        _idx_param = _the_expr_range.parameter
        _fxn_sub = _the_expr_range.body.basic_replaced(
                {_idx_param: _safe_var})
        _i_sub = _the_expr_range.true_start_index
        _j_sub = _the_expr_range.true_end_index
        return expansion_thm.instantiate(
                {Function(_f, _safe_var): _fxn_sub, _i: _i_sub, _j: _j_sub})
Exemple #22
0
    def shifting(self, shift_amount, **defaults_config):
        '''
        Shift the summation indices by the shift_amount, and shift
        the summand by a corresponding compensating amount, deducing
        and returning the equivalence of this summation with the
        index-shifted version.
        This shift() method is implemented only for a Sum with a single
        index and only when the domain is an integer Interval.
        Eventually this should also be implemented for domains of
        Natural, NaturalPos, etc.
        Example: Let S = Sum(i, i+2, Interval(0, 10)). Then S.shift(one)
        will return |- S = Sum(i, i+1, Interval(1, 11)).
        '''
        if not hasattr(self, 'index'):
            raise NotImplementedError(
                "Sum.shifting() only implemented for summations with a single "
                "index over an Interval. The sum {} has indices {}.".format(
                    self, self.indices))
        # The following constraint can eventually be modified to deal
        # with a domain like all Natural … but for now limited to
        # integer Interval domain.
        if not isinstance(self.domain, Interval):
            raise NotImplementedError(
                "Sum.shifting() only implemented for summations with a single "
                "index over an Interval. The sum {} has domain {}.".format(
                    self, self.domain))

        from . import index_shift

        _x = self.index
        _a = self.domain.lower_bound
        _b = self.domain.upper_bound
        _c = shift_amount

        f_op, f_op_sub = Function(f, self.index), self.summand
        """
        # SHOULD BE HANDLED VIA AUTO-SIMPLIFICATION NOW.

        # Create some (possible) reduction formulas for the shifted
        # components, which will then be passed through to the
        # instantiation as "reductions" for simpifying the final form
        # of the indices and summand. Notice that when attempting to
        # simplify the summand, we need to send along the assumption
        # about the index domain. If the (supposed) reduction is
        # trivial (like |– x = x), the eventual instantiation process
        # will ignore/eliminate it.
        replacements = list(defaults.replacements)
        if (simplify_idx):
            lower_bound_shifted = (
                Add(_a, _c).simplification(
                    shallow=True, assumptions=assumptions))
            replacements.append(lower_bound_shifted)
            upper_bound_shifted = (
                Add(_b, _c).simplification(
                    shallow=True, assumptions=assumptions))
            replacements.append(upper_bound_shifted)
        if (simplify_summand):
            summand_shifted = f_op_sub.basic_replaced({_i:subtract(_i, _c)})
            summand_shifted = (
                summand_shifted.simplification(shallow=True,
                    assumptions=[*assumptions, InSet(_i, Interval(_a, _b))]))
            replacements.append(summand_shifted)
        """

        return index_shift.instantiate({
            f_op: f_op_sub,
            x: _x,
            a: _a,
            b: _b,
            c: _c
        })