def _prove_direct_relationship_if_known(self, item1, item2): ''' Apply necessary transitivities to prove a direct relationship between item1 and item2 there is a known chain. There may not be a known chain if the order is relying on the assertion that added items cannot come before previous items. ''' from proveit.logic import Equals item_pair_chains = self.item_pair_chains relation_class = self.relation_class assumptions = self.assumptions eq_sets = self.eq_sets if (item1, item2) not in item_pair_chains: # We may not have the chain for these specific items, but # we should have it for something equivalent to each of the # items (unless item2 was added after item1 and only # presumed to come later w.r.t. the transitive relation). for eq_item1, eq_item2 in itertools.product( eq_sets[item1], eq_sets[item2]): if (eq_item1, eq_item2) not in item_pair_chains: # Maybe the relationship is known even though it # isn't in item_pair_chains. This can be a useful # check in cases of merge sorting where some of the # relationships are presumed. for rel_class in relation_class._RelationClasses(): try: rel = rel_class(eq_item1, eq_item2) rel = rel.prove(assumptions, automation=False) item_pair_chains[(eq_item1, eq_item2)] = [rel] break # We got all we need except: pass if (eq_item1, eq_item2) in item_pair_chains: # If item1, eq_item1 are not identical expressions, # prove that they are logically equal. if item1 != eq_item1: prepend = [Equals(item1, eq_item1).prove(assumptions)] else: prepend = [] # If item2, eq_item2 are not identical expressions, # prove that they are logically equal. if item2 != eq_item2: append = [Equals(eq_item2, item2).prove(assumptions)] else: append = [] # Make the (item1, item2) chain by # prepending/appending necessary equalities to the # (eq_item1, eq_item2) chain chain = (prepend + item_pair_chains[(eq_item1, eq_item2)] + append) item_pair_chains[(item1, item2)] = chain break # We only need one. #print("after chains", item_pair_chains) if (item1, item2) in item_pair_chains: chain = item_pair_chains[(item1, item2)] #print("prove direct relationship via", chain) self.relation_class.applyTransitivities(chain, assumptions)
def pull(self, startIdx=None, endIdx=None, direction='left', assumptions=USE_DEFAULTS): ''' Pull a subset of consecutive operands, self.operands[startIdx:endIdx], to one side or another. Returns the equality that equates self to this new version. Give any assumptions necessary to prove that the operands are in Complexes so that the commutation theorem is applicable. ''' if direction == "left": # pull the factor(s) to the left if startIdx == 0 or startIdx is None: return Equals(self, self).prove(assumptions) # no move necessary return self.commute(None, startIdx, startIdx, endIdx, assumptions=assumptions) elif direction == "right": # pull the factor(s) to the right if endIdx == len(self.operands) or endIdx is None: return Equals(self, self).prove(assumptions) # no move necessary return self.commute(startIdx, endIdx, endIdx, None, assumptions=assumptions) else: raise ValueError( "Invalid pull direction! (Acceptable values are \"left\" and \"right\".)" )
def shallow_simplification(self, *, must_evaluate=False, **defaults_config): ''' For the simple binary case Max(a, b), returns a proven simplification equation for this Max expression assuming the operands 'a' and 'b' have been simplified and that we know or have assumed that either a >= b or b > a. If such relational knowledge is not to be had, we simply return the equation of the Max expression with itself. Cases with more than 2 operands are not yet handled. ''' from proveit.logic import Equals from proveit.numbers import greater_eq # We're only set up to deal with binary operator version if not self.operands.is_double(): # Default is no simplification if not a binary operation. return Equals(self, self).prove() # If binary and we know how operands 'a' and 'b' are related ... op_01, op_02 = self.operands[0], self.operands[1] if (greater_eq(op_01, op_02).proven() or greater_eq(op_02, op_01).proven()): from proveit import x, y from proveit.numbers.ordering import max_def_bin return max_def_bin.instantiate({x: op_01, y: op_02}) # Otherwise still no simplification. return Equals(self, self).prove()
def deduce_equality(self, equality, **defaults_config): ''' Prove the given equality with self on the left-hand side. ''' from proveit.logic import Equals 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'") if equality.proven(): return equality # Already proven. with defaults.temporary() as temp_defaults: # Auto-simplify everything except the left and right sides # of the equality. temp_defaults.preserved_exprs = {equality.lhs, equality.rhs} temp_defaults.auto_simplify = True # Try a special-case "typical equality". if isinstance(equality.rhs, Len): if (isinstance(equality.rhs.operands, ExprTuple) and isinstance(self.operands, ExprTuple)): if (equality.rhs.operands.num_entries() == 1 and isinstance(equality.rhs.operands[0], ExprRange)): try: eq = self.typical_eq() if eq.expr == equality: return eq except (NotImplementedError, ValueError): pass # Next try to compute each side, simplify each side, and # prove they are equal. lhs_computation = equality.lhs.computation() if isinstance(equality.rhs, Len): # Compute both lengths and see if we can prove that they # are equal. rhs_computation = equality.rhs.computation() eq = Equals(lhs_computation.rhs, rhs_computation.rhs) if eq.lhs == eq.rhs: # Trivial reflection eq = eq.conclude_via_reflexivity() else: eq = eq.conclude_via_transitivity() return Equals.apply_transitivities( [lhs_computation, eq, rhs_computation]) else: # Compute the lhs length and see if we can prove that it is # equal to the rhs. eq = Equals(lhs_computation.rhs, equality.rhs) if eq.lhs == eq.rhs: # Trivial reflection eq = eq.conclude_via_reflexivity() else: eq = eq.conclude_via_transitivity() return lhs_computation.apply_transitivity(eq)
def deduce_equality(self, equality, assumptions=USE_DEFAULTS, minimal_automation=False): from proveit.logic import Equals 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'") # Try a special-case "typical equality". if isinstance(equality.rhs, Len): if (isinstance(equality.rhs.operand, ExprTuple) and isinstance(self.operand, ExprTuple)): if (equality.rhs.operand.num_entries() == 1 and isinstance(equality.rhs.operand[0], ExprRange)): try: eq = \ self.typical_eq(assumptions=assumptions) if eq.expr == equality: return eq except (NotImplementedError, ValueError): pass # Next try to compute each side, simplify each side, and # prove they are equal. lhs_computation = equality.lhs.computation(assumptions=assumptions) if isinstance(equality.rhs, Len): # Compute both lengths and see if we can prove that they # are equal. rhs_computation = equality.rhs.computation(assumptions=assumptions) eq = Equals(lhs_computation.rhs, rhs_computation.rhs) if eq.lhs == eq.rhs: # Trivial reflection -- automation is okay for that. eq = eq.prove() else: eq = eq.prove(assumptions, automation=not minimal_automation) return Equals.apply_transitivities( [lhs_computation, eq, rhs_computation], assumptions=assumptions) else: # Compute the lhs length and see if we can prove that it is # equal to the rhs. eq = Equals(lhs_computation.rhs, equality.rhs) if eq.lhs == eq.rhs: # Trivial reflection -- automation is okay for that. eq = eq.prove() else: eq = eq.prove(assumptions, automation=not minimal_automation) return lhs_computation.apply_transitivity(eq, assumptions=assumptions)
def apply_association_thm(expr, start_idx, length, thm, *, repl_map_extras=None, **defaults_config): from proveit.logic import Equals beg, end = start_and_end_indices(expr, start_index=start_idx, length=length) if beg == 0 and end == expr.operands.num_entries(): # association over the entire range is trivial: return Equals(expr, expr).prove() # simply the self equality if repl_map_extras is None: repl_map_extras = dict() i, j, k, A, B, C = [ _var for _var in thm.all_instance_vars() if _var not in repl_map_extras ] _A = expr.operands[:beg] _B = expr.operands[beg:end] _C = expr.operands[end:] _i = _A.num_elements() _j = _B.num_elements() _k = _C.num_elements() with expr.__class__.temporary_simplification_directives() as \ tmp_directives: tmp_directives.ungroup = False repl_map = {i: _i, j: _j, k: _k, A: _A, B: _B, C: _C} repl_map.update(repl_map_extras) return thm.instantiate(repl_map)
def conclude(self, assumptions): from ._theorems_ import supersetEqViaEquality from proveit import ProofFailure from proveit.logic import Equals try: # first attempt a transitivity search return ContainmentRelation.conclude(self, assumptions) except ProofFailure: pass # transitivity search failed # Any set contains itself try: Equals(self.operands[0], self.operands[1]).prove(assumptions, automation=False) return supersetEqViaEquality.specialize({ A: self.operands[0], B: self.operands[1] }) except ProofFailure: pass # Finally, attempt to conclude A supseteq B via forall_{x in B} x in A. return self.concludeAsFolded(elemInstanceVar=safeDummyVar(self), assumptions=assumptions)
def do_reduced_evaluation(self, assumptions=USE_DEFAULTS, **kwargs): ''' Attempt to form evaluation of whether (element not in domain) is TRUE or FALSE. If the domain has a 'membership_object' method, attempt to use the 'equivalence' method from the object it generates. ''' from proveit.logic import Equals, TRUE, InSet # try an 'equivalence' method (via the nonmembership object) equiv = self.nonmembership_object.equivalence(assumptions) val = equiv.evaluation(assumptions).rhs evaluation = Equals(equiv, val).prove(assumptions=assumptions) # try also to evaluate this by deducing membership or non-membership # in case it generates a shorter proof. try: if evaluation.rhs == TRUE: if hasattr(self, 'nonmembership_object'): self.nonmembership_object.conclude(assumptions=assumptions) else: in_domain = In(self.element, self.domain) if hasattr(in_domain, 'membership_object'): in_domain.membership_object.conclude( assumptions=assumptions) except BaseException: pass return evaluation
def conclude(self, assumptions): from ._theorems_ import subsetEqViaEquality from proveit import ProofFailure from proveit.logic import SetOfAll, Equals try: # first attempt a transitivity search return ContainmentRelation.conclude(self, assumptions) except ProofFailure: pass # transitivity search failed # Any set contains itself try: Equals(self.operands[0], self.operands[1]).prove(assumptions, automation=False) return subsetEqViaEquality.specialize({A: self.operands[0], B: self.operands[1]}) except ProofFailure: pass # Check for special case of [{x | Q*(x)}_{x \in S}] \subseteq S if isinstance(self.subset, SetOfAll): from proveit.logic.set_theory.comprehension._theorems_ import comprehensionIsSubset setOfAll = self.subset if len(setOfAll.instanceVars)==1 and setOfAll.instanceElement == setOfAll.instanceVars[0] and setOfAll.domain==self.superset: Q_op, Q_op_sub = Operation(Qmulti, setOfAll.instanceVars), setOfAll.conditions return comprehensionIsSubset.specialize({S:setOfAll.domain, Q_op:Q_op_sub}, relabelMap={x:setOfAll.instanceVars[0]}, assumptions=assumptions) # Finally, attempt to conclude A subseteq B via forall_{x in A} x in B. # Issue: Variables do not match when using safeDummyVar: _x_ to x. # We need to automate this better, right now it is only practical to do concludeAsFolded manually. return self.concludeAsFolded(elemInstanceVar=safeDummyVar(self), assumptions=assumptions)
def empty_range(_i, _j, _f, assumptions): # If the start and end are literal ints and form an # empty range, then it should be straightforward to # prove that the range is empty. from proveit.numbers import is_literal_int, Add from proveit.logic import Equals from proveit import m _m = entries[0].start_index _n = entries[0].end_index empty_req = Equals(Add(_n, one), _m) if is_literal_int(_m) and is_literal_int(_n): if _n.as_int() + 1 == _m.as_int(): empty_req.prove() if empty_req.proven(assumptions): _f = Lambda( (entries[0].parameter, entries[0].body.parameter), entries[0].body.body) _i = entry_map(_i) _j = entry_map(_j) return len_of_empty_range_of_ranges.instantiate( { m: _m, n: _n, f: _f, i: _i, j: _j }, assumptions=assumptions)
def substitute(self, replacement, assumptions=USE_DEFAULTS): ''' Substitute the replacement in place of the inner expression and return a new proven statement (assuming the top level expression is proven, or can be proven automatically). ''' from proveit import x, P from proveit.logic import TRUE, FALSE, Equals from proveit.logic.equality import (substitute_truth, substitute_falsehood) cur_inner_expr = self.expr_hierarchy[-1] if cur_inner_expr == TRUE: return substitute_truth.instantiate( { P: self.repl_lambda(), x: replacement }, assumptions=assumptions) elif cur_inner_expr == FALSE: return substitute_falsehood.instantiate( { P: self.repl_lambda(), x: replacement }, assumptions=assumptions) else: Equals(cur_inner_expr, replacement).sub_right_side_into(self.repl_lambda(), assumptions=assumptions)
def apply_association_thm(expr, startIdx, length, thm, assumptions=USE_DEFAULTS): from proveit.logic import Equals from proveit.number import num beg, end = startIdx, startIdx + length if beg < 0: beg = len(expr.operands) + beg # use wrap-around indexing if not length >= 2: raise IndexError( "The 'length' must be 2 or more when applying association.") 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 l, m, n, AA, BB, CC = thm.allInstanceVars() return thm.specialize( { l: num(beg), m: num(end - beg), n: num(len(expr.operands) - end), AA: expr.operands[:beg], BB: expr.operands[beg:end], CC: expr.operands[end:] }, assumptions=assumptions)
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 apply_association_thm(expr, start_idx, length, thm, assumptions=USE_DEFAULTS): from proveit import ExprTuple from proveit.logic import Equals beg = start_idx if beg < 0: beg = expr.operands.num_entries() + beg # use wrap-around indexing end = beg + length if end > expr.operands.num_entries(): raise IndexError("'start_idx+length' out of bounds: %d > %d." % (end, expr.operands.num_entries())) if beg == 0 and end == expr.operands.num_entries(): # association over the entire range is trivial: return Equals(expr, expr).prove() # simply the self equality i, j, k, A, B, C = thm.all_instance_vars() _A = expr.operands[:beg] _B = expr.operands[beg:end] _C = expr.operands[end:] _i = _A.num_elements(assumptions) _j = _B.num_elements(assumptions) _k = _C.num_elements(assumptions) return thm.instantiate({ i: _i, j: _j, k: _k, A: _A, B: _B, C: _C }, assumptions=assumptions)
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 factor(self, theFactor, pull="left", groupFactor=True, groupRemainder=False, assumptions=USE_DEFAULTS): ''' Factor out "theFactor" from this product, pulling it either to the "left" or "right". If "theFactor" is a product, this may factor out a subset of the operands as long as they are next to each other (use commute to make this happen). If there are multiple occurrences, the first occurrence is used. If groupFactor is True and theFactor is a product, these operands are grouped together as a sub-product. If groupRemainder is True and there are multiple remaining operands (those not in "theFactor"), then these remaining operands are grouped together as a sub-product. Returns the equality that equates self to this new version. Give any assumptions necessary to prove that the operands are in Complexes so that the associative and commutation theorems are applicable. ''' idx, num = self.index(theFactor, alsoReturnNum=True) expr = self.pull(idx, idx + num, pull, assumptions) if groupFactor and num > 1: if pull == 'left': # use 0:num type of convention like standard pythong expr = expr.rhs.group(endIdx=num, assumptions=assumptions) elif pull == 'right': expr = expr.rhs.group(startIdx=-num, assumptions=assumptions) if groupRemainder and len(self.operands) - num > 1: # if the factor has been group, effectively there is just 1 factor operand now numFactorOperands = 1 if groupFactor else num if pull == 'left': expr = expr.rhs.group(startIdx=numFactorOperands, assumptions=assumptions) elif pull == 'right': expr = expr.rhs.group(endIdx=-numFactorOperands, assumptions=assumptions) return Equals(self, expr.rhs)
def conclude(self, **defaults_config): ''' Conclude something of the form a ≤ b. ''' from proveit.logic import InSet from proveit.numbers import Add, zero, RealNonNeg, deduce_number_set from . import non_neg_if_real_non_neg if Equals(self.lower, self.upper).proven(): # We know that a = b, therefore a ≤ b. return self.conclude_via_equality() if self.upper == zero: # Special case with upper bound of zero. from . import non_pos_if_real_non_pos concluded = non_pos_if_real_non_pos.instantiate({a: self.lower}) return concluded if self.lower == zero: # Special case with lower bound of zero. deduce_number_set(self.upper) if InSet(self.upper, RealNonNeg).proven(): return non_neg_if_real_non_neg.instantiate({a: self.upper}) if ((isinstance(self.lower, Add) and self.upper in self.lower.terms.entries) or (isinstance(self.upper, Add) and self.lower in self.upper.terms.entries)): try: # Conclude an sum is bounded by one of its terms. return self.conclude_as_bounded_by_term() except UnsatisfiedPrerequisites: # If prerequisites weren't satisfied to do this, # we can still try something else. pass return NumberOrderingRelation.conclude(self)
def evaluation(self, assumptions=USE_DEFAULTS): ''' Attempt to form evaluation of whether (element in domain) is TRUE or FALSE. If the domain has a 'membershipObject' method, attempt to use the 'equivalence' method from the object it generates. ''' from proveit.logic import Equals, TRUE, NotIn evaluation = None try: # try an 'equivalence' method (via the membership object) equiv = self.membershipObject.equivalence(assumptions) val = equiv.evaluation(assumptions).rhs evaluation = Equals(equiv, val).prove(assumptions=assumptions) except: # try the default evaluation method if necessary evaluation = Operation.evaluation(self, assumptions) # try also to evaluate this by deducing membership or non-membership in case it # generates a shorter proof. try: if evaluation.rhs == TRUE: if hasattr(self, 'membershipObject'): self.membershipObject.conclude(assumptions=assumptions) else: notInDomain = NotIn(self.element, self.domain) if hasattr(notInDomain, 'nonmembershipObject'): notInDomain.nonmembershipObject.conclude( assumptions=assumptions) except: pass return evaluation
def _check_tensor_equality(tensor_equality, allow_unary=False): ''' Check that the tensor_equality has the appropriate form. ''' if isinstance(tensor_equality, Judgment): tensor_equality = tensor_equality.expr if not isinstance(tensor_equality, Equals): raise ValueError("tensor_equality should be an Equals expression; " " instead received: {}.".format(tensor_equality)) if (not isinstance(tensor_equality.lhs, TensorProd) or not isinstance(tensor_equality.rhs, TensorProd)): if allow_unary: # If we are allowing the sides to by unary tensor # products, make it so. tensor_equality = Equals(TensorProd(tensor_equality.lhs), TensorProd(tensor_equality.rhs)) else: raise ValueError( "tensor_equality should be an Equals expression of " "tensor products; " "instead received: {}.".format(tensor_equality)) if (tensor_equality.lhs.factors.num_elements() != tensor_equality.rhs.factors.num_elements()): raise ValueError( "tensor_equality should be an Equals expression of tensor " "products with the same number of factors; " "instead received: {}.".format(tensor_equality)) return tensor_equality
def notEqual(self, rhs, assumptions=USE_DEFAULTS): from ._theorems_ import multNotEqZero if rhs == zero: return multNotEqZero.specialize({xMulti: self.operands}, assumptions=assumptions) raise ProofFailure( Equals(self, zero), assumptions, "'notEqual' only implemented for a right side of zero")
def substitution(self, replacement, assumptions=USE_DEFAULTS): ''' Equate the top level expression with a similar expression with the inner expression replaced by the replacement. ''' from proveit.logic import Equals cur_inner_expr = self.expr_hierarchy[-1] equality = Equals(cur_inner_expr, replacement).prove(assumptions) equality.substitution(self.repl_lambda(), assumptions=assumptions)
def simplification(self, assumptions=USE_DEFAULTS): ''' For trivial cases, a zero or one factor, derive and return this multiplication expression equated with a simplified form. Assumptions may be necessary to deduce necessary conditions for the simplification. ''' from ._theorems_ import multOne, multZero expr = self try: zeroIdx = self.operands.index(zero) # there is a zero in the product. We can simplify that. if zeroIdx > 0: # commute it so that the zero comes first expr = expr.commute(0, zeroIdx, zeroIdx, zeroIdx + 1, assumptions).rhs if len(expr.operands) > 2: # group the other operands so there are only two at the top level expr = expr.group(1, len(expr.operands), assumptions) return Equals( self, multZero.specialize( {x: expr.operands[1]}, assumptions=assumptions)).prove(assumptions) except ValueError: pass # no zero factor try: oneIdx = expr.operands.index(one) # there is a one in the product. We can simplify that. if oneIdx > 0: # commute it so that the one comes first expr = expr.commute(0, oneIdx, oneIdx, oneIdx + 1, assumptions).rhs if len(expr.operands) > 2: # group the other operands so there are only two at the top level expr = expr.group(1, len(expr.operands), assumptions).rhs return Equals( self, multOne.specialize({x: expr.operands[1]}, assumptions=assumptions)).prove(assumptions) except ValueError: pass # no one factor raise ValueError( 'Only trivial simplification is implemented (zero or one factors)')
def __init__(self, expr, assumptions=USE_DEFAULTS): ''' Create a TransRelUpdater starting with the given expression and forming the trivial relation of expr=expr. By providing 'assumptions', they can be used as default assumptions when applying updates. ''' from proveit.logic import Equals self.expr = expr self.relation = Equals(expr, expr).conclude_via_reflexivity() self.assumptions = assumptions
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 multi_elem_entries(element_from_part, start_qubit_idx, end_qubit_idx, *part_start_and_ends, check_part_index_span=True): ''' Yield consecutive vertical entries for MultiQubitElem to represent all parts of a multi-qubit operation in a quantum circuit involving all qubits from 'start_qubit_idx' to 'end_qubit_idx. There will be an entry for each "part" start/end pair of indices. In total, these must start from one, be consecutive, and cover the range from the 'start_qubit_idx' to the 'end_qubit_idx. The element_from_part function must return an element corresponding to a given 'part'. ''' targets = Interval(start_qubit_idx, end_qubit_idx) multi_qubit_gate_from_part = ( lambda part: MultiQubitElem(element_from_part(part), targets)) part = one if len(part_start_and_ends) == 0: raise ValueError("Must specify one or more 'part' start and end " "indices, starting from one and covering the range " "from %s to %s" % (start_qubit_idx, end_qubit_idx)) for part_start, part_end in part_start_and_ends: #try: # Equals(part, part_start).prove() #except ProofFailure: if part != part_start: raise ValueError("Part indices must be provably consecutive " "starting from 1: %s ≠ %s" % (part_start, part)) if part_start == part_end: # just a single element yield multi_qubit_gate_from_part(part) else: param = safe_dummy_var() yield ExprRange(param, multi_qubit_gate_from_part(param), part_start, part_end) part = Add(part_end, one).quick_simplified() if not check_part_index_span: lhs = Add(part_end, num(-1)).quick_simplified() rhs = Add(end_qubit_idx, Neg(start_qubit_idx)).quick_simplified() try: try: lhs = lhs.simplified() except: pass try: rhs = rhs.simplified() except: pass Equals(lhs, rhs).prove() except ProofFailure: raise ValueError("Part indices must span the range of the " "multi qubit operation: %s ≠ %s" % (lhs, rhs))
def test(): substitution.specialize({fx:Not(x), x:a, y:b}, assumptions=[Equals(a, b)]) expr = Equals(a, Add(b, Frac(c, d), Exp(c, d))) gRepl = Lambda.globalRepl(expr, d) d_eq_y = Equals(d, y) d_eq_y.substitution(gRepl, assumptions=[d_eq_y]) d_eq_y.substitution(expr, assumptions=[d_eq_y]) d_eq_y.substitution(expr, assumptions=[d_eq_y]).proof() innerExpr = expr.innerExpr() innerExpr = innerExpr.rhs innerExpr = innerExpr.operands[1] innerExpr = innerExpr.denominator d_eq_y.substitution(innerExpr, assumptions=[d_eq_y]) d_eq_y.substitution(expr.innerExpr().rhs.operands[2].exponent, assumptions=[d_eq_y]) d_eq_y.subRightSideInto(gRepl, assumptions=[d_eq_y,expr]) d_eq_y.subRightSideInto(expr, assumptions=[d_eq_y,expr]) y_eq_d = Equals(y, d) y_eq_d.subLeftSideInto(gRepl, assumptions=[y_eq_d,expr]) y_eq_d.subLeftSideInto(expr, assumptions=[y_eq_d,expr]) y_eq_d.subLeftSideInto(expr, assumptions=[y_eq_d,expr]).proof()
def _simplifiedCoord(coord, assumptions, requirements): ''' Simplify the given coordinate under the given assumptions and append the equality of the simplified and original indices as a requirement if they are not the same. ''' from proveit.logic import Equals #from proveit.number import Add simplified_coord = coord.simplification(assumptions=assumptions).rhs if simplified_coord != coord and requirements is not None: requirements.append(Equals(coord, simplified_coord)) return simplified_coord
def doReducedSimplification(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 from proveit.number import one from ._theorems_ import complexXToFirstPowerIsX if self.exponent == one: return complexXToFirstPowerIsX.specialize({a:self.base}) return Equals(self, self).prove()
def simplification(self, assumptions=USE_DEFAULTS): ''' If possible, return a KnownTruth of this expression equal to a canonically simplified form. Checks for an existing simplifcation. If it doesn't exist, try some default strategies including a reduction. Attempt the Expression-class-specific "doReducedSimplication" when necessary. ''' from proveit.logic import Equals, defaultSimplification, SimplificationError from proveit import KnownTruth, ProofFailure method_called = None try: # First try the default tricks. If a reduction succesfully occurs, # simplification will be called on that reduction. simplification = defaultSimplification(self.innerExpr(), assumptions=assumptions) method_called = defaultSimplification except SimplificationError as e: # The default did nothing, let's try the Expression-class specific versions of # evaluation and simplification. try: # first try evaluation. that is as simple as it gets. simplification = self.doReducedEvaluation(assumptions) method_called = self.doReducedEvaluation except (NotImplementedError, SimplificationError): try: simplification = self.doReducedSimplification(assumptions) method_called = self.doReducedSimplification except (NotImplementedError, SimplificationError): # Simplification did not work. Just use self-equality. self_eq = Equals(self, self) simplification = self_eq.prove() method_called = self_eq.prove if not isinstance(simplification, KnownTruth) or not isinstance( simplification.expr, Equals): msg = ("%s must return a KnownTruth " "equality, not %s for %s assuming %s" % (method_called, simplification, self, assumptions)) raise ValueError(msg) if simplification.lhs != self: msg = ("%s must return a KnownTruth " "equality with 'self' on the left side, not %s for %s " "assuming %s" % (method_called, simplification, self, assumptions)) raise ValueError(msg) # Remember this simplification for next time: Equals.simplifications.setdefault(self, set()).add(simplification) return simplification
def yieldLocationDeltas(self, axis): ''' For the given axis, yield the difference between each successive coordinate at which an element is explicitly located for this tensor (or where an Embed block ends). ''' from proveit.number import zero, Add from proveit.logic import Equals prev_coord = zero for coord in self.sorting_relations[axis]: if not Equals(prev_coord, coord).prove(assumptions=[self.sorting_relations[axis]], automation=False): assert isinstance(coord, Add) and len(coord.operands)==2 and coord.operands[0]==prev_coord, "The first of a set of equal coordinates in the sorting relations are supposed to indicate the difference from the previous one." yield coord.operands[1] # this term is the difference between this coordinate and the simplified form of the previous one prev_coord = coord