def _formatted(self, formatType, fence=False, **kwargs): outStr = '' explicit_conditions = ExprTuple(*self.explicitConditions()) inner_fence = (len(explicit_conditions) > 0) formatted_instance_var = self.instanceVar.formatted(formatType) formatted_instance_element = self.instanceElement.formatted( formatType, fence=inner_fence) formatted_domain = self.domain.formatted(formatType, fence=True) if formatType == 'latex': outStr += r"\left\{" else: outStr += "{" outStr += formatted_instance_element if len(explicit_conditions) > 0: formatted_conditions = explicit_conditions.formatted(formatType, fence=False) if formatType == 'latex': outStr += r'~|~' else: outStr += ' s.t. ' # such that outStr += formatted_conditions if formatType == 'latex': outStr += r"\right\}" else: outStr += "}" outStr += '_{' + formatted_instance_var if self.domain is not None: if formatType == 'latex': outStr += r' \in ' else: outStr += ' in ' outStr += formatted_domain outStr += '}' return outStr
def apply_association_thm(expr, startIdx, length, thm, assumptions=USE_DEFAULTS): from proveit import ExprTuple from proveit.logic import Equals beg = startIdx if beg < 0: beg = len(expr.operands) + beg # use wrap-around indexing end = beg + length if end > len(expr.operands): raise IndexError("'startIdx+length' out of bounds: %d > %d." % (end, len(expr.operands))) if beg == 0 and end == len(expr.operands): # association over the entire range is trivial: return Equals(expr, expr).prove() # simply the self equality i, j, k, A, B, C = thm.allInstanceVars() _A = ExprTuple(*expr.operands[:beg]) _B = ExprTuple(*expr.operands[beg:end]) _C = ExprTuple(*expr.operands[end:]) _i = _A.length(assumptions) _j = _B.length(assumptions) _k = _C.length(assumptions) return thm.specialize({ i: _i, j: _j, k: _k, A: _A, B: _B, C: _C }, assumptions=assumptions)
def derive_cart_exp_membership(self, **defaults_config): ''' If the domain is a tensor product Cartesian exponentials on the same field, prove that the element is also a membero of the Cartesion exponential of the sum of the exponents. Thst is, if the domain is of the form K^{n_1} ⊗ K^{n_2} ⊗ ... ⊗ K^{n_m} derive that the element is also contained in K^{n_1 · n_2 · ... · n_m} ''' from . import tensor_prod_of_cart_exps_within_cart_exp _K, _ns = self._get_cart_exps_field_and_exponents() _ns = ExprTuple(*_ns) _m = _ns.num_elements() inclusion = tensor_prod_of_cart_exps_within_cart_exp.instantiate({ K: _K, m: _m, n: _ns }) return inclusion.derive_superset_membership(self.element)
def deriveSomeFromAnd(self, idx, assumptions=USE_DEFAULTS): ''' added by JML 7/8/19 From (A and ... and B and ... C) derive any one index even if it is an iteration. ''' from proveit import ExprTuple from proveit.logic.boolean.conjunction._theorems_ import someFromAnd lVal = ExprTuple(*self.operands[:idx]).len() mVal = ExprTuple(self.operands[idx]).len() nVal = ExprTuple(*self.operands[idx + 1:]).len() return someFromAnd.specialize( { l: lVal, m: mVal, n: nVal, AA: self.operands[:idx], BB: self.operands[idx], CC: self.operands[idx + 1:] }, assumptions=assumptions)
def apply_disassociation_thm(expr, idx, thm=None, assumptions=USE_DEFAULTS): from proveit import ExprTuple if idx < 0: idx = len(expr.operands) + idx # use wrap-around indexing if idx >= len(expr.operands): raise IndexError("'idx' out of range for disassociation") if not isinstance(expr.operands[idx], expr.__class__): raise ValueError( "Expecting %d index of %s to be grouped (i.e., a nested expression of the same type)" % (idx, str(expr))) i, j, k, A, B, C = thm.allInstanceVars() _A = ExprTuple(*expr.operands[:idx]) _B = expr.operands[idx].operands _C = ExprTuple(*expr.operands[idx + 1:]) _i = _A.length(assumptions) _j = _B.length(assumptions) _k = _C.length(assumptions) return thm.specialize({ i: _i, j: _j, k: _k, A: _A, B: _B, C: _C }, assumptions=assumptions)
def _formatted(self, formatType, fence=False, **kwargs): outStr = '' explicit_conditions = ExprTuple(*self.explicitConditions()) inner_fence = (len(explicit_conditions) > 0) formatted_instance_element = self.instanceElement.formatted( formatType, fence=inner_fence) explicit_domains = self.explicitDomains() domain_conditions = ExprTuple(*self.domainConditions()) if formatType == 'latex': outStr += r"\left\{" else: outStr += "{" outStr += formatted_instance_element if len(explicit_conditions) > 0: formatted_conditions = explicit_conditions.formatted(formatType, fence=False) if formatType == 'latex': outStr += r'~|~' else: outStr += ' s.t. ' # such that outStr += formatted_conditions if formatType == 'latex': outStr += r"\right\}" else: outStr += "}" outStr += '_{' instance_param_or_params = self.instanceParamOrParams if explicit_domains == [explicit_domains[0]] * len(explicit_domains): # all in the same domain outStr += instance_param_or_params.formatted( formatType, operatorOrOperators=',', fence=False) outStr += r' \in ' if formatType == 'latex' else ' in ' outStr += explicit_domains[0].formatted(formatType) else: outStr += domain_conditions.formatted(formatType, operatorOrOperators=',', fence=False) outStr += '}' return outStr
def oneElimination(self, idx, assumptions=USE_DEFAULTS): ''' Equivalence method that derives a simplification in which a single factor of one, at the given index, is eliminated. For example: x*y*1*z = x*y*z ''' from proveit.number import one from ._theorems_ import elimOneLeft, elimOneRight, elimOneAny if self.operands[idx] != one: raise ValueError( "Operand at the index %d expected to be zero for %s" % (idx, str(self))) if len(self.operands) == 2: if idx == 0: return elimOneLeft.specialize({x: self.operands[1]}, assumptions=assumptions) else: return elimOneRight.specialize({x: self.operands[0]}, assumptions=assumptions) _a = ExprTuple(*self.operands[:idx]) _b = ExprTuple(*self.operands[idx + 1:]) _i = _a.length(assumptions) _j = _b.length(assumptions) return elimOneAny.specialize({ i: _i, j: _j, a: _a, b: _b }, assumptions=assumptions)
def deriveBundled(self, assumptions=USE_DEFAULTS): ''' From a nested forall statement, derive the bundled forall statement. For example, forall_{x | Q(x)} forall_{y | R(y)} P(x, y) becomes forall_{x, y | Q(x), R(y)} P(x, y). ''' raise NotImplementedError("Need to update") from ._theorems_ import bundling assert isinstance(self.instanceExpr, Forall), "Can only bundle nested forall statements" innerForall = self.instanceExpr composedInstanceVars = ExprTuple([self.instanceVars, innerForall.instanceVars]) P_op, P_op_sub = Operation(P, composedInstanceVars), innerForall.instanceExpr Q_op, Q_op_sub = Operation(Qmulti, self.instanceVars), self.conditions R_op, R_op_sub = Operation(Rmulti, innerForall.instanceVars), innerForall.conditions return bundling.specialize({xMulti:self.instanceVars, yMulti:innerForall.instanceVars, P_op:P_op_sub, Q_op:Q_op_sub, R_op:R_op_sub, S:self.domain}).deriveConclusion(assumptions)
def latex(self, performUsabilityCheck=True): ''' If the KnownTruth was proven under any assumptions, display the double-turnstyle notation to show that the set of assumptions proves the statement/expression. Otherwise, simply display the expression. ''' from proveit import ExprTuple if performUsabilityCheck and not self.isUsable(): self.raiseUnusableProof() if len(self.assumptions) > 0: assumptionsLatex = ExprTuple(*self.assumptions).formatted( 'latex', fence=False) return r'{' + assumptionsLatex + r'} \vdash ' + self.expr.latex() return r'\vdash ' + self.expr.string()
def __init__(self, operand): ''' Len takes a single operand which should properly be an ExprTuple or an expression (such as a variable) that represents a tuple. ''' operand = single_or_composite_expression(operand) if isinstance(operand, ExprTuple): # Nest an ExprTuple operand in an extra ExprTuple as # a clear indication that Len has a single operand # that is an ExprTuple rather than multiple operands. operand = ExprTuple(operand) # In order to always recognize that Len only takes a single # operand, we must wrap it as an ExprTuple with one entry. Operation.__init__(self, Len._operator_, operand)
def deduceInBool(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.number import one from ._axioms_ import forall_in_bool _x = self.instanceParams P_op, _P_op = Operation(P, _x), self.instanceExpr _n = _x.length(assumptions) x_1_to_n = ExprTuple(ExprRange(k, IndexedVar(x, k), one, _n)) return forall_in_bool.specialize({ n: _n, P_op: _P_op, x_1_to_n: _x }, assumptions=assumptions)
def __init__(self, operands, *, styles=None): ''' Len can take an explicit ExprTuple as operands, or it may take an expression (such as a varaible) that represents a tuple. Either way, this expression is taken as the 'operands'. ''' if isinstance(operands, ExprRange): # An ExprRange cannot represent an ExprTuple, # so we must want this wrapped in an ExprTuple. operands = ExprTuple(operands) # In order to always recognize that Len only takes a single # operand, we must wrap it as an ExprTuple with one entry. Operation.__init__(self, Len._operator_, operands=operands, styles=styles)
def _formatted(self, format_type, fence=False, **kwargs): from proveit import ExprRange out_str = '' explicit_conditions = ExprTuple(*self.explicit_conditions()) inner_fence = (explicit_conditions.num_entries() > 0) formatted_instance_element = self.instance_element.formatted( format_type, fence=inner_fence) explicit_domains = self.explicit_domains() domain_conditions = ExprTuple(*self.domain_conditions()) if format_type == 'latex': out_str += r"\left\{" else: out_str += "{" out_str += formatted_instance_element if explicit_conditions.num_entries() > 0: formatted_conditions = explicit_conditions.formatted(format_type, fence=False) if format_type == 'latex': out_str += r'~|~' else: out_str += ' s.t. ' # such that out_str += formatted_conditions if format_type == 'latex': out_str += r"\right\}" else: out_str += "}" out_str += '_{' instance_param_or_params = self.instance_param_or_params if (not any( isinstance(entry, ExprRange) for entry in explicit_domains) and explicit_domains == [explicit_domains[0]] * len(explicit_domains)): # all in the same domain out_str += instance_param_or_params.formatted( format_type, operator_or_operators=',', fence=False) out_str += r' \in ' if format_type == 'latex' else ' in ' out_str += explicit_domains[0].formatted(format_type) else: out_str += domain_conditions.formatted(format_type, operator_or_operators=',', fence=False) out_str += '}' return out_str
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 possibly_wrap_html_display_objects(orig): from proveit import ExprTuple try: if hasattr(orig, '_repr_html_'): # No need to wrap. Already has _repr_html. return orig all_expr_objs = True for obj in orig: if not isinstance(obj, Expression): all_expr_objs = False if not hasattr(obj, '_repr_html_'): return orig if all_expr_objs: # If they are all expression objects, wrap it in # an ExprTuple. return ExprTuple(*orig) return HTML_DisplayObjects(orig) except: return orig
def _formatted(self, format_type, **kwargs): ''' Format the binary relation operation. Note: it may be reversed if the "direction" style is "reversed". ''' from proveit import ExprTuple wrap_positions=self.wrap_positions() justification=self.get_style('justification') fence = kwargs.get('fence', False) subFence = kwargs.get('subFence', True) operator_str = self.operator.formatted(format_type) operands = self.operands if self.is_reversed(): operator_str = self.__class__.reversed_operator_str(format_type) operands = ExprTuple(*reversed(operands.entries)) return Operation._formattedOperation( format_type, fence=fence, subFence=subFence, operator_or_operators=operator_str, operands=operands, wrap_positions=wrap_positions, justification=justification)
def choose(self, *skolem_constants, print_message=True): ''' From the existential expression self = exists_{x_1,...,x_n | Q(x_1,...,x_n)} P(x_1,...,x_n), generate Skolem constants a_1,...,a_n in correspondence with the instance params x_1,...,x_n. The process will: (1) add Q(a_1,...,a_n) and P(a_1,...,a_n) to the default assumptions; (2) register the Skolem constants a_1,...,a_n in the skolem_consts_to_existential dictionary so they can be eliminated later using the eliminate() method; (3) return the newly-generated assumptions Q(a_1,...,a_n) and P(a_1,...,a_n) ''' # Register this particular collection of Skolem constants # in the dictionary as a key linking them to this Exists object Exists.skolem_consts_to_existential[skolem_constants] = self # build the Skolemized versions of the conditions Q and the # instance expression P repl_dict = { param: skolem_const for param, skolem_const in zip(self.instance_params, skolem_constants) } P_skolem = self.instance_expr.basic_replaced(repl_dict) Q_skolem = self.conditions.basic_replaced(repl_dict) # Update the default assumptions with the Skolem versions # of the conditions and instance expression defaults.assumptions = (*defaults.assumptions, *Q_skolem.entries, P_skolem) if print_message: print( "Creating Skolem 'constant(s)': {0}.\n" "Call the Judgment.eliminate{0} to complete the " "Skolemization\n(when the 'constant(s)' are no longer needed).\n" "Adding to defaults.assumptions:".format( skolem_constants, (*Q_skolem.entries))) return ExprTuple(*Q_skolem.entries, P_skolem)
def negSimplification(self, idx, assumptions=USE_DEFAULTS): ''' Equivalence method that derives a simplification in which a specific negated factor, at the given index, is factored out. For example: w*(-x)*y*z = -(w*x*y*z) ''' from proveit.number import Neg from ._theorems_ import multNegLeft, multNegRight, multNegAny if not isinstance(self.operands[idx], Neg): raise ValueError( "Operand at the index %d expected to be a negation for %s" % (idx, str(self))) if len(self.operands) == 2: if idx == 0: _x = self.operands[0].operand _y = self.operands[1] return multNegLeft.specialize({ x: _x, y: _y }, assumptions=assumptions) else: _x = self.operands[0] _y = self.operands[1].operand return multNegRight.specialize({ x: _x, y: _y }, assumptions=assumptions) _a = ExprTuple(*self.operands[:idx]) _b = self.operands[idx].operand _c = ExprTuple(*self.operands[idx + 1:]) _i = _a.length(assumptions) _j = _c.length(assumptions) return multNegAny.specialize({ i: _i, j: _j, a: _a, b: _b, c: _c }, assumptions=assumptions)
def factorization(self, the_factor, pull="left", group_factors=True, field=None, **defaults_config): ''' Deduce an equality between this VecAdd expression and a version in which either: (1) the scalar factor the_factor has been factored out in front (or possibly out behind) to produce a new ScalarMult; OR (2) the tensor product factor the_factor has been factored out in front (or possible out behind) to produce a new TensorProd. For example, if x = VecAdd(ScalarMult(a, v1), ScalarMult(a, v2)) then x.factorization(a) produces: |- x = ScalarMult(a, VecAdd(v1, v2)). Prove-It will need to know or be able to derive a vector space in which the vectors live. This method only works if the terms of the VecAdd are all ScalarMult objects or all TensorProd objects. In the case of all ScalarMult objects, any nested ScalarMult objects are first flattened if possible. Note: In the case of a VecAdd of all TensorProd objects, the lack of commutativity for tensor products limits any factorable tensor product factors to those occurring on the far left or far right of each tensor product term. Thus, for example, if x = VecAdd(TensorProd(v1, v2, v3), TensorProd(v1, v4, v5)) we can call x.factorization(v1) to obtain |- x = TensorProd(v1, VecAdd(TensorProd(v2, v3), TensorProd(v4, v5))), but we cannot factor v1 our of the expression y = VecAdd(TensorProd(v2, v1, v3), TensorProd(v4, v1, v5)) ''' expr = self eq = TransRelUpdater(expr) replacements = list(defaults.replacements) from proveit.linear_algebra import ScalarMult, TensorProd from proveit.numbers import one, Mult # Case (1) VecAdd(ScalarMult, ScalarMult, ..., ScalarMult) if all(isinstance(op, ScalarMult) for op in self.operands): # look for the_factor in each scalar; # code based on Add.factorization() _b = [] for _i in range(expr.terms.num_entries()): # remove nesting of ScalarMults term = expr.terms[_i].shallow_simplification().rhs expr = eq.update( expr.inner_expr().terms[_i].shallow_simplification()) # simplify the scalar part of the ScalarMult term = term.inner_expr().scalar.shallow_simplification().rhs expr = eq.update(expr.inner_expr().terms[_i].scalar. shallow_simplification()) if hasattr(term.scalar, 'factorization'): term_scalar_factorization = term.scalar.factorization( the_factor, pull, group_factors=group_factors, group_remainder=True, preserve_all=True) if not isinstance(term_scalar_factorization.rhs, Mult): raise ValueError( "Expecting right hand side of each factorization " "to be a product. Instead obtained: {}".format( term_scalar_factorization.rhs)) if pull == 'left': # the grouped remainder on the right _b.append( ScalarMult( term_scalar_factorization.rhs.operands[-1], term.scaled)) else: # the grouped remainder on the left _b.append( ScalarMult( term_scalar_factorization.rhs.operands[0], term.scaled)) # substitute in the factorized term expr = eq.update( term_scalar_factorization.substitution( expr.inner_expr().terms[_i].scalar, preserve_all=True)) else: if term.scalar != the_factor: raise ValueError( "Factor, %s, is not present in the term at " "index %d of %s!" % (the_factor, _i, self)) if pull == 'left': replacements.append( Mult(term.scalar, one).one_elimination(1)) else: replacements.append( Mult(one, term.scalar).one_elimination(0)) _b.append(ScalarMult(one, term.scaled)) if not group_factors and isinstance(the_factor, Mult): factor_sub = the_factor.operands else: factor_sub = ExprTuple(the_factor) # pull left/right not really relevant for the ScalarMult # cases; this simplification step still seems relevant if defaults.auto_simplify: # Simplify the remainder of the factorization if # auto-simplify is enabled. replacements.append(VecAdd(*_b).simplification()) from proveit import K, i, k, V, a # Perhaps here we could search through the operands to find # an appropriate VecSpace? Or maybe it doesn't matter? vec_space_membership = expr.operands[0].deduce_in_vec_space( field=field) _V_sub = vec_space_membership.domain _K_sub = VecSpaces.known_field(_V_sub) _i_sub = expr.operands.num_elements() _k_sub = the_factor _a_sub = ExprTuple(*_b) from proveit.linear_algebra.scalar_multiplication import ( distribution_over_vectors) distribution = distribution_over_vectors.instantiate( { V: _V_sub, K: _K_sub, i: _i_sub, k: _k_sub, a: _a_sub }, replacements=replacements) # need to connect the distributed version back to the # original self, via a shallow_simplification() of # each of the ScalarMult terms resulting in the distribution for _i in range(len(distribution.rhs.operands.entries)): distribution = (distribution.inner_expr().rhs.operands[_i]. shallow_simplify()) eq.update(distribution.derive_reversed()) # Case (2) VecAdd(TensorProd, TensorProd, ..., TensorProd) elif all(isinstance(op, TensorProd) for op in self.operands): # if hasattr(the_factor, 'operands'): # print("the_factor has operands: {}".format(the_factor.operands)) # the_factor_tuple = the_factor.operands.entries # else: # print("the_factor does not have operands: {}".format(the_factor)) # the_factor_tuple = (the_factor,) if isinstance(the_factor, TensorProd): the_factor_tuple = the_factor.operands.entries else: the_factor_tuple = (the_factor, ) # Setting the default_field here because the field # used manually in the association step somehow gets lost VecSpaces.default_field = field # look for the_factor in each TensorProd appearing in # the VecAdd operands, looking at the left vs. right # sides depending on the 'pull' direction specified _b = [] # to hold factors left behind for _i in range(expr.terms.num_entries()): # Notice we're not ready to deal with ExprRange # versions of Add operands here! # We are also implicitly assuming that each TensorProd # has at least two operands term = expr.terms[_i] if hasattr(term, 'operands'): term_tuple = term.operands.entries else: term_tuple = (term, ) if pull == 'left': # look for factor at left-most-side if the_factor_tuple != term_tuple[0:len(the_factor_tuple)]: raise ValueError( "VecAdd.factorization() expecting the_factor " "{0} to appear at the leftmost side of each " "addend, but {0} does not appear at the " "leftmost side of the addend {1}.".format( the_factor, term)) else: # we're OK, so save away the remainder of # factors from the rhs of the term, # and group any multi-term factor on the left if len(term_tuple[len(the_factor_tuple):]) == 1: _b.append(term_tuple[-1]) else: _b.append( TensorProd( *term_tuple[len(the_factor_tuple):])) # then create an associated version of the # expr to match the eventual thm instantiation # ALSO NEED TO DO THIS FOR THE RIGHT CASE expr = eq.update( expr.inner_expr().operands[_i].association( len(the_factor_tuple), len(term_tuple) - len(the_factor_tuple), preserve_all=True)) # perhaps we actually don't need the assoc step? # if len(the_factor_tuple) != 1: # expr = eq.update(expr.inner_expr().operands[_i]. # association(0, len(the_factor_tuple), # preserve_all=True)) elif pull == 'right': # look for factor at right-most-side if the_factor_tuple != term_tuple[-( len(the_factor_tuple)):]: raise ValueError( "VecAdd.factorization() expecting the_factor " "{0} to appear at the rightmost side of each " "addend, but {0} does not appear at the " "rightmost side of the addend {1}.".format( the_factor, term)) else: # we're OK, so save away the remainder of # factors from the lhs of the term, # and group any multi-term factor on the right if len(term_tuple[0:-(len(the_factor_tuple))]) == 1: _b.append(term_tuple[0]) else: _b.append( TensorProd( *term_tuple[0:-(len(the_factor_tuple))])) # then create an associated version of the # expr to match the eventual thm instantiation expr = eq.update( expr.inner_expr().operands[_i].association( 0, len(term_tuple) - len(the_factor_tuple), preserve_all=True)) # perhaps we actually don't need the assoc step? # if len(the_factor_tuple) != 1: # expr = eq.update(expr.inner_expr().operands[_i]. # association( # len(term_tuple)-len(the_factor_tuple), # len(the_factor_tuple), # preserve_all=True)) else: raise ValueError( "VecAdd.factorization() requires 'pull' argument " "to be specified as either 'left' or 'right'.") # now ready to instantiate the TensorProd/VecAdd # theorem: tensor_prod_distribution_over_add # and derive it's reversed result from proveit.linear_algebra.tensors import ( tensor_prod_distribution_over_add) from proveit import a, b, c, i, j, k, K, V from proveit.numbers import zero, one, num # useful to get ahead of time the num of operands # in the_factor and define the replacement # if hasattr(the_factor, 'operands'): # num_factor_entries = num(the_factor.operands.num_entries()) # factor_entries = the_factor.operands.entries # else: # num_factor_entries = one # factor_entries = (the_factor,) # useful to get ahead of time the num of operands # in the_factor and define the replacement if isinstance(the_factor, TensorProd): num_factor_entries = num(the_factor.operands.num_entries()) factor_entries = the_factor.operands.entries else: num_factor_entries = one factor_entries = (the_factor, ) # call deduce_in_vec_space() on the original self # instead of the current expr, otherwise we can run into # compications due to the associated sub-terms vec_space_membership = self.operands[0].deduce_in_vec_space( field=field) _V_sub = vec_space_membership.domain _K_sub = VecSpaces.known_field(_V_sub) if pull == 'left': # num of operands in left the_factor _i_sub = num_factor_entries # num of operands in right factor _k_sub = zero # the actual factor operands _a_sub = factor_entries # the other side is empty _c_sub = () elif pull == 'right': # left side is empty _i_sub = zero # right side has the factor _k_sub = num_factor_entries # left side is empty _a_sub = () # right side has the factor _c_sub = factor_entries _j_sub = num(len(_b)) _b_sub = ExprTuple(*_b) from proveit.linear_algebra.tensors import ( tensor_prod_distribution_over_add) impl = tensor_prod_distribution_over_add.instantiate( { V: _V_sub, K: _K_sub, i: _i_sub, j: _j_sub, k: _k_sub, a: _a_sub, b: _b_sub, c: _c_sub }, preserve_all=True) conseq = impl.derive_consequent() eq.update(conseq.derive_reversed()) else: print("Not yet an identified case. Sorry!") return eq.relation
def factorization(self, the_factors, pull="left", group_factors=True, group_remainder=None, **defaults_config): ''' Return the proven factorization (equality with the factored form) from pulling the factor(s) from this summation to the "left" or "right". If group_factors is True, the factors will be grouped together as a sub-product. group_remainder is not relevant kept for compatibility with other factor methods. ''' from proveit import ExprTuple, var_range, IndexedVar from proveit.numbers.multiplication import distribute_through_summation from proveit.numbers import Mult, one if not isinstance(the_factors, Expression): # If 'the_factors' is not an Expression, assume it is # an iterable and make it a Mult. the_factors = Mult(*the_factors) if not free_vars(the_factors).isdisjoint(self.instance_params): raise ValueError( 'Cannot factor anything involving summation indices ' 'out of a summation') expr = self # for convenience updating our equation eq = TransRelUpdater(expr) # We may need to factor the summand within the summation summand_assumptions = defaults.assumptions + self.conditions.entries summand_factorization = self.summand.factorization( the_factors, pull, group_factors=group_factors, group_remainder=True, assumptions=summand_assumptions) if summand_factorization.lhs != summand_factorization.rhs: gen_summand_factorization = summand_factorization.generalize( self.instance_params, conditions=self.conditions) expr = eq.update( expr.instance_substitution(gen_summand_factorization, preserve_all=True)) if not group_factors and isinstance(the_factors, Mult): factors = the_factors.factors else: factors = ExprTuple(the_factors) if pull == 'left': _a = factors _c = ExprTuple() summand_remainder = expr.summand.factors[-1] elif pull == 'right': _a = ExprTuple() _c = factors summand_remainder = expr.summand.factors[0] else: raise ValueError("'pull' must be 'left' or 'right', not %s" % pull) _b = self.instance_params _i = _a.num_elements() _j = _b.num_elements() _k = _c.num_elements() _f = Lambda(expr.instance_params, summand_remainder) _Q = Lambda(expr.instance_params, expr.condition) _impl = distribute_through_summation.instantiate( { i: _i, j: _j, k: _k, f: _f, Q: _Q, b: _b }, preserve_all=True) quantified_eq = _impl.derive_consequent(preserve_all=True) eq.update(quantified_eq.instantiate({a: _a, c: _c}, preserve_all=True)) return eq.relation
def doReducedEvaluation(self, assumptions=USE_DEFAULTS, **kwargs): ''' Derive and return this multiplication expression equated with an irreducible value. Handle the trivial case of a zero factor or do pairwise evaluation after simplifying negations and eliminating one factors. ''' from ._theorems_ import multZeroLeft, multZeroRight, multZeroAny from proveit.logic import isIrreducibleValue, EvaluationError from proveit.number import zero # First check for any zero factors -- quickest way to do an evaluation. try: zeroIdx = self.operands.index(zero) if len(self.operands) == 2: if zeroIdx == 0: return multZeroLeft.specialize({x: self.operands[1]}, assumptions=assumptions) else: return multZeroRight.specialize({x: self.operands[0]}, assumptions=assumptions) _a = self.operands[:zeroIdx] _b = self.operands[zeroIdx + 1:] _i = ExprTuple(*_a.length(assumptions)) _j = ExprTuple(*_b.length(assumptions)) return multZeroAny.specialize({ i: _i, j: _j, a: _a, b: _b }, assumptions=assumptions) except (ValueError, ProofFailure): pass # No such "luck" regarding a simple multiplication by zero. expr = self # A convenience to allow successive update to the equation via transitivities. # (starting with self=self). eq = TransRelUpdater(self, assumptions) # Simplify negations -- factor them out. expr = eq.update(expr.negSimplifications(assumptions)) if not isinstance(expr, Mult): # The expression may have changed to a negation after doing # negSimplification. Start the simplification of this new # expression fresh at this point. eq.update(expr.evaluation(assumptions)) return eq.relation # Eliminate any factors of one. expr = eq.update(expr.oneEliminations(assumptions)) if isIrreducibleValue(expr): return eq.relation # done if len(self.operands) > 2: eq.update(pairwiseEvaluation(expr, assumptions)) return eq.relation raise EvaluationError(self, assumptions)
def distribution(self, idx=None, assumptions=USE_DEFAULTS): r''' Distribute through the operand at the given index. Returns the equality that equates self to this new version. Examples: :math:`a (b + c + a) d = a b d + a c d + a a d` :math:`a (b - c) d = a b d - a c d` :math:`a \left(\sum_x f(x)\right c = \sum_x a f(x) c` Give any assumptions necessary to prove that the operands are in Complexes so that the associative and commutation theorems are applicable. ''' from ._theorems_ import distributeThroughSum, distributeThroughSubtract #, distributeThroughSummation from proveit.number.division._theorems_ import prodOfFracs #, fracInProd from proveit.number import Add, Div, Neg, Sum if idx is None and len(self.factors) == 2 and all( isinstance(factor, Div) for factor in self.factors): return prodOfFracs.specialize( { x: self.factors[0].numerator, y: self.factors[1].numerator, z: self.factors[0].denominator, w: self.factors[1].denominator }, assumptions=assumptions) operand = self.operands[idx] _a = ExprTuple(*self.operands[:idx]) _c = ExprTuple(*self.operands[idx + 1:]) _i = _a.length(assumptions) _k = _c.length(assumptions) if isinstance(operand, Add): _b = self.operands[idx].operands _j = _b.length(assumptions) return distributeThroughSum.specialize( { i: _i, j: _j, k: _k, a: _a, b: _b, c: _c }, assumptions=assumptions) elif (isinstance(operand, Add) and len(operand.operands) == 2 and isinstance(operand.operands[0], Neg)): _j = _k _x = self.operands[idx].operands[0] _y = self.operands[idx].operands[1].operand return distributeThroughSubtract.specialize( { i: _i, j: _j, a: _a, x: _x, y: _y, c: _c }, assumptions=assumptions) elif isinstance(operand, Div): raise NotImplementedError("Mult.distribution must be updated " "for Div case.") ''' eqn = fracInProd.specialize({wMulti:self.operands[:idx], x:self.operands[idx].operands[0], y:self.operands[idx].operands[1], zMulti:self.operands[idx+1:]}, assumptions=assumptions) try: # see if the numerator can simplify (e.g., with a one factor) numerSimplification = eqn.rhs.numerator.simplification(assumptions=assumptions) dummyVar = eqn.safeDummyVar() return numerSimplification.subRightSideInto(Equals(eqn.lhs, frac(dummyVar, eqn.rhs.denominator)), dummyVar) except: return eqn ''' elif isinstance(operand, Sum): raise NotImplementedError("Mult.distribution must be updated " "for Sum case.") ''' yMultiSub = operand.indices Pop, Pop_sub = Operation(P, operand.indices), operand.summand S_sub = operand.domain xDummy, zDummy = self.safeDummyVars(2) spec1 = distributeThroughSummation.specialize({Pop:Pop_sub, S:S_sub, yMulti:yMultiSub, xMulti:Etcetera(MultiVariable(xDummy)), zMulti:Etcetera(MultiVariable(zDummy))}, assumptions=assumptions) return spec1.deriveConclusion().specialize({Etcetera(MultiVariable(xDummy)):self.operands[:idx], \ Etcetera(MultiVariable(zDummy)):self.operands[idx+1:]}, assumptions=assumptions) ''' else: raise Exception("Unsupported operand type to distribute over: " + str(operand.__class__))
def derive_via_multi_dilemma(self, conclusion, assumptions=USE_DEFAULTS): ''' From (A or B) as self, and assuming A => C, B => D, and A, B, C, and D are Boolean, derive and return the conclusion, C or D. ''' from . import constructive_dilemma, destructive_dilemma, constructive_multi_dilemma, destructive_multi_dilemma from proveit.logic import Not, Or from proveit import ExprTuple assert (isinstance(conclusion, Or) and (conclusion.operands.num_entries() == self.operands.num_entries())), \ ("derive_via_multi_dilemma requires conclusion to be a " "disjunction, the same number of operands as self.") # Check for destructive versus constructive dilemma cases. if all(isinstance(operand, Not) for operand in self.operands) and all( isinstance(operand, Not) for operand in conclusion.operands): # destructive case. if self.operands.is_double() and destructive_dilemma.is_usable(): # From Not(C) or Not(D), A => C, B => D, conclude Not(A) or # Not(B) return destructive_dilemma.instantiate( { C: self.operands[0].operand, D: self.operands[1].operand, A: conclusion.operands[0].operand, B: conclusion.operands[1].operand }, assumptions=assumptions) elif destructive_multi_dilemma.is_usable(): # raise NotImplementedError("Generalized destructive multi-dilemma not implemented yet.") # Iterated destructive case. From (Not(A) or Not(B) or Not(C) # or Not(D)) as self negated_operands_self = [ operand.operand for operand in self.operands ] negated_operands_conc = [ operand.operand for operand in conclusion.operands ] _A = ExprTuple(*negated_operands_self) _B = ExprTuple(*negated_operands_conc) _m = _A.num_elements(assumptions) return destructive_multi_dilemma.instantiate( { m: _m, A: _A, B: _B }, assumptions=assumptions) # constructive case. if self.operands.is_double(): # From (A or B), A => C, B => D, conclude C or D. return constructive_dilemma.instantiate( { A: self.operands[0], B: self.operands[1], C: conclusion.operands[0], D: conclusion.operands[1] }, assumptions=assumptions) #raise NotImplementedError("Generalized constructive multi-dilemma not implemented yet.") _A = self.operands _B = conclusion.operands _m = _A.num_elements(assumptions) return constructive_multi_dilemma.instantiate({ m: _m, A: _A, B: _B }, assumptions=assumptions)
def eliminate(skolem_constants, judgment, **defaults_config): ''' For the provided judgment of the form S |– alpha and the tuple of Skolem constants skolem_constants that had been specified earlier using the Exists.choose(), derive and return a new judgment S' |– alpha where all assumptions in S involving only the given skolem_constants are now eliminated. This process will only work if the provided skolem_constants exactly match a set of Skolem constants used earlier in an Exists.choose() method to produce the Skolem constant-based subset of assumptions you wish to eliminate from S. ''' from proveit import Lambda from proveit import n, P, Q, alpha from proveit.logic import And from proveit.core_expr_types import (x_1_to_n, y_1_to_n) from proveit.logic.booleans.quantification.existence import ( skolem_elim) if skolem_constants not in Exists.skolem_consts_to_existential: raise KeyError("In calling Exists.eliminate(), the Skolem " "constants provided were: {}, but you can only " "eliminate Skolem constants that were chosen " "earlier when using Exists.choose() and the " "Skolem constants to be eliminated must appear " "exactly as specified in the original " "Exists.choose() method.".format(skolem_constants)) existential = Exists.skolem_consts_to_existential[skolem_constants] skolem_assumptions = set( existential.choose(*skolem_constants, print_message=False)) with defaults.temporary() as temp_defaults: temp_defaults.assumptions = ( assumption for assumption in defaults.assumptions if assumption not in skolem_assumptions) _P = Lambda(existential.instance_params, existential.instance_expr) if hasattr(existential, 'condition'): _Q = Lambda(existential.instance_params, existential.condition) else: # There is no condition but we still need to provide # something for _Q so we provide an empty conjunction, # And(). _Q = Lambda(existential.instance_params, And()) _alpha = judgment _n = existential.instance_params.num_elements() x_1_to__n = ExprTuple(x_1_to_n.basic_replaced({n: _n})) y_1_to__n = ExprTuple(y_1_to_n.basic_replaced({n: _n})) # express the judgment as an implication to match details of # the skolem_elim theorem being instantiated further below P_implies_alpha = _alpha.as_implication(hypothesis=_P.apply( *skolem_constants)) # the generalization to further match theorem details # can be handled through automation # P_implies_alpha.generalize( # skolem_constants, # conditions=[_Q.apply(*skolem_constants)]) return skolem_elim.instantiate( { n: _n, P: _P, Q: _Q, alpha: _alpha, x_1_to__n: skolem_constants, y_1_to__n: existential.instance_params }, preserve_all=True).derive_consequent()
def factorization_of_scalars(self, **defaults_config): ''' Prove equality with a Qmult in which the complex numbers are pulled to the front andassociated together via Mult. For example, (a A b B c C d D e) = ((a*b*c*d*e) A B C D) where a, b, c, d, and e are complex numbers, '*' denotes number multiplication, and spaces, here, denote the Qmult operation. Also see scalar_mult_factorization. ''' from . import (QmultCodomain, qmult_pulling_scalar_out_front, qmult_pulling_scalars_out_front, qmult_scalar_association) expr = self eq = TransRelUpdater(expr) # First, lets prove the Qmult is well-formed and, in the # process, ensure to know which operands are Complex. if not InClass(self, QmultCodomain).proven(): QmultCodomain.membership_object(self).conclude() # Go through the operands in reverse order so the complex # factors will be in the original order out front in the end. n_complex_entries = 0 for _k, operand in enumerate(reversed(self.operands.entries)): _idx = self.operands.num_entries() - _k - 1 + n_complex_entries if InSet(operand, Complex).proven(): # We have a complex number to pull out in front. _A = expr.operands[:_idx] _b = operand _C = expr.operands[_idx + 1:] _l = _A.num_elements() _n = _C.num_elements() expr = eq.update( qmult_pulling_scalar_out_front.instantiate( { l: _l, n: _n, b: _b, A: _A, C: _C }, preserve_all=True)) n_complex_entries += 1 elif isinstance(operand, ExprRange): if ExprRange(operand.parameter, InSet(operand.body, Complex), operand.true_start_index, operand.true_end_index).proven(): # We have a range of complex numbers to pull out in # front. _A = expr.operands[:_idx] _b = ExprTuple(operand) _C = expr.operands[_idx + 1:] _j = _b.num_elements() _l = _A.num_elements() _n = _C.num_elements() thm = qmult_pulling_scalars_out_front expr = eq.update( thm.instantiate( { j: _j, l: _l, n: _n, b: _b, A: _A, C: _C }, preserve_all=True)) n_complex_entries += 1 # Associate the complex numbers, now out in front. _b = expr.operands[:n_complex_entries] _A = expr.operands[n_complex_entries:] _j = _b.num_elements() _l = _A.num_elements() if (_b.num_entries() > 0 and not _b.is_single() and _A.num_entries() > 0): expr = eq.update( qmult_scalar_association.instantiate( { j: _j, l: _l, b: _b, A: _A }, preserve_expr=expr)) # The multiplication of complex numbers is complex. expr.operands[0].deduce_in_number_set(Complex) return eq.relation