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)
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)
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)
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)
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})
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)
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)
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)
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)
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)
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()
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})
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)
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))
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.")
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()
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()
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)
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 })
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))
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})
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 })