def simplification(self, *, must_evaluate=False, **defaults_config): ''' Proves that this ExprTuple is equal to an ExprTuple with all of its entries simplified (and ExprRanges reduced). ''' from proveit.relation import TransRelUpdater from proveit import ExprRange expr = self eq = TransRelUpdater(expr) _k = 0 for entry in self.entries: if isinstance(entry, ExprRange): entry_simp = entry.reduction(preserve_all=True) num_entries = entry_simp.rhs.num_entries() else: if must_evaluate: entry_simp = entry.evaluation() else: entry_simp = entry.simplification() num_entries = 1 if entry_simp.lhs != entry_simp.rhs: expr = eq.update(expr.substitution(entry_simp, start_idx=_k)) _k += num_entries return eq.relation
def simplification_of_operands(self, **defaults_config): ''' Prove this Operation equal to a form in which its operands have been simplified. ''' from proveit.relation import TransRelUpdater from proveit import ExprRange, NamedExprs from proveit.logic import is_irreducible_value if any(isinstance(operand, ExprRange) for operand in self.operands): # If there is any ExprRange in the operands, simplify the # operands together as an ExprTuple. return self.inner_expr().operands[:].simplification() else: expr = self eq = TransRelUpdater(expr) with defaults.temporary() as temp_defaults: # No auto-simplification or replacements here; # just simplify operands one at a time. temp_defaults.preserve_all = True operands = self.operands if isinstance(operands, NamedExprs): # operands as NamedExprs for key in operands.keys(): operand = operands[key] if not is_irreducible_value(operand): inner_operand = getattr(expr.inner_expr(), key) expr = eq.update(inner_operand.simplification()) else: # operands as ExprTuple for k, operand in enumerate(operands): if not is_irreducible_value(operand): inner_operand = expr.inner_expr().operands[k] expr = eq.update(inner_operand.simplification()) return eq.relation
def _redundant_mod_elimination( expr, mod_elimination_thm, mod_elimination_in_sum_thm): ''' For use by Mod and ModAbs for shallow_simplification. ''' dividend = expr.dividend divisor = expr.divisor if isinstance(dividend, Mod) and dividend.divisor==divisor: # [(a mod b) mod b] = [a mod b] return mod_elimination_thm.instantiate( {a:dividend.dividend, b:divisor}) elif isinstance(dividend, Add): # Eliminate 'mod L' from each term. eq = TransRelUpdater(expr) _L = divisor mod_terms = [] for _k, term in enumerate(dividend.terms): if isinstance(term, Mod) and term.divisor==_L: mod_terms.append(_k) for _k in mod_terms: # Use preserve_all=True for all but the last term. preserve_all = (_k != mod_terms[-1]) _a = expr.dividend.terms[:_k] _b = expr.dividend.terms[_k].dividend _c = expr.dividend.terms[_k+1:] _i = _a.num_elements() _j = _c.num_elements() expr = eq.update( mod_elimination_in_sum_thm .instantiate( {i:_i, j:_j, a:_a, b:_b, c:_c, L:_L}, preserve_all=preserve_all)) return eq.relation return Equals(expr, expr).conclude_via_reflexivity()
def simplification(self, assumptions=USE_DEFAULTS, automation=True): if not automation: return Expression.simplification(self, assumptions, automation=False) from proveit.relation import TransRelUpdater eq = TransRelUpdater(self, assumptions) expr = eq.update(self.computation(assumptions)) expr = eq.update(expr.simplification(assumptions)) return eq.relation
def shallow_simplification(self, *, must_evaluate=False, **defaults_config): ''' Returns a proven simplification equation for this NumKet expression assuming the operands have been simplified. Currently deals only with: (1) simplifying a NumKet with register size = 1 to a simple Ket. It's not immediately clear that we always want to do such a thing, but here goes. ''' if self.size == one: # from . import single_qubit_register_ket from . import single_qubit_num_ket return single_qubit_num_ket.instantiate( {b: self.num}, preserve_all=True) # Else simply return self=self. # Establishing some minimal infrastructure # for future development expr = self # for convenience updating our equation: eq = TransRelUpdater(expr) # Future processing possible here. return eq.relation
def do_reduced_simplification(self, assumptions=USE_DEFAULTS): ''' For trivial cases, a zero or one exponent or zero or one base, derive and return this exponential expression equated with a simplified form. Assumptions may be necessary to deduce necessary conditions for the simplification. ''' from proveit.logic import Equals, InSet from proveit.numbers import one, two, Rational, Real, Abs from proveit.relation import TransRelUpdater from . import complex_x_to_first_power_is_x if self.exponent == one: return complex_x_to_first_power_is_x.instantiate({a: self.base}) if (isinstance(self.base, Exp) and isinstance(self.base.exponent, Div) and self.base.exponent.numerator == one and self.base.exponent.denominator == self.exponent): from . import nth_power_of_nth_root _n, _x = nth_power_of_nth_root.instance_params return nth_power_of_nth_root.instantiate( { _n: self.exponent, _x: self.base.base }, assumptions=assumptions) expr = self # for convenience updating our equation: eq = TransRelUpdater(expr, assumptions) if self.exponent == two and isinstance(self.base, Abs): from . import (square_abs_rational_simp, square_abs_real_simp) # |a|^2 = a if a is real rational_base = InSet(self.base, Rational).proven(assumptions) real_base = InSet(self.base, Real).proven(assumptions) thm = None if rational_base: thm = square_abs_rational_simp elif real_base: thm = square_abs_real_simp if thm is not None: simp = thm.instantiate({a: self.base.operand}, assumptions=assumptions) expr = eq.update(simp) # A further simplification may be possible after # eliminating the absolute value. expr = eq.update(expr.simplification(assumptions)) return eq.relation
def doReducedSimplification(self, assumptions=USE_DEFAULTS, **kwargs): ''' Derive and return this negation expression equated with a simpler form. Deals with double negation specifically. ''' from proveit.relation import TransRelUpdater expr = self # For convenience updating our equation: eq = TransRelUpdater(expr, assumptions) # Handle double negation: if isinstance(self.operand, Neg): # simplify double negation expr = eq.update(self.doubleNegSimplification(assumptions)) # simplify what is inside the double-negation. expr = eq.update(expr.simplification(assumptions)) return eq.relation
def simplification(self, **defaults_config): from proveit.relation import TransRelUpdater from proveit.logic import And expr = self eq = TransRelUpdater(expr) if True: #not self.value.is_simplified(): # Simplify the 'value'. expr = eq.update( expr.value_substitution(self.value.simplification())) if isinstance(self.condition, And): # Simplify the conditions. if True: #any(not condition.is_simplified() for condition # in self.condition.operands.entries): inner_condition = expr.inner_expr().condition expr = eq.update(inner_condition.simplification_of_operands()) elif True: #not self.condition.is_simplified(): # Simplify the condition. expr = eq.update( expr.condition_substitution(self.condition.simplification())) # Perform a shallow simplifation on this Conditional. # If the expression has not been reduced yet, no need for an # "evaluation check" by the @prover decorator. _no_eval_check = (expr == self) eq.update(expr.shallow_simplification(_no_eval_check=_no_eval_check)) return eq.relation
def sub_expr_substitution(self, new_sub_exprs, **defaults_config): ''' Given new sub-expressions to replace existing sub-expressions, return the equality between this Expression and the new one with the new sub-expressions. ''' from proveit.logic import Equals from proveit.relation import TransRelUpdater assert len(new_sub_exprs) == 2, ( "Expecting 2 sub-expressions: operator and operands") eq = TransRelUpdater(self) expr = self if new_sub_exprs[0] != self.sub_expr(0): expr = eq.update( expr.operator_substitution( Equals(self.sub_expr(0), new_sub_exprs[0]))) if new_sub_exprs[1] != self.sub_expr(1): expr = eq.update( expr.operands_substitution( Equals(self.sub_expr(1), new_sub_exprs[1]))) return eq.relation
def distribution(self, assumptions=USE_DEFAULTS): ''' Distribute negation through a sum, deducing and returning the equality between the original and distributed forms. ''' from ._theorems_ import distributeNegThroughBinarySum from ._theorems_ import distributeNegThroughSubtract, distributeNegThroughSum from proveit.number import Add, num from proveit.relation import TransRelUpdater expr = self eq = TransRelUpdater( expr, assumptions) # for convenience updating our equation if isinstance(self.operand, Add): # Distribute negation through a sum. add_expr = self.operand if len(add_expr.operands) == 2: # special case of 2 operands if isinstance(add_expr.operands[1], Neg): expr = eq.update( distributeNegThroughSubtract.specialize( { a: add_expr.operands[0], b: add_expr.operands[1].operand }, assumptions=assumptions)) else: expr = eq.update( distributeNegThroughBinarySum.specialize( { a: add_expr.operands[0], b: add_expr.operands[1] }, assumptions=assumptions)) else: # distribute the negation over the sum expr = eq.update(distributeNegThroughSum.specialize({ n: num(len(add_expr.operands)), xx: add_expr.operands }), assumptions=assumptions) assert isinstance( expr, Add ), "distributeNeg theorems are expected to yield an Add expression" # check for double negation for k, operand in enumerate(expr.operands): assert isinstance( operand, Neg ), "Each term from distributeNegThroughSum is expected to be negated" if isinstance(operand.operand, Neg): expr = eq.update( expr.innerExpr().operands[k].doubleNegSimplification()) return eq.relation else: raise Exception( 'Only negation distribution through a sum or subtract is implemented' )
def shallow_simplification(self, *, must_evaluate=False, **defaults_config): ''' Proves that this ExprTuple is equal to an ExprTuple with ExprRanges reduced unless these are "preserved" expressions. ''' from proveit.relation import TransRelUpdater from proveit import ExprRange from proveit.logic import is_irreducible_value, EvaluationError expr = self eq = TransRelUpdater(expr) if defaults.preserve_all: # Preserve all sub-expressions -- don't simplify. return eq.relation _k = 0 for entry in self.entries: if isinstance(entry, ExprRange): if entry in defaults.preserved_exprs: if must_evaluate: # An ExprRange is not irreducible. raise EvaluationError(self) # Preserve this entry -- don't simplify it. _k += ExprTuple(entry).num_entries() continue entry_simp = entry.reduction() if must_evaluate and not is_irreducible_value(entry_simp.rhs): raise EvaluationError(self) if entry_simp.lhs != entry_simp.rhs: substitution = expr.substitution( entry_simp, start_idx=_k, preserve_all=True) expr = eq.update(substitution) _k += entry_simp.rhs.num_entries() else: if must_evaluate and not is_irreducible_value(entry): raise EvaluationError(self) _k += 1 return eq.relation
def conclude_via_inout_consolidation(self, **defaults_config): ''' Prove this circuit equivalence by consolidating the input (at the beginning) and/or output (at the end). ''' lhs_cols, rhs_cols = (self.lhs.vert_expr_array.entries, self.rhs.vert_expr_array.entries) if len(lhs_cols) != len(rhs_cols): raise ValueError( "Cannot perform 'conclude_via_inout_consolidation' " "if the number of columns is different: %s ≠ %s." % (len(lhs_cols), len(rhs_cols))) if not all( lhs_col == rhs_col for lhs_col, rhs_col in zip(lhs_cols[1:-1], rhs_cols[1:-1])): raise ValueError( "Cannot perform 'conclude_via_inout_consolidation' " "if the inner columns do not all match.") expr = self.lhs # Use this updater to go from the lhs to the rhs via # transitive equivalences. equiv = TransRelUpdater(expr) if lhs_cols[0] != rhs_cols[0]: # The first column does not match, so try to consolidate # inputs. lhs_consolidation = Qcircuit(VertExprArray( lhs_cols[0])).input_consolidation() expr = equiv.update(lhs_consolidation.substitution(expr)) rhs_consolidation = Qcircuit(VertExprArray( rhs_cols[0])).input_consolidation() input_equiv = QcircuitEquiv(lhs_consolidation.rhs, rhs_consolidation.rhs).prove() expr = equiv.update(input_equiv.substitution(expr)) expr = equiv.update(rhs_consolidation.substitution(expr)) if lhs_cols[-1] != rhs_cols[-1]: # The first column does not match, so try to consolidate # inputs. lhs_consolidation = Qcircuit(VertExprArray( lhs_cols[-1])).output_consolidation() expr = equiv.update(lhs_consolidation.substitution(expr)) rhs_consolidation = Qcircuit(VertExprArray( rhs_cols[-1])).output_consolidation() output_equiv = QcircuitEquiv(lhs_consolidation.rhs, rhs_consolidation.rhs).prove() expr = equiv.update(output_equiv.substitution(expr)) expr = equiv.update(rhs_consolidation.substitution(expr)) return equiv.relation
def simplification(self, assumptions=USE_DEFAULTS): ''' Derive and return this negation expression equated with a simpler form. Deals with double negation specifically. ''' from proveit.number import zero from proveit.logic import Equals from ._theorems_ import doubleNegation from proveit.relation import TransRelUpdater # Handle double negation: if isinstance(self.operand, Neg): expr = self eq = TransRelUpdater( expr, assumptions) # for convenience updating our equation # simplify double negation expr = eq.update(self.doubleNegSimplification(assumptions)) # simplify what is inside the double-negation. expr = eq.update(expr.simplification(assumptions)) return eq.relation # otherwise, just use the default simplification return Operation.simplification(self, assumptions)
def distribution(self, assumptions=USE_DEFAULTS): ''' Distribute negation through a sum, deducing and returning the equality between the original and distributed forms. ''' from . import distribute_neg_through_binary_sum from . import distribute_neg_through_subtract, distribute_neg_through_sum from proveit.numbers import Add from proveit.relation import TransRelUpdater expr = self # for convenience updating our equation eq = TransRelUpdater(expr, assumptions) if isinstance(self.operand, Add): # Distribute negation through a sum. add_expr = self.operand if add_expr.operands.is_double(): # special case of 2 operands if isinstance(add_expr.operands[1], Neg): expr = eq.update(distribute_neg_through_subtract.instantiate( {a: add_expr.operands[0], b: add_expr.operands[1].operand}, assumptions=assumptions)) else: expr = eq.update(distribute_neg_through_binary_sum.instantiate( {a: add_expr.operands[0], b: add_expr.operands[1]}, assumptions=assumptions)) else: # distribute the negation over the sum _x = add_expr.operands _n = _x.num_elements(assumptions) expr = eq.update(distribute_neg_through_sum.instantiate( {n: _n, x: _x}), assumptions=assumptions) assert isinstance( expr, Add), "distribute_neg theorems are expected to yield an Add expression" # check for double negation for k, operand in enumerate(expr.operands): assert isinstance( operand, Neg), "Each term from distribute_neg_through_sum is expected to be negated" if isinstance(operand.operand, Neg): expr = eq.update( expr.inner_expr().operands[k].double_neg_simplification()) return eq.relation else: raise Exception( 'Only negation distribution through a sum or subtract is implemented')
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
def merger(self, **defaults_config): ''' If this is an tuple of expressions that can be directly merged together into a single ExprRange, return this proven equivalence. For example, {j \in Natural, k-(j+1) \in Natural} |- (x_1, .., x_j, x_{j+1}, x_{j+2}, ..., x_k) = (x_1, ..., x_k) ''' from proveit._core_.expression.lambda_expr import ( Lambda, ArgumentExtractionError) from .expr_range import ExprRange, simplified_index from proveit.relation import TransRelUpdater from proveit.core_expr_types.tuples import ( merge, merge_front, merge_back, merge_extension, merge_pair, merge_series) from proveit import f, i, j, k, l, x from proveit.numbers import one, Add, subtract # A convenience to allow successive update to the equation via # transitivities (starting with self=self). eq = TransRelUpdater(self) # Determine the position of the first ExprRange item and get the # lambda map. first_range_pos = len(self.entries) lambda_map = None for _k, item in enumerate(self): if isinstance(item, ExprRange): lambda_map = Lambda(item.lambda_map.parameter, item.lambda_map.body) first_range_pos = _k break if 1 < first_range_pos: if lambda_map is None: raise NotImplementedError("Means of extracting a lambda " "map has not been implemented") pass # need the lambda map # Collapse singular items at the beginning. front_singles = ExprTuple(eq.expr[:first_range_pos]) i_sub = lambda_map.extract_argument(front_singles[0]) j_sub = lambda_map.extract_argument(front_singles[-1]) if len(front_singles.entries) == 2: # Merge a pair of singular items. front_merger = merge_pair.instantiate( {f: lambda_map, i: i_sub, j: j_sub}) else: # Merge a series of singular items in one shot. front_merger = merge_series.instantiate( {f: lambda_map, x: front_singles, i: i_sub, j: j_sub}) eq.update( front_merger.substitution( self.inner_expr()[:first_range_pos])) if eq.expr.num_entries() == 1: # We have accomplished a merger down to one item. return eq.relation if eq.expr.num_entries() == 2: # Merge a pair. if isinstance(eq.expr[0], ExprRange): if isinstance(eq.expr[1], ExprRange): # Merge a pair of ExprRanges. item = eq.expr[1] other_lambda_map = Lambda(item.lambda_map.parameter, item.lambda_map.body) if other_lambda_map != lambda_map: raise ExprTupleError( "Cannot merge together ExprRanges " "with different lambda maps: %s vs %s" % (lambda_map, other_lambda_map)) _i, _j = eq.expr[0].true_start_index, eq.expr[0].true_end_index _k, _l = eq.expr[1].true_start_index, eq.expr[1].true_end_index merger = \ merge.instantiate( {f: lambda_map, i: _i, j: _j, k: _k, l: _l}) else: # Merge an ExprRange and a singular item. _i, _j = eq.expr[0].true_start_index, eq.expr[0].true_end_index try: _k = lambda_map.extract_argument(eq.expr[1]) except ArgumentExtractionError: _k = simplified_index(Add(_j, one)) if _k == Add(_j, one): merger = merge_extension.instantiate( {f: lambda_map, i: _i, j: _j}) else: merger = merge_back.instantiate( {f: lambda_map, i: _i, j: _j, k: _k}) else: # Merge a singular item and ExprRange. _i = simplified_index( subtract(eq.expr[1].true_start_index, one)) _j, _k = eq.expr[1].true_start_index, eq.expr[1].true_end_index merger = \ merge_front.instantiate({f: lambda_map, i: _i, j: _j, k: _k}) all_decreasing = all(expr.is_decreasing() for expr in eq.expr if isinstance(expr, ExprRange)) if all_decreasing: # Apply the 'decreasing' order style to match what we # had originally. for _i in (0, 1): if isinstance(eq.expr[_i], ExprRange): merger = (merger.inner_expr().lhs[_i] .with_decreasing_order()) merger = merger.inner_expr().rhs[0].with_decreasing_order() eq.update(merger) return eq.relation while eq.expr.num_entries() > 1: front_merger = ExprTuple(*eq.expr[:2].entries).merger() eq.update(front_merger.substitution( eq.expr.inner_expr()[:2])) return eq.relation
def deduce_equality(self, equality, *, eq_via_elem_eq_thm=None, **defaults_config): from proveit import ExprRange from proveit import a, b, i from proveit.logic import Equals from proveit.core_expr_types.tuples import tuple_eq_via_elem_eq from proveit.relation import TransRelUpdater if not isinstance(equality, Equals): raise ValueError("The 'equality' should be an Equals expression") if equality.lhs != self: raise ValueError("The left side of 'equality' should be 'self'") from proveit.numbers import num, one # Handle the special counting cases. For example, # (1, 2, 3, 4) = (1, ..., 4) _n = len(self.entries) if all(self[_k] == num(_k + 1) for _k in range(_n)): if (isinstance(equality.rhs, ExprTuple) and equality.rhs.num_entries() == 1 and isinstance(equality.rhs[0], ExprRange)): expr_range = equality.rhs[0] if (expr_range.true_start_index == one and expr_range.true_end_index == num(_n)): if len(self.entries) >= 10: raise NotImplementedError("counting range equality " "not implemented for more " "then 10 elements") import proveit.numbers.numerals.decimals equiv_thm = proveit.numbers.numerals.decimals\ .__getattr__('count_to_%d_range' % _n) return equiv_thm lhs, rhs = equality.lhs, equality.rhs if (lhs.num_entries() == rhs.num_entries() == 1 and isinstance(lhs[0], ExprRange) and isinstance(rhs[0], ExprRange)): # Prove the equality of two ExprRanges. r_range = rhs[0] expr = lhs if expr[0].is_decreasing(): # We could handle different styles later, but # let's be consistent with increasing order for now # to make this easier to implement. expr = expr.inner_expr()[0].with_increasing_order() eq = TransRelUpdater(expr) if expr[0].true_start_index != r_range.true_start_index: # Shift indices so they have the same start. expr = eq.update(expr[0].shift_equivalence( new_start=r_range.true_start_index)) if expr[0].lambda_map != r_range.lambda_map: # Change the lambda map. expr = eq.update(expr[0].range_fn_transformation( r_range.lambda_map)) if expr[0].true_end_index != r_range.true_end_index: # Make the end indices be the same: end_eq = Equals(expr[0].true_end_index, r_range.true_end_index).prove() expr = eq.update(end_eq.substitution( expr.inner_expr()[0].true_end_index)) return eq.relation # Try tuple_eq_via_elem_eq as the last resort. _i = lhs.num_elements() _a = lhs _b = rhs if eq_via_elem_eq_thm is None: eq_via_elem_eq_thm = tuple_eq_via_elem_eq return eq_via_elem_eq_thm.instantiate({i:_i, a:_a, b:_b})
def bundle(expr, bundle_thm, num_levels=2, *, assumptions=USE_DEFAULTS): ''' Given a nested OperationOverInstances, derive or equate an equivalent form in which a given number of nested levels is bundled together. Use the given theorem specific to the particular OperationOverInstances. For example, \forall_{x, y | Q(x, y)} \forall_{z | R(z)} P(x, y, z) can become \forall_{x, y, z | Q(x, y), R(z)} P(x, y, z) via bundle with num_levels=2. For example of the form of the theorem required, see proveit.logic.boolean.quantification.bundling or proveit.logic.boolean.quantification.bundling_equality. ''' from proveit.relation import TransRelUpdater from proveit.logic import Implies, Equals # Make a TransRelUpdater only if the bundle_thm yield an # equation, in which case we'll want the result to be an equation. eq = None bundled = expr while num_levels >= 2: if (not isinstance(bundled, OperationOverInstances) or not isinstance(bundled.instanceExpr, OperationOverInstances)): raise ValueError( "May only 'bundle' nested OperationOverInstances, " "not %s" % bundled) _m = bundled.instanceParams.length() _n = bundled.instanceExpr.instanceParams.length() _P = bundled.instanceExpr.instanceExpr _Q = bundled.effectiveCondition() _R = bundled.instanceExpr.effectiveCondition() m, n = bundle_thm.instanceVars P, Q, R = bundle_thm.instanceExpr.instanceVars correspondence = bundle_thm.instanceExpr.instanceExpr if isinstance(correspondence, Implies): if (not isinstance(correspondence.antecedent, OperationOverInstances) or not len(correspondence.consequent.instanceParams) == 2): raise ValueError("'bundle_thm', %s, does not have the " "expected form with the bundled form as " "the consequent of the implication, %s" % (bundle_thm, correspondence)) x_1_to_m, y_1_to_n = correspondence.consequent.instanceParams elif isinstance(correspondence, Equals): if not isinstance( correspondence.rhs, OperationOverInstances or not len(correspondence.antecedent.instanceParams) == 2): raise ValueError("'bundle_thm', %s, does not have the " "expected form with the bundled form on " "right of the an equality, %s" % (bundle_thm, correspondence)) x_1_to_m, y_1_to_n = correspondence.rhs.instanceParams all_params = bundled.instanceParams + bundled.instanceExpr.instanceParams Pxy = Function(P, all_params) Qx = Function(Q, bundled.instanceParams) Rxy = Function(R, all_params) x_1_to_m = x_1_to_m.replaced({m: _m}) y_1_to_n = y_1_to_n.replaced({n: _n}) instantiation = bundle_thm.instantiate( { m: _m, n: _n, ExprTuple(x_1_to_m): bundled.instanceParams, ExprTuple(y_1_to_n): bundled.instanceExpr.instanceParams, Pxy: _P, Qx: _Q, Rxy: _R }, assumptions=assumptions) if isinstance(instantiation.expr, Implies): bundled = instantiation.deriveConsequent() elif isinstance(instantiation.expr, Equals): if eq is None: eq = TransRelUpdater(bundled) try: bundled = eq.update(instantiation) except ValueError: raise ValueError( "Instantiation of bundle_thm %s is %s but " "should match %s on one side of the equation." % (bundle_thm, instantiation, bundled)) else: raise ValueError("Instantiation of bundle_thm %s is %s but " "should be an Implies or Equals expression." % (bundle_thm, instantiation)) num_levels -= 1 if eq is None: # Return the bundled result. return bundled else: # Return the equality between the original expression and # the bundled result. return eq.relation
def merger(self, assumptions=USE_DEFAULTS): ''' If this is an tuple of expressions that can be directly merged together into a single ExprRange, return this proven equivalence. For example, {j \in Naturals, k-(j+1) \in Naturals} |- (x_1, .., x_j, x_{j+1}, x_{j+2}, ..., x_k) = (x_1, ..., x_k) ''' from proveit._core_.expression.lambda_expr import Lambda from .expr_range import ExprRange from proveit.relation import TransRelUpdater from proveit.core_expr_types.tuples._theorems_ import ( merge, merge_front, merge_back, merge_extension, merge_pair, merge_series) from proveit._common_ import f, i, j, k, l, x from proveit.number import Add, one # A convenience to allow successive update to the equation via # transitivities (starting with self=self). eq = TransRelUpdater(self, assumptions) # Determine the position of the first ExprRange item and get the # lambda map. first_range_pos = len(self) lambda_map = None for _k, item in enumerate(self): if isinstance(item, ExprRange): lambda_map = Lambda(item.lambda_map.parameter, item.lambda_map.body) first_range_pos = _k break if 1 < first_range_pos: if lambda_map is None: raise NotImplementedError("Means of extracting a lambda " "map has not been implemented") pass # need the lambda map # Collapse singular items at the beginning. front_singles = ExprTuple(eq.expr[:first_range_pos]) i_sub = lambda_map.extractArgument(front_singles[0]) j_sub = lambda_map.extractArgument(front_singles[-1]) if len(front_singles) == 2: # Merge a pair of singular items. front_merger = merge_pair.specialize( { f: lambda_map, i: i_sub, j: j_sub }, assumptions=assumptions) else: # Merge a series of singular items in one shot. front_merger = merge_series.specialize( { f: lambda_map, x: front_singles, i: i_sub, j: j_sub }, assumptions=assumptions) eq.update( front_merger.substitution(self.innerExpr()[:first_range_pos], assumptions=assumptions)) if len(eq.expr) == 1: # We have accomplished a merger down to one item. return eq.relation if len(eq.expr) == 2: # Merge a pair. if isinstance(eq.expr[0], ExprRange): if isinstance(eq.expr[1], ExprRange): # Merge a pair of ExprRanges. item = eq.expr[1] other_lambda_map = Lambda(item.lambda_map.parameter, item.lambda_map.body) if other_lambda_map != lambda_map: raise ExprTupleError( "Cannot merge together ExprRanges " "with different lambda maps: %s vs %s" % (lambda_map, other_lambda_map)) _i, _j = eq.expr[0].start_index, eq.expr[0].end_index _k, _l = eq.expr[1].start_index, eq.expr[1].end_index merger = \ merge.specialize({f:lambda_map, i:_i, j:_j, k:_k, l:_l}, assumptions=assumptions) else: # Merge an ExprRange and a singular item. _i, _j = eq.expr[0].start_index, eq.expr[0].end_index _k = lambda_map.extractArgument(eq.expr[1]) if _k == Add(_j, one): merger = merge_extension.specialize( { f: lambda_map, i: _i, j: _j }, assumptions=assumptions) else: merger = merge_back.specialize( { f: lambda_map, i: _i, j: _j, k: _k }, assumptions=assumptions) else: # Merge a singular item and ExprRange. iSub = lambda_map.extractArgument(eq.expr[0]) jSub, kSub = eq.expr[1].start_index, eq.expr[1].end_index merger = \ merge_front.specialize({f:lambda_map, i:iSub, j:jSub, k:kSub}, assumptions=assumptions) eq.update(merger) return eq.relation while len(eq.expr) > 1: front_merger = ExprTuple(*eq.expr[:2]).merger(assumptions) eq.update( front_merger.substitution(eq.expr.innerExpr(assumptions)[:2], assumptions=assumptions)) return eq.relation
def shallow_simplification(self, *, must_evaluate=False, **defaults_config): ''' Returns a proven simplification equation for this Exp expression assuming the operands have been simplified. Handles the following evaluations: a^0 = 1 for any complex a 0^x = 0 for any positive x 1^x = 1 for any complex x a^(Log(a, x)) = x for RealPos a and x, a != 1. x^n = x*x*...*x = ? for a natural n and irreducible x. Handles a zero or one exponent or zero or one base as simplifications. ''' from proveit.relation import TransRelUpdater from proveit.logic import EvaluationError, is_irreducible_value from proveit.logic import InSet from proveit.numbers import (zero, one, two, is_literal_int, is_literal_rational, Log, Rational, Abs) from . import (exp_zero_eq_one, exponentiated_zero, exponentiated_one, exp_nat_pos_expansion) if self.is_irreducible_value(): # already irreducible return Equals(self, self).conclude_via_reflexivity() if must_evaluate: if not all(is_irreducible_value(operand) for operand in self.operands): for operand in self.operands: if not is_irreducible_value(operand): # The simplification of the operands may not have # worked hard enough. Let's work harder if we # must evaluate. operand.evaluation() return self.evaluation() if self.exponent == zero: return exp_zero_eq_one.instantiate({a: self.base}) # =1 elif self.base == zero: # Will fail if the exponent is not positive, but this # is the only sensible thing to try. return exponentiated_zero.instantiate({x: self.exponent}) # =0 elif self.exponent == one: return self.power_of_one_reduction() elif self.base == one: return exponentiated_one.instantiate({x: self.exponent}) # =1 elif (isinstance(self.base, Exp) and isinstance(self.base.exponent, Div) and self.base.exponent.numerator == one and self.base.exponent.denominator == self.exponent): from . import nth_power_of_nth_root _n, _x = nth_power_of_nth_root.instance_params return nth_power_of_nth_root.instantiate( {_n: self.exponent, _x: self.base.base}) elif (isinstance(self.base, Exp) and isinstance(self.exponent, Div) and self.exponent.numerator == one and self.exponent.denominator == self.base.exponent): from . import nth_root_of_nth_power, sqrt_of_square _n = self.base.exponent _x = self.base.base if _n == two: return sqrt_of_square.instantiate({x: _x}) return nth_root_of_nth_power.instantiate({n: _n, x: _x}) elif (is_literal_rational(self.base) and is_literal_int(self.exponent) and self.exponent.as_int() > 1): expr = self eq = TransRelUpdater(expr) expr = eq.update(exp_nat_pos_expansion.instantiate( {x:self.base, n:self.exponent}, preserve_all=True)) # We should come up with a better way of reducing # ExprRanges representing repetitions: _n = self.exponent.as_int() if _n <= 0 or _n > 9: raise NotImplementedError("Currently only implemented for 1-9") repetition_thm = proveit.numbers.numerals.decimals \ .__getattr__('reduce_%s_repeats' % _n) rep_reduction = repetition_thm.instantiate({x: self.base}) expr = eq.update(expr.inner_expr().operands.substitution( rep_reduction.rhs, preserve_all=True)) expr = eq.update(expr.evaluation()) return eq.relation elif (isinstance(self.exponent, Log) and self.base == self.exponent.base): # base_ns = self.base.deduce_number_set() # antilog_ns = self.exponent.antilog.deduce_number_set() if (InSet(self.base, RealPos).proven() and InSet(self.exponent.antilog, RealPos).proven() and NotEquals(self.base, one).proven()): return self.power_of_log_reduction() expr = self # for convenience updating our equation: eq = TransRelUpdater(expr) if self.exponent == two and isinstance(self.base, Abs): from . import (square_abs_rational_simp, square_abs_real_simp) # |a|^2 = a if a is real try: deduce_number_set(self.base) except UnsatisfiedPrerequisites: pass rational_base = InSet(self.base, Rational).proven() real_base = InSet(self.base, Real).proven() thm = None if rational_base: thm = square_abs_rational_simp elif real_base: thm = square_abs_real_simp if thm is not None: simp = thm.instantiate({a: self.base.operand}) expr = eq.update(simp) # A further simplification may be possible after # eliminating the absolute value. expr = eq.update(expr.simplification()) return eq.relation
def deduce_bound(self, inner_expr_bound_or_bounds, inner_exprs_to_bound=None, **defaults_config): ''' Return a bound of this arithmetic expression based upon the bounds of any number of inner expressions. The inner expression should appear on the left side of the corresponding bound which should be a number ordering relation (< or <=). The returned, proven bound will have this expression on the left-hand side. The bounds of the inner expressions will be processed in the order they are provided. If inner_exprs_to_bound is provided, restrict the bounding to these particular InnerExpr objects. Otherwise, all inner expressions are fair game. ''' if isinstance(inner_expr_bound_or_bounds, Judgment): inner_expr_bound_or_bounds = inner_expr_bound_or_bounds.expr if isinstance(inner_expr_bound_or_bounds, ExprTuple): inner_expr_bounds = inner_expr_bound_or_bounds.entries elif isinstance(inner_expr_bound_or_bounds, Expression): inner_expr_bounds = [inner_expr_bound_or_bounds] else: inner_expr_bounds = inner_expr_bound_or_bounds inner_expr_bounds = deque(inner_expr_bounds) inner_relations = dict() if len(inner_expr_bounds) == 0: raise ValueError("Expecting one or more 'inner_expr_bounds'") while len(inner_expr_bounds) > 0: inner_expr_bound = inner_expr_bounds.popleft() print('inner_expr_bound', inner_expr_bound) if isinstance(inner_expr_bound, TransRelUpdater): # May be one of the internally generated # TransRelUpdater for percolating bounds up through # the expression hierarchy to the root. inner_expr_bound = inner_expr_bound.relation elif isinstance(inner_expr_bound, Judgment): inner_expr_bound = inner_expr_bound.expr inner = inner_expr_bound.lhs if inner == self: raise ValueError( "Why supply a bound for the full expression when " "calling 'deduce_bound'? There is nothing to deduce.") no_such_inner_expr = True no_such_number_op_inner_expr = True # Apply bound to each inner expression as applicable. if inner_exprs_to_bound is None: inner_exprs = generate_inner_expressions(self, inner) else: inner_exprs = inner_exprs_to_bound for inner_expr in inner_exprs: no_such_inner_expr = False inner_expr_depth = len(inner_expr.expr_hierarchy) assert inner_expr_depth > 1, ( "We already checked that the inner expression was not " "equal to the full expression. What's the deal?") # Create/update the relation for the container of this # inner expression. if inner_expr_depth >= 3: container = inner_expr.expr_hierarchy[-2] if isinstance(container, ExprTuple): # Skip an ExprTuple layer. if inner_expr_depth >= 4: container = inner_expr.expr_hierarchy[-3] else: container = self else: container = self if not isinstance(container, NumberOperation): # Skip over any 'container' that is not a # NumberOperation. continue no_such_number_op_inner_expr = False container_relation = inner_relations.setdefault( container, TransRelUpdater(container)) expr = container_relation.expr # Don't simplify or make replacements if there # is more to go: preserve_all = (len(inner_expr_bounds) > 0) container_relation.update( expr.bound_via_operand_bound(inner_expr_bound, preserve_all=preserve_all)) # Append the relation for processing if container is self: # No further processing needed when the container continue # is self. if (len(inner_expr_bounds) == 0 or inner_expr_bounds[-1] != container_relation): inner_expr_bounds.append(container_relation) if no_such_inner_expr: raise ValueError( "The left side of %s does not appear within %s" % (inner_expr_bound, self)) if no_such_number_op_inner_expr: raise ValueError( "The left side of %s is not contained within a " "NumberOperation expression" % (inner_expr_bound, self)) assert self in inner_relations, ( "If there are more than one inner bounds and they are " "valid, they should have percolated to the top") return inner_relations[self].relation
def unbundle(expr, unbundle_thm, num_param_entries=(1, ), *, assumptions=USE_DEFAULTS): ''' Given a nested OperationOverInstances, derive or equate an equivalent form in which the parameter entries are split in number according to 'num_param_entries'. Use the given theorem specific to the particular OperationOverInstances. For example, \forall_{x, y, z | Q(x, y), R(z)} P(x, y, z) can become \forall_{x, y | Q(x, y)} \forall_{z | R(z)} P(x, y, z) via bundle with num_param_entries=(2, 1) or num_param_entries=(2,) -- the last number can be implied by the remaining number of parameters. For example of the form of the theorem required, see proveit.logic.boolean.quantification.unbundling or proveit.logic.boolean.quantification.bundling_equality. ''' from proveit.relation import TransRelUpdater from proveit.logic import Implies, Equals, And # Make a TransRelUpdater only if the bundle_thm yield an # equation, in which case we'll want the result to be an equation. eq = None unbundled = expr net_indicated_param_entries = sum(num_param_entries) num_actual_param_entries = len(expr.instanceParams) for n in num_param_entries: if not isinstance(n, int) or n <= 0: raise ValueError( "Each of 'num_param_entries', must be an " "integer greater than 0. %s fails this requirement." % (num_param_entries)) if net_indicated_param_entries > num_actual_param_entries: raise ValueError( "Sum of 'num_param_entries', %s=%d should not " "be greater than the number of parameter entries " "of %s for unbundling." % (num_param_entries, net_indicated_param_entries, expr)) if net_indicated_param_entries < num_actual_param_entries: diff = num_actual_param_entries - net_indicated_param_entries num_param_entries = list(num_param_entries) + [diff] else: num_param_entries = list(num_param_entries) while len(num_param_entries) > 1: n_last_entries = num_param_entries.pop(-1) first_params = ExprTuple(*unbundled.instanceParams[:-n_last_entries]) first_param_vars = {getParamVar(param) for param in first_params} remaining_params = \ ExprTuple(*unbundled.instanceParams[-n_last_entries:]) _m = first_params.length() _n = remaining_params.length() _P = unbundled.instanceExpr # Split up the conditions between the outer # OperationOverInstances and inner OperationOverInstances condition = unbundled.effectiveCondition() if isinstance(condition, And): _nQ = 0 for cond in condition.operands: cond_vars = free_vars(cond, err_inclusively=True) if first_param_vars.isdisjoint(cond_vars): break _nQ += 1 if _nQ == 0: _Q = And() elif _nQ == 1: _Q = condition.operands[0] else: _Q = And(*condition.operands[:_nQ]) _nR = len(condition.operands) - _nQ if _nR == 0: _R = And() elif _nR == 1: _R = condition.operands[-1] else: _R = And(*condition.operands[_nQ:]) elif first_param_vars.isdisjoint( free_vars(condition, err_inclusively=True)): _Q = condition _R = And() else: _Q = And() _R = condition m, n = unbundle_thm.instanceVars P, Q, R = unbundle_thm.instanceExpr.instanceVars correspondence = unbundle_thm.instanceExpr.instanceExpr if isinstance(correspondence, Implies): if (not isinstance(correspondence.antecedent, OperationOverInstances) or not len(correspondence.antecedent.instanceParams) == 2): raise ValueError("'unbundle_thm', %s, does not have the " "expected form with the bundled form as " "the antecedent of the implication, %s" % (unbundle_thm, correspondence)) x_1_to_m, y_1_to_n = correspondence.antecedent.instanceParams elif isinstance(correspondence, Equals): if not isinstance( correspondence.rhs, OperationOverInstances or not len(correspondence.antecedent.instanceParams) == 2): raise ValueError("'unbundle_thm', %s, does not have the " "expected form with the bundled form on " "right of the an equality, %s" % (unbundle_thm, correspondence)) x_1_to_m, y_1_to_n = correspondence.rhs.instanceParams else: raise ValueError("'unbundle_thm', %s, does not have the expected " "form with an equality or implication " "correspondence, %s" % (unbundle_thm, correspondence)) Qx = Function(Q, first_params) Rxy = Function(R, unbundled.instanceParams) Pxy = Function(P, unbundled.instanceParams) x_1_to_m = x_1_to_m.replaced({m: _m}) y_1_to_n = y_1_to_n.replaced({n: _n}) instantiation = unbundle_thm.instantiate( { m: _m, n: _n, ExprTuple(x_1_to_m): first_params, ExprTuple(y_1_to_n): remaining_params, Pxy: _P, Qx: _Q, Rxy: _R }, assumptions=assumptions) if isinstance(instantiation.expr, Implies): unbundled = instantiation.deriveConsequent() elif isinstance(instantiation.expr, Equals): if eq is None: eq = TransRelUpdater(unbundled) try: unbundled = eq.update(instantiation) except ValueError: raise ValueError( "Instantiation of bundle_thm %s is %s but " "should match %s on one side of the equation." % (unbundle_thm, instantiation, unbundled)) else: raise ValueError("Instantiation of bundle_thm %s is %s but " "should be an Implies or Equals expression." % (unbundle_thm, instantiation)) if eq is None: # Return the unbundled result. return unbundled else: # Return the equality between the original expression and # the unbundled result. return eq.relation
def shallow_simplification(self, *, must_evaluate=False, **defaults_config): ''' Returns a proven simplification equation for this Qmult expression assuming the operands have been simplified. Currently deals only with: (1) simplify unary case (2) Ungrouping nested tensor products. (3) Factoring out scalars. ''' from proveit.linear_algebra import ScalarMult from proveit.physics.quantum import (Bra, Ket, HilbertSpaces, varphi, var_ket_psi) from proveit.physics.quantum.algebra import Hspace yield_known_hilbert_spaces = HilbertSpaces.yield_known_hilbert_spaces if self.operands.is_single(): # Handle unary cases from . import (qmult_of_ket, qmult_of_bra, qmult_of_complex, qmult_of_linmap) operand = self.operands[0] if InSet(operand, Complex).proven(): # Qmult of a complex number is just the complex number return qmult_of_complex.instantiate({c: operand}) elif isinstance(operand, Bra): # Qmult of a bra is the bra (equal to the # linear map it represents). for _Hspace in yield_known_hilbert_spaces(Ket( operand.operand)): return qmult_of_bra.instantiate({ Hspace: _Hspace, varphi: operand.operand }) elif operand in MatrixSpace.known_memberships: # Qmult of a matrix. It equates to a lambda # map, but this isn't considered a simplification. # Use linmap_reduction instead if this is desired. return Equals(self, self).conclude_via_reflexivity() else: for _Hspace in yield_known_hilbert_spaces(operand): # Qmult of a ket is just the ket return qmult_of_ket.instantiate({ Hspace: _Hspace, var_ket_psi: operand }) for linmap in containing_hilbert_space_linmap_sets(operand): # Qmult of a linear map is just the linear map _Hspace, _X = linmap.from_vspace, linmap.to_vspace return qmult_of_linmap.instantiate({ Hspace: _Hspace, X: _X, A: operand }) return Equals(self, self).conclude_via_reflexivity() # for convenience updating our equation: expr = self eq = TransRelUpdater(expr) if Qmult._simplification_directives_.ungroup: # ungroup the expression (disassociate nested additions). _n = 0 length = expr.operands.num_entries() - 1 # loop through all operands while _n < length: operand = expr.operands[_n] # print("n, length", n, length) if isinstance(operand, Qmult): # if it is grouped, ungroup it expr = eq.update(expr.disassociation(_n, preserve_all=True)) elif isinstance(operand, ScalarMult): # ungroup contained ScalarMult's expr = eq.update( expr.scalar_mult_absorption(_n, preserve_all=True)) length = expr.operands.num_entries() _n += 1 if Qmult._simplification_directives_.factor_scalars: # Next, pull out scalar factors expr = eq.update(expr.factorization_of_scalars()) if Qmult._simplification_directives_.use_scalar_mult: # Finally, use ScalarMult for any scalar operands. if (isinstance(expr, Qmult) and expr.operands.num_entries() > 1 and InSet(expr.operands[0], Complex).proven()): expr = eq.update(expr.scalar_mult_factorization()) return eq.relation