def extract_var_tuple_indices(indexed_var_tuple): ''' Given an ExprTuple of only IndexedVar and ExprRange entries, returns an ExprTuple of just the corresponding indices (including ranges of indices and nested ranges of indices). ''' from proveit import IndexedVar, ExprRange from proveit._core_.expression.operation.indexed_var import extract_indices indices = [] if not isinstance(indexed_var_tuple, ExprTuple): raise TypeError("'indexed_var_tuple' must be an ExprTuple") for entry in indexed_var_tuple: if isinstance(entry, IndexedVar): entry_indices = extract_indices(entry) if len(entry_indices) == 1: indices.append(entry_indices[0]) else: indices.append(entry_indices) elif isinstance(entry, ExprRange): inner_indices = extract_var_tuple_indices(ExprTuple(entry.body)) assert len(inner_indices) == 1 body = inner_indices[0] indices.append( ExprRange(entry.parameter, body, entry.start_index, entry.end_index)) else: raise TypeError("'var_range' must be an ExprTuple only of " "IndexedVar or (nested) ExprRange entries.") return ExprTuple(*indices)
def entry_end(entry): if isinstance(entry, ExprRange): if isinstance(entry.body, ExprRange): # Return an ExprRange of lambda maps. return ExprRange(entry.parameter, entry.body.end_index, entry.start_index, entry.end_index) else: return entry.end_index return one # for individual elements, use start=end=1
def var_array(var, start_index_or_indices, end_index_or_indices, array_type=ExprArray): from proveit import (safe_dummy_vars, composite_expression, IndexedVar) from .vert_expr_array import VertExprArray start_indices = composite_expression(start_index_or_indices) end_indices = composite_expression(end_index_or_indices) if array_type not in (ExprArray, VertExprArray, ExprTuple): raise ValueError("Not a valid array type: %s" % array_type) if not (start_indices.num_entries() == end_indices.num_entries() == 2): raise ValueError("Expecting two start indices and two end indices " "when creating a Variable array") _i, _j = safe_dummy_vars(2, var, start_indices, end_indices) return array_type( ExprRange(_i, [ ExprRange(_j, IndexedVar(var, [_i, _j]), start_indices[1], end_indices[1]) ], start_indices[0], end_indices[0]))
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 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 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 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 known_vec_spaces(vecs, *, field=None): ''' Return the known vector spaces of the given vecs under the specified field (or the default field). ''' # Effort to appropriately handle an ExprRange operand added # here by wdc and ww on 1/3/2022. vec_spaces = [] for vec in vecs: if isinstance(vec, ExprRange): # create our expr range with defaults.temporary() as tmp_defaults: assumption = InSet(vec.parameter, Interval(vec.true_start_index, vec.true_end_index)) tmp_defaults.assumptions = ( defaults.assumptions + (assumption ,)) body = VecSpaces.known_vec_space(vec.body, field=field) vec_spaces.append( ExprRange(vec.parameter, body, vec.true_start_index, vec.true_end_index)) else: vec_spaces.append(VecSpaces.known_vec_space(vec, field=field)) return vec_spaces
def _get_cart_exps_field_and_exponents(self): ''' Helper for derive_cart_exp_membership. ''' domain = self.domain def raise_invalid(): raise ValueError("'derive_cart_exp_membership' only applicable on " "a TensorProdMembership where the domain is a " "tensor product of Cartesian exponentials on the " "same field, not %s" % domain) assert isinstance(domain, TensorProd) _ns = [] _K = None for domain_factor in domain.factors.entries: if isinstance(domain_factor, ExprRange): if not isinstance(domain_factor.body, CartExp): raise_invalid() factor_K = domain_factor.body.base _ns.append( ExprRange(domain_factor.parameter, domain_factor.body.exponent, domain_factor.start_index, domain_factor.end_index, styles=domain_factor.get_styles())) elif isinstance(domain_factor, CartExp): factor_K = domain_factor.base _ns.append(domain_factor.exponent) else: raise_invalid() if _K is None: _K = factor_K elif _K != factor_K: raise_invalid() return _K, _ns
def deduce_number_set(expr, **defaults_config): ''' Prove that 'expr' is an Expression that represents a number in a standard number set that is as restrictive as we can readily know. ''' from proveit.logic import And, InSet, Equals, NotEquals from proveit.numbers import Less, LessEq, zero # Find the first (most restrictive) number set that # contains 'expr' or something equal to it. for number_set in sorted_number_sets: membership = None for eq_expr in Equals.yield_known_equal_expressions(expr): if isinstance(eq_expr, ExprRange): membership = And( ExprRange(eq_expr.parameter, InSet(eq_expr.body, number_set), eq_expr.true_start_index, eq_expr.true_end_index, styles=eq_expr.get_styles())) else: membership = InSet(eq_expr, number_set) if membership.proven(): break # found a known number set membership else: membership = None if membership is not None: membership = InSet(expr, number_set).prove() break if hasattr(expr, 'deduce_number_set'): # Use 'deduce_number_set' method. try: deduced_membership = expr.deduce_number_set() except (UnsatisfiedPrerequisites, ProofFailure): deduced_membership = None if deduced_membership is not None: assert isinstance(deduced_membership, Judgment) if not isinstance(deduced_membership.expr, InSet): raise TypeError("'deduce_number_set' expected to prove an " "InSet type expression") if deduced_membership.expr.element != expr: raise TypeError("'deduce_number_set' was expected to prove " "that %s is in some number set" % expr) # See if this deduced number set is more restrictive than # what we had surmised already. deduced_number_set = deduced_membership.domain if membership is None: membership = deduced_membership number_set = deduced_number_set elif (deduced_number_set != number_set and number_set.includes(deduced_number_set)): number_set = deduced_number_set membership = deduced_membership if membership is None: from proveit import defaults raise UnsatisfiedPrerequisites( "Unable to prove any number membership for %s" % expr) # Already proven to be in some number set, # Let's see if we can restrict it further. if Less(zero, expr).proven(): # positive number_set = pos_number_set.get(number_set, None) elif Less(expr, zero).proven(): # negative number_set = neg_number_set.get(number_set, None) elif LessEq(zero, expr).proven(): # non-negative number_set = nonneg_number_set.get(number_set, None) elif LessEq(expr, zero).proven(): # non-positive number_set = nonpos_number_set.get(number_set, None) elif NotEquals(expr, zero).proven(): number_set = nonzero_number_set.get(number_set, None) if number_set is None: # Just use what we have already proven. return membership.prove() return InSet(expr, number_set).prove()
def deduce_in_vec_space(self, vec_space=None, *, field, **defaults_config): ''' Prove that this linear combination of vectors is in a vector space. The vector space may be specified or inferred via known memberships. A field for the vector space must be specified. ''' from proveit.linear_algebra import ScalarMult terms = self.terms if vec_space is None: vec_space = VecSpaces.common_known_vec_space(terms, field=field) field = VecSpaces.known_field(vec_space) all_scaled = all((isinstance(term, ScalarMult) or ( isinstance(term, ExprRange) and isinstance(term.body, ScalarMult))) for term in terms) if all_scaled: # Use a linear combination theorem since everything # is scaled. from proveit.linear_algebra.scalar_multiplication import ( binary_lin_comb_closure, lin_comb_closure) if terms.is_double(): # Special linear combination binary case _a, _b = terms[0].scalar, terms[1].scalar _x, _y = terms[0].scaled, terms[1].scaled return binary_lin_comb_closure.instantiate({ K: field, V: vec_space, a: _a, b: _b, x: _x, y: _y }) else: # General linear combination case _a = [] _x = [] for term in terms: if isinstance(term, ExprRange): _a.append( ExprRange(term.parameter, term.body.scalar, term.true_start_index, term.true_end_index)) _x.append( ExprRange(term.parameter, term.body.scaled, term.true_start_index, term.true_end_index)) else: _a.append(term.scalar) _x.append(term.scaled) _n = terms.num_elements() return lin_comb_closure.instantiate({ n: _n, K: field, V: vec_space, a: _a, x: _x }) else: # Use a vector addition closure theorem. from . import binary_closure, closure if terms.is_double(): # Special binary case return binary_closure.instantiate({ K: field, V: vec_space, x: terms[0], y: terms[1] }) else: # General case _n = terms.num_elements() return closure.instantiate({ n: _n, K: field, V: vec_space, x: terms })
def get_format_cell_entries(self, format_cell_entries=None): ''' Returns a list of (for the most part) lists of entries in correspondence with each format cell of this ExprArray. It is possible to have an entry that represents an entire inner list (an entire row, or column in the case of a VertExprArray). Each entry is a pair or triple tuple with the first item containing an Expression corresponding to the entry and the others indicating the role of the cell. It will be composed of the role within the outer ExprTuple and the role within the inner ExprTuple respectively with the following exception. For a ExprRange of ExprTuples of ExprRanges with explicit parameterization, the explicit parameterization will be shown compactly only in the center of the range of ranges but will be made implicit in the cells above/below/left/right. The 'role' information will be used to determine how to format the cell with respect to using horizontal/vertical ellipses, etc. If 'format_cell_entries' is provided, append to it rather than creating a new list. ''' # Construct the list of (for the most part) lists of entries # first by simply composing outer and inner roles. # Remember coordinates of entries that are 'explicit' outside # and inside -- we'll edit their surrounding entries next. if format_cell_entries is None: format_cell_entries = [] else: if not isinstance(format_cell_entries, list): raise TypeError("Expecting 'format_cell_entries' to be a " "list") doubly_explicit_coordinates = [] for _i, outer_cell_info in enumerate( ExprTuple.yield_format_cell_info(self)): (outer_expr, outer_role), assumptions = outer_cell_info orig_outer_expr = outer_expr if isinstance(outer_expr, ExprRange): outer_expr = outer_expr.innermost_body() # But then wrap the inner_expr's below with the # ExprRange's if the inner_expr is not an ExprRange. if isinstance(outer_expr, ExprTuple): # An explicit inner list. with defaults.temporary() as tmp_defaults: tmp_defaults.automation = False tmp_defaults.assumptions = assumptions inner_format_cell_entries = [] for _j, inner_cell_info in enumerate( outer_expr.yield_format_cell_info()): (inner_expr, inner_role), _ = inner_cell_info if (orig_outer_expr != outer_expr and not isinstance(inner_expr, ExprRange)): # The other_expr was originally an # ExprRange but the inner_expr is not. In # this case, we want to show the entry expr # as an ExprRange for the outer orientation # with this inner expression. That is, # let's wrap the inner_expr with the # ExprRange(s) in the same way as the # original ExprRange. _expr_ranges = [orig_outer_expr] while isinstance(_expr_ranges[-1].body, ExprRange): _expr_ranges.append(_expr_ranges[-1].body) for _er in reversed(_expr_ranges): inner_expr = ExprRange( _er.parameter, inner_expr, _er.true_start_index, _er.true_end_index, use_canonical_parameter=False) inner_expr = inner_expr.with_mimicked_style( _er, ignore_inapplicable_styles=True) # Compose outer and inner roles. inner_format_cell_entries.append( (inner_expr, outer_role, inner_role)) if outer_role == inner_role == 'explicit': doubly_explicit_coordinates.append((_i, _j)) format_cell_entries.append(inner_format_cell_entries) else: # Represent an entire inner list with an entry. format_cell_entries.append((orig_outer_expr, outer_role)) # Where roles are 'explicit' outside and inside, we'll make # surrounding roles be implicit for a more compact # representation (avoiding redundant information). for (_i, _j) in doubly_explicit_coordinates: # Make implicit "above" (before w.r.t. outer level) _k = 1 while True: expr, outer_role, inner_role = format_cell_entries[_i - _k][_j] format_cell_entries[_i - _k][_j] = (expr, outer_role, 'implicit') if format_cell_entries[_i - _k][_j][1] == 0: break # First of the ExprRange -- done. _k += 1 # Make implicit "below" (after w.r.t. outer level) expr, outer_role, inner_role = format_cell_entries[_i + 1][_j] format_cell_entries[_i + 1][_j] = (expr, outer_role, 'implicit') # Make implicit to the "left" (before w.r.t. inner level). _k = 1 while True: expr, outer_role, inner_role = format_cell_entries[_i][_j - _k] format_cell_entries[_i][_j - _k] = (expr, 'implicit', inner_role) if format_cell_entries[_i][_j - _k][2] == 0: break # First of the ExprRange -- done. _k += 1 # Make implicit to the "right" (after w.r.t. inner level). expr, outer_role, inner_role = format_cell_entries[_i][_j + 1] format_cell_entries[_i][_j + 1] = (expr, 'implicit', inner_role) return format_cell_entries
def deduce_equal_or_not(self, other_tuple, **defaults_config): ''' Prove and return that this ExprTuple is either equal or not equal to other_tuple or raises an UnsatisfiedPrerequisites or NotImplementedError if we cannot readily prove either of these. ''' from proveit import (ExprRange, safe_dummy_var, UnsatisfiedPrerequisites) from proveit.logic import ( And, Or, Equals, NotEquals, deduce_equal_or_not) if self == other_tuple: return Equals(self, other_tuple).conclude_via_reflexivity if not isinstance(other_tuple, ExprTuple): raise TypeError("Expecting 'other_tuple' to be an ExprTuple " "not a %s"%other_tuple.__class__) _i = self.num_elements() _j = other_tuple.num_elements() size_relation = deduce_equal_or_not(_i, _j) if isinstance(size_relation.expr, NotEquals): # Not equal because the lengths are different. return self.not_equal(other_tuple) def raise_non_corresponding(): raise NotImplementedError( "ExprTuple.deduce_equal_or_not is only " "implemented for the case when ExprRanges " "match up: %s vs %s"%self, other_tuple) if self.num_entries() == other_tuple.num_entries(): if self.num_entries()==1 and self.contains_range(): if not other_tuple.contains_range(): # One ExprTuple has a range but the other doesn't. # That case isn't handled. raise_non_corresponding() lhs_range = self.entries[0] rhs_range = other_tuple.entries[0] start_index = lhs_range.start_index end_index = lhs_range.end_index if ((start_index != rhs_range.start_index) or (end_index != rhs_range.end_index)): # Indices must match for a proper correspondence. raise_non_corresponding() if lhs_range.parameter != rhs_range.parameter: # Use a safe common parameter. param = safe_dummy_var(lhs_range.body, rhs_range.body) lhs_range_body = lhs_range.body.basic_replaced( {lhs_range.parameter: param}) rhs_range_body = rhs_range.body.basic_replaced( {rhs_range.parameter: param}) else: param = lhs_range.parameter lhs_range_body = lhs_range.body rhs_range_body = rhs_range.body inner_assumptions = defaults.assumptions + ( lhs_range.parameter_condition(),) try: body_relation = deduce_equal_or_not( lhs_range_body, rhs_range_body, assumptions=inner_assumptions) if isinstance(body_relation, Equals): # Every element is equal, so the ExprTuples # are equal. return self.deduce_equality( Equals(self, other_tuple)) else: # Every element is not equal, so the ExprTuples # are not equal. # This will enable "any" from "all". And(ExprRange( param, NotEquals(lhs_range_body, rhs_range_body), start_index, end_index)).prove() return self.not_equal(other_tuple) except (NotImplementedError, UnsatisfiedPrerequisites): pass if And(ExprRange(param, Equals(lhs_range_body, rhs_range_body), start_index, end_index)).proven(): # Every element is equal, so the ExprTuples # are equal. return self.deduce_equality( Equals(self, other_tuple)) elif Or(ExprRange(param, NotEquals(lhs_range_body, rhs_range_body), start_index, end_index)).proven(): # Some element pair is not equal, so the ExprTuples # are not equal. return self.not_equal(other_tuple) raise UnsatisfiedPrerequisites( "Could not determine whether %s = %s" %(self, other_tuple)) # Loop through each entry pair in correspondence and # see if we can readily prove whether or not they are # all equal. for idx, (_x, _y) in enumerate( zip(self.entries, other_tuple.entries)): if isinstance(_x, ExprRange) != isinstance(_y, ExprRange): raise_non_corresponding() if _x == _y: # The expressions are the same, so we know they # are equal. continue if isinstance(_x, ExprRange): # Wrap ExprRanges in ExprTuples and compare as # single entry tuples. _x = ExprTuple(_x) _y = ExprTuple(_y) _k = _x.num_elements() _l = _y.num_elements() size_relation = deduce_equal_or_not(_k, _l) if isinstance(size_relation.expr, NotEquals): # Not implemented when the ExprRanges don't # correspond in size. raise_non_corresponding() relation = deduce_equal_or_not(_x, _y) else: # Compare singular entries. relation = deduce_equal_or_not(_x, _y) if isinstance(relation.expr, NotEquals): # Aha! They are not equal. return self.not_equal(other_tuple) # They are equal! return self.deduce_equality(Equals(self, other_tuple)) raise NotImplementedError( "ExprTuple.deduce_equal_or_not is not implemented " "for ExprTuples that have a different number of " "elements.")
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