def computation_via_complement(self, complement_prob_of_all, sample_space, **defaults_config): from . import complementary_event_prob_of_all if not isinstance(complement_prob_of_all, ProbOfAll): raise TypeError("'complement_prob_of_all' should be a ProbOfAll") if complement_prob_of_all.domain != self.domain: raise ValueError("'complement_prob_of_all' should have the same " "domain: %s ≠ %s" % (complement_prob_of_all.domain, self.domain)) _Omega = sample_space _X = self.domain _f = Lambda(self.instance_param, self.instance_expr) _Q = Lambda(self.instance_param, self.non_domain_condition()) _R = Lambda(complement_prob_of_all.instance_param, complement_prob_of_all.non_domain_condition()) _x = self.instance_param _y = complement_prob_of_all.instance_param impl = complementary_event_prob_of_all.instantiate({ Omega: _Omega, X: _X, f: _f, Q: _Q, R: _R, x: _x, y: _y }) return impl.derive_consequent()
def transformation(self, lambda_map, new_domain, sample_space, **defaults_config): ''' Equate this ProbOfAll expression with a new ProbOfAll expression with the 'new_domain' using the lambda_map transformation. The lambda_map must be a bijection from the new_domain to the original domain, and the original mapping must be 1-to-1 function from the original domain to 'sample_space' which must be a sample space. ''' from . import prob_of_all_events_transformation _Omega = sample_space _A = self.domain _B = new_domain _Q = Lambda(self.instance_param, self.non_domain_condition()) _f = Lambda(self.instance_param, self.instance_expr) _g = lambda_map _x = self.instance_param _y = lambda_map.parameter return prob_of_all_events_transformation.instantiate({ Omega: _Omega, A: _A, B: _B, Q: _Q, f: _f, g: _g, x: _x, y: _y })
def deduce_in_number_set(self, number_set, **defaults_config): from . import (summation_nat_closure, summation_nat_pos_closure, summation_int_closure, summation_real_closure, summation_complex_closure) _x = self.instance_param _f = Lambda(_x, self.instance_expr) _Q = Lambda(_x, self.condition) if number_set == Natural: thm = summation_nat_closure elif number_set == NaturalPos: thm = summation_nat_pos_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 NotImplementedError( "'Sum.deduce_in_number_set' not implemented for the %s set" % str(number_set)) impl = thm.instantiate({x: _x, f: _f, Q: _Q}) antecedent = impl.antecedent if not antecedent.proven(): # Conclude the antecedent via generalization. antecedent.conclude_via_generalization() return impl.derive_consequent()
def derive_negated_forall(self, **defaults_config): ''' 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)). ''' from . import exists_unfolding from . import exists_not_implies_not_forall from proveit.logic import Not _x = self.instance_params _Q = Lambda(_x, self.condition) if isinstance(self.instance_expr, Not): _P = Lambda(_x, self.instance_expr.operand) _impl = exists_not_implies_not_forall.instantiate({ P: _P, Q: _Q, S: self.domain, x: _x }) return _impl.derive_consequent() else: _P = Lambda(_x, self.instance_expr) _impl = exists_unfolding.instantiate({ P: _P, Q: _Q, S: self.domain, x: _x }) return _impl.derive_consequent()
def mappingHTML(self): from proveit import Lambda from proveit.logic import Set mappedVarLists = self.mappedVarLists html = '<span style="font-size:20px;">' if len(mappedVarLists) == 1 or (len(mappedVarLists) == 2 and len(mappedVarLists[-1]) == 0): # a single relabeling map, or a single specialization map with no relabeling map mappedVars = mappedVarLists[0] html += ', '.join( Lambda(var, self.mappings[var])._repr_html_() for var in mappedVars) else: html += ', '.join( Set(*[Lambda(var, self.mappings[var]) for var in mappedVars])._repr_html_() for mappedVars in mappedVarLists[:-1]) if len(mappedVarLists[-1]) > 0: # the last group is the relabeling map, if there is one mappedVars = mappedVarLists[-1] html += ', relabeling ' + Set( *[Lambda(var, self.mappings[var]) for var in mappedVars])._repr_html_() html += '</span>' return html
def number_sum_reduction(self, **defaults_config): from . import scalar_sum_extends_number_sum _b = self.indices _f = Lambda(_b, self.summand) _Q = Lambda(_b, self.condition) _j = _b.num_elements() impl = scalar_sum_extends_number_sum.instantiate( {j:_j, b:_b, f:_f, Q:_Q}) return impl.derive_consequent()
def unfold(self, assumptions=USE_DEFAULTS): ''' From this existential quantifier, derive the "unfolded" version according to its definition (the negation of a universal quantification). ''' from proveit.logic.booleans.quantification.existence \ import exists_unfolding _n = self.instance_params.num_elements(assumptions) _P = Lambda(self.instance_params, self.operand.body.value) _Q = Lambda(self.instance_params, self.operand.body.condition) return exists_unfolding.instantiate( {n: _n, P: _P, Q: _Q}, assumptions=assumptions). \ derive_consequent(assumptions)
def distribution(self, idx, *, field=None, **defaults_config): ''' Given a Qmult operand at the (0-based) index location 'idx' that is a vector sum or summation, prove the distribution over that Qmult factor and return an equality to the original Qmult. For example, we could take the Qmult qmult = Qmult(A, B+C, D) and call qmult.distribution(1) to obtain: |- Qmult(A, B+C, D) = Qmult(A, B, D) + Qmult(A, C. D) ''' from . import (qmult_distribution_over_add, qmult_distribution_over_summation) sum_factor = self.operands[idx] _A = self.operands[:idx] _C = self.operands[idx + 1:] _m = _A.num_elements() _n = _C.num_elements() if isinstance(sum_factor, VecAdd): _B = sum_factor.operands _i = _B.num_elements() impl = qmult_distribution_over_add.instantiate({ i: _i, m: _m, n: _n, A: _A, B: _B, C: _C }) return impl.derive_consequent().with_wrapping_at() elif isinstance(sum_factor, VecSum): _b = sum_factor.indices _j = _b.num_elements() _f = Lambda(sum_factor.indices, sum_factor.summand) _Q = Lambda(sum_factor.indices, sum_factor.condition) impl = qmult_distribution_over_summation.instantiate({ f: _f, Q: _Q, j: _j, m: _m, n: _n, b: _b, A: _A, C: _C }) return impl.derive_consequent().with_wrapping_at() else: raise ValueError( "Don't know how to distribute tensor product over " + str(sum_factor.__class__) + " factor")
def deduce_in_bool(self, **defaults_config): ''' 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). ''' from . import exists_is_bool _x = self.instance_params _P = Lambda(_x, self.instance_expr) _Q = Lambda(_x, self.condition) return exists_is_bool.instantiate({ P: _P, Q: _Q, S: self.domain, x: _x })
def distribution(self, idx, *, field=None, **defaults_config): ''' Given a TensorProd operand at the (0-based) index location 'idx' that is a vector sum or summation, prove the distribution over that TensorProd factor and return an equality to the original TensorProd. For example, we could take the TensorProd tens_prod = TensorProd(a, b+c, d) and call tens_prod.distribution(1) to obtain: |- TensorProd(a, b+c, d) = TensorProd(a, b, d) + TensorProd(a, c, d) ''' from . import (tensor_prod_distribution_over_add, tensor_prod_distribution_over_summation) _V = VecSpaces.known_vec_space(self, field=field) _K = VecSpaces.known_field(_V) sum_factor = self.operands[idx] _a = self.operands[:idx] _c = self.operands[idx+1:] _i = _a.num_elements() _k = _c.num_elements() if isinstance(sum_factor, VecAdd): _b = sum_factor.operands _V = VecSpaces.known_vec_space(self, field=field) _j = _b.num_elements() # use preserve_all=True in the following instantiation # because the instantiation is an intermediate step; # otherwise auto_simplification can over-do things impl = tensor_prod_distribution_over_add.instantiate( {K:_K, i:_i, j:_j, k:_k, V:_V, a:_a, b:_b, c:_c}, preserve_all=True) return impl.derive_consequent().with_wrapping_at() elif isinstance(sum_factor, VecSum): _b = sum_factor.indices _j = _b.num_elements() _f = Lambda(sum_factor.indices, sum_factor.summand) _Q = Lambda(sum_factor.indices, sum_factor.condition) # use preserve_all=True in the following instantiation # because the instantiation is an intermediate step; # otherwise auto_simplification can over-do things impl = tensor_prod_distribution_over_summation.instantiate( {K:_K, f:_f, Q:_Q, i:_i, j:_j, k:_k, V:_V, a:_a, b:_b, c:_c}, preserve_all=True) return impl.derive_consequent().with_wrapping_at() else: raise ValueError( "Don't know how to distribute tensor product over " + str(sum_factor.__class__) + " factor")
def conclude_over_expr_range(self, **defaults_config): ''' Conclude a conjunction over an ExprRange via each element of the ExprRange being True. This could be conceptualized as a generalization of the conclude_as_redundant() method above. ''' from proveit import ExprRange, Lambda from . import conjunction_from_quantification if (self.operands.num_entries() != 1 or not isinstance(self.operands[0], ExprRange)): raise ValueError( "'And.conclude_over_expr_range()' only allowed " "for a conjunction of the form " "P(i) and P(i+1) and .. and P(j) (i.e. a conjunction " "over a single ExprRange), but instead you have: {}".format( self)) the_expr_range = self.operands[0] _i_sub = the_expr_range.true_start_index _j_sub = the_expr_range.true_end_index _k_sub = the_expr_range.parameter _P_sub = Lambda(the_expr_range.parameter, the_expr_range.body) impl = conjunction_from_quantification.instantiate({ i: _i_sub, j: _j_sub, k: _k_sub, P: _P_sub }) return impl.derive_consequent()
def mappingStr(self): from proveit import Lambda from proveit.logic import Set mappedVarLists = self.mappedVarLists out_str = '' if len(mappedVarLists) == 1 or (len(mappedVarLists) == 2 and len(mappedVarLists[-1]) == 0): # a single relabeling map, or a single specialization map with no relabeling map mappedVars = mappedVarLists[0] out_str += ', '.join(str(Lambda(var, self.mappings[var])) for var in mappedVars) else: out_str += ', '.join(str(Set(*[Lambda(var, self.mappings[var]) for var in mappedVars])) for mappedVars in mappedVarLists[:-1]) if len(mappedVarLists[-1]) > 0: # the last group is the relabeling map, if there is one mappedVars = mappedVarLists[-1] out_str += ', relabeling ' + str(Set(*[Lambda(var, self.mappings[var]) for var in mappedVars])) return out_str
def empty_range(_i, _j, _f, assumptions): # If the start and end are literal ints and form an # empty range, then it should be straightforward to # prove that the range is empty. from proveit.numbers import is_literal_int, Add from proveit.logic import Equals from proveit import m _m = entries[0].start_index _n = entries[0].end_index empty_req = Equals(Add(_n, one), _m) if is_literal_int(_m) and is_literal_int(_n): if _n.as_int() + 1 == _m.as_int(): empty_req.prove() if empty_req.proven(assumptions): _f = Lambda( (entries[0].parameter, entries[0].body.parameter), entries[0].body.body) _i = entry_map(_i) _j = entry_map(_j) return len_of_empty_range_of_ranges.instantiate( { m: _m, n: _n, f: _f, i: _i, j: _j }, assumptions=assumptions)
def conclude(self, **defaults_config): from proveit.logic import SubsetEq if (self.has_domain() and self.instance_params.is_single and self.conditions.is_single()): instance_map = Lambda(self.instance_params, self.instance_expr) domain = self.domain known_domains = set() # Check the known quantified instance expressions # and known set inclusions of domains to see if we can # construct a proof via inclusive existential # quantification. if instance_map in Exists.known_instance_maps: known_foralls = Exists.known_instance_maps[instance_map] for known_forall in known_foralls: if (known_forall.has_domain() and known_forall.instance_params.is_single() and known_forall.conditions.is_single()): if known_forall.is_applicable(): known_domains.add(known_forall.domain) if len(known_domains) > 0 and domain in SubsetEq.known_left_sides: # We know this quantification in other domain(s). # Does our domain include any of those? for known_inclusion in SubsetEq.known_right_sides[domain]: if known_inclusion.is_applicable(): subset = known_inclusion.subset if subset in known_domains: # We know the quantification over a s # uperset. We can use # inclusive_universal_quantification. return self.conclude_via_domain_inclusion(subset)
def elim_domain_condition(self, **defaults_config): ''' From [(x -> f(x) if x ∈ A) ∈ Bijections(A, B)], derive and return [f ∈ Bijections(A, B)]. ''' from . import elim_domain_condition bijections = self.domain _A = bijections.domain _B = bijections.codomain _f_with_cond = self.element if (not isinstance(_f_with_cond, Lambda) or not isinstance(_f_with_cond.body, Conditional)): raise TypeError( "'elim_domain_condition' only works with conditioned " "Lambda function element, not %s" % _f_with_cond) condition = _f_with_cond.body.condition domain_cond = InSet(_f_with_cond.parameter, _A) if condition != domain_cond: raise TypeError( "'elim_domain_condition' only works with a Lambda " "function element conditioned on the parameter being " "in the domain: %s ≠ %s" % (condition, domain_cond)) _f = Lambda(_f_with_cond.parameter, _f_with_cond.body.value) return elim_domain_condition.instantiate({A: _A, B: _B, f: _f})
def defintion(self, sample_space, **defaults_config): ''' The defintion of a ProbOfAll equates it with the probability of an event (set of samples in a sample space). ''' from . import prob_of_all_def _Omega = sample_space _X = self.domain _f = Lambda(self.instance_param, self.instance_expr) _Q = Lambda(self.instance_param, self.non_domain_condition()) return prob_of_all_def.instantiate({ Omega: _Omega, X: _X, f: _f, Q: _Q, x: self.instance_param })
def definition(self, assumptions=USE_DEFAULTS): ''' Return definition of this existential quantifier as an equation with this existential quantifier on the left and a negated universal quantification on the right. ''' from proveit.logic.booleans.quantification.existence \ import exists_def _n = self.instance_params.num_elements(assumptions) _P = Lambda(self.instance_params, self.operand.body.value) _Q = Lambda(self.instance_params, self.operand.body.condition) return exists_def.instantiate({ n: _n, P: _P, Q: _Q }, assumptions=assumptions)
def computation(self, sample_space, **defaults_config): ''' The computation of a ProbOfAll equates it with the corresponding summation over sample probabilities as long as the function is an injection onto a sample space. ''' from . import prob_of_all_as_sum _Omega = sample_space _X = self.domain _f = Lambda(self.instance_param, self.instance_expr) _Q = Lambda(self.instance_param, self.non_domain_condition()) return prob_of_all_as_sum.instantiate({ Omega: _Omega, X: _X, f: _f, Q: _Q, x: self.instance_param })
def unfold(self, **defaults_config): ''' From this existential quantifier, derive the "unfolded" version according to its definition (the negation of a universal quantification). ''' from proveit.logic.booleans.quantification.existence \ import exists_unfolding _x = _y = self.instance_params _n = _x.num_elements() _P = Lambda(_x, self.operand.body.value) _Q = Lambda(_x, self.operand.body.condition) return exists_unfolding.instantiate({ n: _n, P: _P, Q: _Q, x: _x, y: _y }).derive_consequent()
def _lambdaExpr(lambdaMap, defaultGlobalReplSubExpr): from proveit._core_.expression.inner_expr import InnerExpr if isinstance(lambdaMap, InnerExpr): expr = lambdaMap.replMap() else: expr = lambdaMap if not isinstance(expr, Lambda): # as a default, do a global replacement return Lambda.globalRepl(expr, defaultGlobalReplSubExpr) return expr
def definition(self, **defaults_config): ''' Return definition of this existential quantifier as an equation with this existential quantifier on the left and a negated universal quantification on the right. ''' from proveit.logic.booleans.quantification.existence \ import exists_def _x = _y = self.instance_params _n = _x.num_elements() _P = Lambda(_x, self.operand.body.value) _Q = Lambda(_x, self.operand.body.condition) return exists_def.instantiate({ n: _n, P: _P, Q: _Q, x: _x, y: _y }, preserve_expr=self)
def deduce_in_bool(self, **defaults_config): ''' 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 = Lambda(_x, self.instance_expr) _n = _x.num_elements() return forall_in_bool.instantiate({n: _n, P: _P, x: _x})
def side_effects(self, judgment): ''' Side-effect derivations to attempt automatically for an exists operations. ''' # Remember the proven Existential judgments by their # instance expressions. instance_map = Lambda(judgment.expr.instance_params, judgment.expr.instance_expr) Exists.known_instance_maps.setdefault(instance_map, set()).add(judgment) return yield self.derive_negated_forall # derive the negated forall form
def entry_map(entry): if isinstance(entry, ExprRange): if isinstance(entry.body, ExprRange): # Return an ExprRange of lambda maps. return ExprRange(entry.parameter, entry.body.lambda_map, entry.start_index, entry.end_index) else: # Use the ExprRange entry's map. return entry.lambda_map # For individual elements, just map to the # elemental entry. return Lambda(_x, entry)
def deduce_in_vec_space(self, vec_space=None, *, field=None, **defaults_config): ''' Prove that this vector summation is in a vector space. ''' from . import summation_closure if vec_space is None: with defaults.temporary() as tmp_defaults: tmp_defaults.assumptions = (defaults.assumptions + self.conditions.entries) vec_space = VecSpaces.known_vec_space(self.summand, field=field) _V = vec_space _K = VecSpaces.known_field(_V) _b = self.indices _j = _b.num_elements() _f = Lambda(self.indices, self.summand) if not hasattr(self, 'condition'): print(self) _Q = Lambda(self.indices, self.condition) return summation_closure.instantiate( {j:_j, K:_K, f:_f, Q:_Q, V:_V, b:_b}).derive_consequent()
def conclude_via_example(self, example_instance, **defaults_config): ''' Conclude and return this [exists_{x_1, .., x_n | Q(x_1, ..., x_n)} P(x_1, ..., x_n)] from P(y_1, ..., y_n) and Q(y_1, ..., y_n) where y_1, ..., y_n is the given example_instance. ''' from . import existence_by_example from . import existence_by_example_with_conditions _x = self.instance_params _n = _x.num_elements() _P = Lambda(_x, self.instance_expr) _y = composite_expression(example_instance) if hasattr(self, 'condition'): _Q = Lambda(_x, self.condition) return existence_by_example_with_conditions.instantiate({ n: _n, x: _x, y: _y, P: _P, Q: _Q }) return existence_by_example.instantiate({n: _n, x: _x, y: _y, P: _P})
def reduceOperands(innerExpr, inPlace=True, mustEvaluate=False, assumptions=USE_DEFAULTS): ''' Attempt to return an InnerExpr object that is provably equivalent to the given innerExpr but with simplified operands at the inner-expression level. If inPlace is True, the top-level expression must be a KnownTruth and the simplified KnownTruth is derived instead of an equivalence relation. If mustEvaluate is True, the simplified operands must be irreducible values (see isIrreducibleValue). ''' # Any of the operands that can be simplified must be replaced with their evaluation from proveit import InnerExpr assert isinstance( innerExpr, InnerExpr), "Expecting 'innerExpr' to be of type 'InnerExpr'" inner = innerExpr.exprHierarchy[-1] while True: allReduced = True for operand in inner.operands: if not mustEvaluate or not isIrreducibleValue(operand): # the operand is not an irreducible value so it must be evaluated operandEval = operand.evaluation( assumptions=assumptions ) if mustEvaluate else operand.simplification( assumptions=assumptions) if mustEvaluate and not isIrreducibleValue(operandEval.rhs): raise EvaluationError( 'Evaluations expected to be irreducible values') if operandEval.lhs != operandEval.rhs: # compose map to replace all instances of the operand within the inner expression lambdaMap = innerExpr.replMap().compose( Lambda.globalRepl(inner, operand)) # substitute in the evaluated value if inPlace: innerExpr = InnerExpr( operandEval.subRightSideInto(lambdaMap), innerExpr.innerExprPath) else: innerExpr = InnerExpr( operandEval.substitution(lambdaMap).rhs, innerExpr.innerExprPath) allReduced = False break # start over (there may have been multiple substitutions) if allReduced: return innerExpr inner = innerExpr.exprHierarchy[-1]
def substitute_domain(self, superset, **defaults_config): ''' 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. ''' from proveit.logic import And from . import exists_in_superset _x = self.instance_params _P = Lambda(_x, self.instance_expr) if self.conditions.num_entries() == 1: _Q = Lambda(_x, self.condition) else: _Q = Lambda(_x, And(self.conditions[1:])) _impl = exists_in_superset.instantiate({ P: _P, Q: _Q, A: self.domain, B: superset, x: _x, y: _x }) return _impl.derive_consequent()
def entry_map(entry): # Don't auto-simplify the entry. preserved_exprs.add(entry) if isinstance(entry, ExprRange): if isinstance(entry.body, ExprRange): # Return an ExprRange of lambda maps. return ExprRange(entry.parameter, entry.body.lambda_map, entry.true_start_index, entry.true_end_index) else: return entry.lambda_map # For individual elements, just map to the # elemental entry. return Lambda(_x, entry)
def partition(self, A, B, super_set, sample_space, **defaults_config): ''' Equate this ProbOfAll expression with the some of two ProbOfAll expressions over two disjoint domains whose union is the original domain. ''' from . import prob_of_disjoint_events_is_prob_sum _Omega = sample_space _A = A _B = B _C = self.domain _f = Lambda(self.instance_param, self.instance_expr) _Q = Lambda(self.instance_param, self.non_domain_condition()) _X = super_set return prob_of_disjoint_events_is_prob_sum.instantiate({ Omega: _Omega, A: _A, B: _B, C: _C, X: _X, Q: _Q, f: _f })