def conclude(self, assumptions): ''' If the domain has a 'foldForall' method, attempt to conclude this Forall statement via 'concludeAsFolded' or by proving the instance expression under proper assumptions and then generalizing. ''' # first try to prove via generalization without automation extra_assumptions = tuple(self.inclusiveConditions()) try: proven_inst_expr = self.explicitInstanceExpr().prove( assumptions=extra_assumptions + defaults.checkedAssumptions(assumptions), automation=False) return proven_inst_expr.generalize(self.explicitInstanceVars(), conditions=extra_assumptions) except: pass # next try 'foldAsForall' on the domain (if applicable) hasFoldAsForall = False if self.hasDomain() and hasattr(self.domain, 'foldAsForall'): # try foldAsForall first hasFoldAsForall = True try: return self.concludeAsFolded(assumptions) except: pass # lastly, try to prove via generalization with automation try: proven_inst_expr = self.explicitInstanceExpr().prove( assumptions=extra_assumptions + defaults.checkedAssumptions(assumptions)) instanceVarLists = [list(self.explicitInstanceVars())] conditions = list(self.inclusiveConditions()) # see if we can generalize multiple levels simultaneously for a shorter proof while isinstance(proven_inst_expr.proof(), Generalization): instanceVarLists.append( list(proven_inst_expr.explicitInstanceVars())) conditions += proven_inst_expr.conditions proven_inst_expr = proven_inst_expr.proof().requiredTruths[0] return proven_inst_expr.generalize(forallVarLists=instanceVarLists, conditions=conditions) except ProofFailure: if hasFoldAsForall: raise ProofFailure( self, assumptions, "Unable to conclude automatically; both the 'foldAsForall' method on the domain and automated generalization failed." ) else: raise ProofFailure( self, assumptions, "Unable to conclude automatically; the domain has no 'foldAsForall' method and automated generalization failed." )
def call_method_with_known_truth_assumptions(*args, **kwargs): if len(args) > assumptionsIndex: args = list(args) assumptions = args[assumptionsIndex] assumptions = defaults.checkedAssumptions(assumptions) assumptions += self.assumptions args[assumptionsIndex] = defaults.checkedAssumptions(assumptions) else: assumptions = kwargs.get('assumptions', USE_DEFAULTS) assumptions = defaults.checkedAssumptions(assumptions) assumptions = tuple(assumptions) + self.assumptions kwargs['assumptions'] = defaults.checkedAssumptions(assumptions) return attr.__call__(*args, **kwargs)
def rounding_deduceInNumberSet(expr, number_set, roundingRealClosureThm, roundingRealPosClosureThm, assumptions=USE_DEFAULTS): ''' Given a number set number_set, attempt to prove that the given Ceil, Floor, or Round expression is in that number set using the appropriate closure theorem. ''' from proveit import ProofFailure from proveit._common_ import x from proveit.logic import InSet from proveit.number import Integers, Naturals # among other things, convert any assumptions=None # to assumptions=() assumptions = defaults.checkedAssumptions(assumptions) if number_set == Integers: return roundingRealClosureThm.specialize( {x:expr.operand}, assumptions=assumptions) if number_set == Naturals: return roundingRealPosClosureThm.specialize( {x:expr.operand}, assumptions=assumptions) msg = ("The rounding_methods.py function " "'rounding_deduceInNumberSet()' is not implemented for the " "%s set"%str(number_set)) raise ProofFailure(InSet(expr, number_set), assumptions, msg)
def applyTransitivities(chain, assumptions=USE_DEFAULTS): ''' Apply transitvity rules on a list of relations in the given chain to proof the relation over the chain end points. Each element of the chain must be a KnownTruth object that represents a proven relation for which transitivity rules may be applied via an 'applyTransitivity' method (such as a KnownTruth for a proven Equals statement). The chain must "connect" in the sense that any two neighbors in the chain can be joined vie applyTransitivity. The transitivity rule will be applied left to right. ''' assumptions = defaults.checkedAssumptions(assumptions) if len(chain) == 0: raise TransitivityException(None, assumptions, 'Empty transitivity relation train') if not all(isinstance(element, KnownTruth) for element in chain): raise TypeError( 'Expecting chain elements to be KnownTruth objects') while len(chain) >= 2: first = chain.pop(0) second = chain.pop(0) new_relation = first.applyTransitivity(second, assumptions=assumptions) if not isinstance(new_relation, KnownTruth): raise TypeError( "applyTransitivity should return a KnownTruth, not %s" % new_relation) chain.insert(0, new_relation) return chain[0] # we are done
def apply_roundingElimination(expr, roundingEliminationThm, assumptions=USE_DEFAULTS): ''' Let F(x) represent the relevant Ceil(x), Floor(x), or Round(x) fxn calling the apply_roundingElimination() method from the respective F(x).roundingElimination() method. For the trivial case of F(x) where the operand x is already an integer, derive and return this rounding F expression equated with the operand itself: |- F(x) = x. For example, |- Ceil(x) = x. Assumptions may be necessary to deduce necessary conditions (for example, that x actually is an integer) for the simplification. This method is utilized by the F(x).apply_reducedSimplification() method, indirectly via the F(x).roundingElimination() method, but only after the operand x is verified to already be proven (or assumed) to be an integer. For the case where the operand is of the form x = real + int, see the apply_roundingExtraction() function. ''' from proveit._common_ import x # among other things, convert any assumptions=None # to assumptions=() to avoid later len() errors assumptions = defaults.checkedAssumptions(assumptions) return roundingEliminationThm.specialize( {x:expr.operand}, assumptions=assumptions)
def applyTransitivity(self, other, assumptions=USE_DEFAULTS): ''' Apply transitivity to derive a new relation from 'self' and 'other'. For example, from self:a<b, other:b=c, derive a<c. This must be implemented for the different types of transitive relations. This default version handles the case where 'other' is an equality (as in the above example. ''' from proveit.logic import Equals #print 'apply transitivity', self, other assumptions = defaults.checkedAssumptions(assumptions) if isinstance(other, Equals): if other.lhs in (self.lhs, self.rhs): subrule = other.subRightSideInto commonExpr = other.lhs elif other.rhs in (self.lhs, self.rhs): subrule = other.subLeftSideInto commonExpr = other.rhs else: raise ValueError( "Equality does not involve either side of inequality!") if commonExpr == self.lhs: # replace the lhs of self with its counterpart from the "other" equality. return subrule(self.innerExpr().lhs, assumptions=assumptions) elif commonExpr == self.rhs: # replace the rhs of self with its counterpart from the "other" equality. return subrule(self.innerExpr().rhs, assumptions=assumptions) raise NotImplementedError( 'Must implement applyTransitivity appropriately for each kind of TransitiveRelation' )
def isEqualToOrSubsetEqOf(number_set, equal_sets=None, subset_sets=None, subset_eq_sets=None, assumptions=None): ''' A utility function used in the doReducedSimplification() method to test whether the number set specified by number_set: • is equal to any of the number sets provided in the list of equal_sets • OR is already known/proven to be a proper subset of any of the number sets provided in the list of subset_sets, • OR is already known/proven to be an improper subset of any of the number sets provided in the list of subset_eq_sets, returning True at the first such equality, subset, or subset_eq relation found to be True. ''' # among other things, convert any assumptions=None # to assumptions=() (thus averting len(None) errors) assumptions = defaults.checkedAssumptions(assumptions) if not equal_sets == None: for temp_set in equal_sets: if number_set == temp_set: return True if not subset_eq_sets == None: for temp_set in subset_eq_sets: if SubsetEq(number_set, temp_set).proven(assumptions): return True if not subset_sets == None: for temp_set in subset_sets: if Subset(number_set, temp_set).proven(assumptions): return True return False
def deduceInNumberSet(self, number_set, assumptions=USE_DEFAULTS): ''' Given a number set number_set (such as Integers, Reals, etc), attempt to prove that the given ModAbs expression is in that number set using the appropriate closure theorem. ''' from proveit._common_ import a, b from proveit.logic import InSet from proveit.number.modular._theorems_ import ( modAbsIntClosure, modAbsRealClosure) from proveit.number import Integers, Reals # among other things, make sure non-existent assumptions # manifest as empty tuple () rather than None assumptions = defaults.checkedAssumptions(assumptions) if number_set == Integers: return modAbsIntClosure.specialize( {a:self.value, b:self.divisor}, assumptions=assumptions) if number_set == Reals: return modAbsRealClosure.specialize( {a:self.value, b:self.divisor}, assumptions=assumptions) msg = ("'ModAbs.deduceInNumberSet()' not implemented for " "the %s set"%str(number_set)) raise ProofFailure(InSet(self, number_set), assumptions, msg)
def sorted_items(cls, items, reorder=True, assumptions=USE_DEFAULTS): ''' Return the given items in sorted order with respect to this TransitivityRelation class (cls) under the given assumptions using known transitive relations. If reorder is False, raise a TransitivityException if the items are not in sorted order as provided. Weak relations (e.g., <=) are only considered when calling this method on a weak relation class (otherwise, only equality and strong relations are used in the sorting). ''' from proveit.number import isLiteralInt assumptions = defaults.checkedAssumptions(assumptions) if all(isLiteralInt(item) for item in items): # All the items are integers. Use efficient n log(n) sorting to # get them in the proper order and then use fixedTransitivitySort # to efficiently prove this order. items = sorted(items, key=lambda item: item.asInt()) reorder = False if reorder: sorter = TransitivitySorter(cls, items, assumptions=assumptions) return list(sorter) else: return cls._fixedTransitivitySort(items, assumptions=assumptions).operands
def sort(RelationClass, items, reorder=True, assumptions=USE_DEFAULTS): assumptions = defaults.checkedAssumptions(assumptions) if reorder: return RelationClass._transitivitySort(items, assumptions=assumptions) else: return RelationClass._fixedTransitivitySort( items, assumptions=assumptions)
def denyViaContradiction(contradictoryExpr, conclusion, assumptions): ''' Deny the conclusion (affirm its negation) via reductio ad absurdum. First calls deriveContradiction on the contradictoryExpr to derive FALSE, then derive the negated conclusion after proving that the conclusion itself implies FALSE. The conclusion must be a Boolean. ''' from proveit.logic import Not assumptions = defaults.checkedAssumptions(assumptions) extendedAssumptions = assumptions + (conclusion,) return contradictoryExpr.deriveContradiction(extendedAssumptions).asImplication(conclusion).deriveViaContradiction(assumptions)
def concludeViaTransitivity(self, assumptions=USE_DEFAULTS): from proveit.logic import Equals assumptions = defaults.checkedAssumptions(assumptions) if self.__class__ == Equals: forceStrong = False # "strong" vs "weak" doesn't make sense when the relation is simply equality else: forceStrong = ( self.__class__ == self.__class__._checkedStrongRelationClass()) return self.__class__._transitivitySearch(self.lhs, self.rhs, forceStrong=forceStrong, assumptions=assumptions)
def deduceInNumberSet(self, number_set, assumptions=USE_DEFAULTS): ''' Given a number set number_set (such as Integers, Reals, etc), attempt to prove that the given expression is in that number set using the appropriate closure theorem. ''' from proveit.number.absolute_value._theorems_ import ( absComplexClosure, absNonzeroClosure, absComplexClosureNonNegReals) from proveit.number import Complexes, Reals, RealsNonNeg, RealsPos # among other things, make sure non-existent assumptions # manifest as empty tuple () rather than None assumptions = defaults.checkedAssumptions(assumptions) if number_set == Reals: return absComplexClosure.specialize({a: self.operand}, assumptions=assumptions) if number_set == RealsPos: return absNonzeroClosure.specialize({a: self.operand}, assumptions=assumptions) if number_set == RealsNonNeg: return absComplexClosureNonNegReals.specialize( {a: self.operand}, assumptions=assumptions) # To be thorough and a little more general, we check if the # specified number_set is already proven to *contain* one of # the number sets we have theorems for -- for example, # Y=Complexes contain X=Reals, and # Y=(-1, inf) contains X=RealsPos, # but we don't have specific thms for those supersets Y. # If so, use the appropiate thm to determine that self is in X, # then prove that self must also be in Y since Y contains X. if Subset(Reals, number_set).proven(assumptions=assumptions): absComplexClosure.specialize({a: self.operand}, assumptions=assumptions) return InSet(self, number_set).prove(assumptions=assumptions) if Subset(RealsPos, number_set).proven(assumptions=assumptions): absNonzeroClosure.specialize({a: self.operand}, assumptions=assumptions) return InSet(self, number_set).prove(assumptions=assumptions) if Subset(RealsNonNeg, number_set).proven(assumptions=assumptions): absComplexClosureNonNegReals.specialize({a: self.operand}, assumptions=assumptions) return InSet(self, number_set).prove(assumptions=assumptions) # otherwise, we just don't have the right thm to make it work msg = ("'Abs.deduceInNumberSet()' not implemented for " "the %s set" % str(number_set)) raise ProofFailure(InSet(self, number_set), assumptions, msg)
def sort(cls, items, reorder=True, assumptions=USE_DEFAULTS): ''' Return the proven Sequence, a known truth for an expression of type cls.SequenceClass(), representing the sorted sequence according to the cls.sorted_items method. Weak relations (e.g., <=) are only considered when calling this method on a weak relation class (otherwise, only equality and strong relations are used in the sorting). ''' automation = True assumptions = defaults.checkedAssumptions(assumptions) if reorder: items = cls.sorted_items(items, reorder, assumptions) #print("sorted", items) automation = False # Direct relations already proven. return cls._fixedTransitivitySort(items, assumptions=assumptions, automation=automation)
def concludeViaTransitivity(self, assumptions=USE_DEFAULTS): from proveit.logic import Equals assumptions = defaults.checkedAssumptions(assumptions) proven_relation = self.__class__._transitivitySearch(self.lhs, self.rhs, assumptions=assumptions) relation = proven_relation.expr if relation.__class__ != self.__class__: if self.__class__ == self.__class__._checkedWeakRelationClass(): if relation.__class__ == self.__class__._checkedStrongRelationClass(): return relation.deriveRelaxed() elif relation.__class__ == Equals: # Derive a weaker relation via the strong relation # of equality: return self.concludeViaEquality(assumptions) msg = ("Not able to conclude the desired relation of %s" " from the proven relation of %s." %(str(self), str(relation))) raise TransitivityException(self, assumptions, msg) return proven_relation
def mergesort(cls, item_iterators, assumptions=USE_DEFAULTS, skip_exact_reps=False, skip_equiv_reps=False, requirements=None): ''' Return the proven Sequence, a known truth for an expression of type cls.SequenceClass(), representing the sorted sequence according to the cls.mergesorted_items method. Weak relations (e.g., <=) are only considered when calling this method on a weak relation class (otherwise, only equality and strong relations are used in the sorting). ''' assumptions = defaults.checkedAssumptions(assumptions) items = cls.mergesorted_items(item_iterators, assumptions, skip_exact_reps, skip_equiv_reps, requirements) items = list(items) #print("merge sorted", items) return cls._fixedTransitivitySort(items, assumptions=assumptions, automation=False)
def conclude(self, assumptions=USE_DEFAULTS): ''' From [element != a] AND ... AND [element != n], derive and return [element not in {a, b, ..., n}], where self is the EnumNonmembership object. ''' # among other things, convert any assumptions=None # to assumptions=() assumptions = defaults.checkedAssumptions(assumptions) from ._theorems_ import nonmembershipFold enum_elements = self.domain.elements element = self.element operands = self.domain.operands return nonmembershipFold.instantiate( { n: num(len(enum_elements)), x: self.element, y: enum_elements }, assumptions=assumptions)
def conclude(self, assumptions=USE_DEFAULTS): ''' Try to deduce that the given element is in the number set under the given assumptions. ''' element = self.element # See if the element is known to be equal with something # that is known to be in the number set. assumptions_set = set(defaults.checkedAssumptions(assumptions)) for eq, equiv_elem in Equals.knownRelationsFromLeft( element, assumptions_set): try: equiv_elem_in_set = InSet(equiv_elem, self.number_set) equiv_elem_in_set.prove(assumptions, automation=False) return eq.subLeftSideInto(equiv_elem_in_set, assumptions) except ProofFailure: pass ''' # Maybe let's not simplify first. If # See if we can simplify the element first. if hasattr(element, 'simplification'): simplification = element.simplification(assumptions=assumptions) element = simplification.rhs if element != self.element: # Prove membersip for the simplified element elem_in_set = InSet(element, self.number_set).prove(assumptions) # Substitute into the original. return simplification.subLeftSideInto(elem_in_set, assumptions) ''' # Try the 'deduceInNumberSet' method. if hasattr(element, 'deduceInNumberSet'): return element.deduceInNumberSet(self.number_set, assumptions=assumptions) else: msg = str(element) + " has no 'deduceInNumberSet' method." raise ProofFailure(InSet(self.element, self.number_set), assumptions, msg)
def invert(lambda_map, rhs, assumptions=USE_DEFAULTS): ''' Given some x -> f(x) map and a right-hand-side, find the x for which f(x) = rhs amongst known equalities under the given assumptions. Return this x if one is found; return None otherwise. ''' assumptionsSet = set(defaults.checkedAssumptions(assumptions)) if (lambda_map, rhs) in Equals.inversions: # Previous solution(s) exist. Use one if the assumptions are sufficient. for known_equality, inversion in Equals.inversions[(lambda_map, rhs)]: if known_equality.isSufficient(assumptionsSet): return inversion # The mapping may be a trivial identity: f(x) = f(x) try: x = lambda_map.extractParameter(rhs) # Found a trivial inversion. Store it for future reference. known_equality = Equals(rhs, rhs).prove() Equals.inversions.setdefault((lambda_map, rhs), []).append( (known_equality, x)) return x # Return the found inversion. except ParameterExtractionError: pass # well, it was worth a try # Search among known relations for a solution. for known_equality, lhs in Equals.knownRelationsFromRight( rhs, assumptionsSet): try: x = lambda_map.extractParameter(lhs) # Found an inversion. Store it for future reference. Equals.inversions.setdefault((lambda_map, rhs), []).append( (known_equality, x)) return x # Return the found inversion. except ParameterExtractionError: pass # not a match. keep looking. raise InversionError("No inversion found to map %s onto %s" % (str(lambda_map), str(rhs)))
def deduceInNumberSet(self, number_set, assumptions=USE_DEFAULTS): ''' Given a number set number_set (such as Integers, Reals, etc), attempt to prove that the given expression is in that number set using the appropriate closure theorem. Created: 3/21/2020 by wdc, based on the same method in the Add and Exp classes. Last modified: 3/26/2020 by wdc. Added defaults.checkedAssumptions to avoid ProofFailure error. Previously modified: 3/21/2020 by wdc. Creation Once established, these authorship notations can be deleted. ''' from proveit.logic import InSet from proveit.number.absolute_value._theorems_ import ( absComplexClosure, absNonzeroClosure, absComplexClosureNonNegReals) from proveit.number import Complexes, Reals, RealsNonNeg, RealsPos # among other things, make sure non-existent assumptions # manifest as empty tuple () rather than None assumptions = defaults.checkedAssumptions(assumptions) if number_set == Reals: return absComplexClosure.specialize({a: self.operand}, assumptions=assumptions) if number_set == RealsPos: return absNonzeroClosure.specialize({a: self.operand}, assumptions=assumptions) if number_set == RealsNonNeg: return absComplexClosureNonNegReals.specialize( {a: self.operand}, assumptions=assumptions) msg = ("'Abs.deduceInNumberSet()' not implemented for " "the %s set" % str(number_set)) raise ProofFailure(InSet(self, number_set), assumptions, msg)
def defaultSimplification(innerExpr, inPlace=False, mustEvaluate=False, operandsOnly=False, assumptions=USE_DEFAULTS, automation=True): ''' Default attempt to simplify the given inner expression under the given assumptions. If successful, returns a KnownTruth (using a subset of the given assumptions) that expresses an equality between the expression (on the left) and one with a simplified form for the "inner" part (in some canonical sense determined by the Operation). If inPlace is True, the top-level expression must be a KnownTruth and the simplified KnownTruth is derived instead of an equivalence relation. If mustEvaluate=True, the simplified form must be an irreducible value (see isIrreducibleValue). Specifically, this method checks to see if an appropriate simplification/evaluation has already been proven. If not, but if it is an Operation, call the simplification/evaluation method on all operands, make these substitions, then call simplification/evaluation on the expression with operands substituted for simplified forms. It also treats, as a special case, evaluating the expression to be true if it is in the set of assumptions [also see KnownTruth.evaluation and evaluateTruth]. If operandsOnly = True, only simplify the operands of the inner expression. ''' # among other things, convert any assumptions=None # to assumptions=() to avoid len(None) errors assumptions = defaults.checkedAssumptions(assumptions) from proveit.logic import TRUE, FALSE from proveit.logic.boolean._axioms_ import trueAxiom topLevel = innerExpr.exprHierarchy[0] inner = innerExpr.exprHierarchy[-1] if operandsOnly: # Just do the reduction of the operands at the level below the # "inner expression" reducedInnerExpr = reduceOperands(innerExpr, inPlace, mustEvaluate, assumptions) if inPlace: try: return reducedInnerExpr.exprHierarchy[0].prove( assumptions, automation=False) except: assert False try: eq = Equals(topLevel, reducedInnerExpr.exprHierarchy[0]) return eq.prove(assumptions, automation=False) except: assert False def innerSimplification(innerEquivalence): if inPlace: return innerEquivalence.subRightSideInto(innerExpr, assumptions=assumptions) return innerEquivalence.substitution(innerExpr, assumptions=assumptions) if isIrreducibleValue(inner): return Equals(inner, inner).prove() assumptionsSet = set(defaults.checkedAssumptions(assumptions)) # See if the expression is already known to be true as a special # case. try: inner.prove(assumptionsSet, automation=False) trueEval = evaluateTruth(inner, assumptionsSet) # A=TRUE given A if inner == topLevel: if inPlace: return trueAxiom else: return trueEval return innerSimplification(trueEval) except: pass # See if the negation of the expression is already known to be true # as a special case. try: inner.disprove(assumptionsSet, automation=False) falseEval = evaluateFalsehood(inner, assumptionsSet) # A=FALSE given Not(A) return innerSimplification(falseEval) except: pass # ================================================================ # # See if the expression already has a proven simplification # # ================================================================ # # construct the key for the known_simplifications dictionary assumptions_sorted = sorted(assumptions, key=lambda expr: hash(expr)) known_simplifications_key = (inner, tuple(assumptions_sorted)) if (mustEvaluate and inner in Equals.known_evaluation_sets): evaluations = Equals.known_evaluation_sets[inner] candidates = [] for knownTruth in evaluations: if knownTruth.isSufficient(assumptionsSet): # Found existing evaluation suitable for the assumptions candidates.append(knownTruth) if len(candidates) >= 1: # Return the "best" candidate with respect to fewest number # of steps. min_key = lambda knownTruth: knownTruth.proof().numSteps() simplification = min(candidates, key=min_key) return innerSimplification(simplification) elif (not mustEvaluate and known_simplifications_key in Equals.known_simplifications): simplification = Equals.known_simplifications[ known_simplifications_key] return innerSimplification(simplification) # ================================================================ # if not automation: msg = 'Unknown evaluation (without automation): ' + str(inner) raise SimplificationError(msg) # See if the expression is equal to something that has an evaluation # or is already known to be true. if inner in Equals.knownEqualities: for knownEq in Equals.knownEqualities[inner]: try: if knownEq.isSufficient(assumptionsSet): if inPlace: # Should first substitute in the known # equivalence then simplify that. if inner == knownEq.lhs: knownEq.subRightSideInto(innerExpr, assumptions) elif inner == knownEq.rhs: knownEq.subLeftSideInto(innerExpr, assumptions) # Use mustEvaluate=True. Simply being equal to # something simplified isn't necessarily the # appropriate simplification for "inner" itself. alt_inner = knownEq.otherSide(inner).innerExpr() equivSimp = \ defaultSimplification(alt_inner, inPlace=inPlace, mustEvaluate=True, assumptions=assumptions, automation=False) if inPlace: # Returns KnownTruth with simplification: return equivSimp innerEquiv = knownEq.applyTransitivity( equivSimp, assumptions) if inner == topLevel: return innerEquiv return innerEquiv.substitution(innerExpr, assumptions=assumptions) except SimplificationError: pass # try to simplify via reduction if not isinstance(inner, Operation): if mustEvaluate: raise EvaluationError('Unknown evaluation: ' + str(inner), assumptions) else: # don't know how to simplify, so keep it the same return innerSimplification(Equals(inner, inner).prove()) reducedInnerExpr = reduceOperands(innerExpr, inPlace, mustEvaluate, assumptions) if reducedInnerExpr == innerExpr: if mustEvaluate: # Since it wasn't irreducible to begin with, it must change # in order to evaluate. raise SimplificationError('Unable to evaluate: ' + str(inner)) else: raise SimplificationError('Unable to simplify: ' + str(inner)) # evaluate/simplify the reduced inner expression inner = reducedInnerExpr.exprHierarchy[-1] if mustEvaluate: innerEquiv = inner.evaluation(assumptions) else: innerEquiv = inner.simplification(assumptions) value = innerEquiv.rhs if value == TRUE: # Attempt to evaluate via proving the expression; # This should result in a shorter proof if allowed # (e.g., if theorems are usable). try: evaluateTruth(inner, assumptions) except: pass if value == FALSE: # Attempt to evaluate via disproving the expression; # This should result in a shorter proof if allowed # (e.g., if theorems are usable). try: evaluateFalsehood(inner, assumptions) except: pass reducedSimplification = innerSimplification(innerEquiv) if inPlace: simplification = reducedSimplification else: # Via transitivity, go from the original expression to the # reduced expression (simplified inner operands) and then the # final simplification (simplified inner expression). reduced_top_level = reducedInnerExpr.exprHierarchy[0] eq1 = Equals(topLevel, reduced_top_level) eq1.prove(assumptions, automation=False) eq2 = Equals(reduced_top_level, reducedSimplification.rhs) eq2.prove(assumptions, automation=False) simplification = eq1.applyTransitivity(eq2, assumptions) if not inPlace and topLevel == inner: # Store direct simplifications in the known_simplifications # dictionary for next time. Equals.known_simplifications[ known_simplifications_key] = simplification if isIrreducibleValue(value): # also store it in the known_evaluation_sets dictionary for # next time, since it evaluated to an irreducible value. Equals.known_evaluation_sets.setdefault(topLevel, set()).add(simplification) return simplification
def insert(self, item, assumptions=USE_DEFAULTS): assumptions = defaults.checkedAssumptions(assumptions) Relation = self.__class__.RelationClass() return Relation._transitivityInsert(self, self.operators, self.operands, item, assumptions)
def doReducedSimplification(self, assumptions=USE_DEFAULTS): ''' For the case Abs(x) where the operand x is already known to be or assumed to be a non-negative real, derive and return this Abs expression equated with the operand itself: |- Abs(x) = x. For the case where x is already known or assumed to be a negative real, return the Abs expression equated with the negative of the operand: |- Abs(x) = -x. Assumptions may be necessary to deduce necessary conditions for the simplification. ''' from proveit.number import Greater, GreaterEq, Mult, Neg from proveit.number import (zero, Naturals, NaturalsPos, RealsNeg, RealsNonNeg, RealsPos) # among other things, convert any assumptions=None # to assumptions=() (thus averting len(None) errors) assumptions = defaults.checkedAssumptions(assumptions) #-- -------------------------------------------------------- --# #-- Case (1): Abs(x) where entire operand x is known or --# #-- assumed to be non-negative Real. --# #-- -------------------------------------------------------- --# if InSet(self.operand, RealsNonNeg).proven(assumptions=assumptions): # Entire operand is known to be or assumed to be a # non-negative real, so we can return Abs(x) = x return self.absElimination(operand_type='non-negative', assumptions=assumptions) #-- -------------------------------------------------------- --# #-- Case (2): Abs(x) where entire operand x is known or --# #-- assumed to be a negative Real. --# #-- -------------------------------------------------------- --# if InSet(self.operand, RealsNeg).proven(assumptions=assumptions): # Entire operand is known to be or assumed to be a # negative real, so we can return Abs(x) = -x return self.absElimination(operand_type='negative', assumptions=assumptions) #-- -------------------------------------------------------- --# #-- Case (3): Abs(x) where entire operand x is not yet known --* #-- to be a non-negative Real, but can easily be --# #-- proven to be a non-negative Real because it is --# #-- (a) known or assumed to be ≥ 0 or #-- (b) known or assumed to be in a subset of the --# #-- non-negative Reals, or --# #-- (c) the addition or product of operands, all --# #-- of which are known or assumed to be non- --# #-- negative reals. TBA! #-- -------------------------------------------------------- --# if (Greater(self.operand, zero).proven(assumptions=assumptions) and not GreaterEq(self.operand, zero).proven(assumptions=assumptions)): GreaterEq(self.operand, zero).prove(assumptions=assumptions) # and then it will get picked up in the next if() below if GreaterEq(self.operand, zero).proven(assumptions=assumptions): from proveit.number.sets.real._theorems_ import ( inRealsNonNegIfGreaterEqZero) inRealsNonNegIfGreaterEqZero.specialize({a: self.operand}, assumptions=assumptions) return self.absElimination(operand_type='non-negative', assumptions=assumptions) if self.operand in InSet.knownMemberships.keys(): for kt in InSet.knownMemberships[self.operand]: if kt.isSufficient(assumptions): if isEqualToOrSubsetEqOf( kt.expr.operands[1], equal_sets=[RealsNonNeg, RealsPos], subset_eq_sets=[Naturals, NaturalsPos, RealsPos], assumptions=assumptions): InSet(self.operand, RealsNonNeg).prove(assumptions=assumptions) return self.absElimination(operand_type='non-negative', assumptions=assumptions) if isinstance(self.operand, Add) or isinstance(self.operand, Mult): count_of_known_memberships = 0 count_of_known_relevant_memberships = 0 for op in self.operand.operands: if op in InSet.knownMemberships.keys(): count_of_known_memberships += 1 if count_of_known_memberships == len(self.operand.operands): for op in self.operand.operands: op_temp_known_memberships = InSet.knownMemberships[op] for kt in op_temp_known_memberships: if (kt.isSufficient(assumptions) and isEqualToOrSubsetEqOf( kt.expr.operands[1], equal_sets=[RealsNonNeg, RealsPos], subset_eq_sets=[ Naturals, NaturalsPos, RealsPos, RealsNonNeg ], assumptions=assumptions)): count_of_known_relevant_memberships += 1 break if (count_of_known_relevant_memberships == len( self.operand.operands)): # Prove that the sum or product is in # RealsNonNeg and then instantiate absElimination. for op in self.operand.operands: InSet(op, RealsNonNeg).prove(assumptions=assumptions) return self.absElimination(assumptions=assumptions) #-- -------------------------------------------------------- --# #-- Case (4): Abs(x) where operand x can easily be proven --# #-- to be a negative Real because -x is known to --# #-- be in a subset of the positive Reals --# #-- -------------------------------------------------------- --# negated_op = None if isinstance(self.operand, Neg): negated_op = self.operand.operand else: negated_op = Neg(self.operand) negated_op_simp = negated_op.simplification( assumptions=assumptions).rhs if negated_op_simp in InSet.knownMemberships.keys(): from proveit.number.sets.real._theorems_ import ( negInRealsNegIfPosInRealsPos) for kt in InSet.knownMemberships[negated_op_simp]: if kt.isSufficient(assumptions): if isEqualToOrSubsetEqOf( kt.expr.operands[1], equal_sets=[RealsNonNeg, RealsPos], subset_sets=[NaturalsPos, RealsPos], subset_eq_sets=[NaturalsPos, RealsPos], assumptions=assumptions): InSet(negated_op_simp, RealsPos).prove(assumptions=assumptions) negInRealsNegIfPosInRealsPos.specialize( {a: negated_op_simp}, assumptions=assumptions) return self.absElimination(operand_type='negative', assumptions=assumptions) # for updating our equivalence claim(s) for the # remaining possibilities from proveit import TransRelUpdater eq = TransRelUpdater(self, assumptions) return eq.relation
def insert(self, item, assumptions=USE_DEFAULTS): assumptions = defaults.checkedAssumptions(assumptions) return self.__class__._transitivityInsert(self, [self.operator], self.operands, item, assumptions)
def specialize(self, specializeMap=None, relabelMap=None, assumptions=USE_DEFAULTS): ''' Performs a specialize derivation step to be proven under the given assumptions, in addition to the assumptions of the KnownTruth. This will eliminate one or more nested Forall operations, specializing the instance variables according to specializeMap. Eliminates the number of Forall operations required to utilize all of the specializeMap keys. The default mapping of all instance variables is a mapping to itself (e.g., {x:x, y:y}). Simultaneously, variables may be relabeled via relabelMap (see the relabel method). Note, there is a difference between making substitutons simultaneously versus in-series. For example, the {x:y, y:x} mapping will swap x and y variables, but mapping {x:y} then {y:x} in series would set both variables to x. Returns the proven specialized KnownTruth, or throws an exception if the proof fails. ''' from proveit import Operation, Variable, Lambda, singleOrCompositeExpression from proveit.logic import Forall from .proof import Specialization, SpecializationFailure, ProofFailure if not self.isUsable(): # If this KnownTruth is not usable, see if there is an alternate under the # set of assumptions that is usable. try: alternate = self.expr.prove(assumptions, automation=False) except ProofFailure: self.raiseUnusableProof() return alternate.specialize(specializeMap, relabelMap, assumptions) # if no specializeMap is provided, specialize the "explicitInstanceVars" of the Forall with default mappings # (mapping instance variables to themselves) if specializeMap is None: specializeMap = { ivar: ivar for ivar in self.explicitInstanceVars() } if relabelMap is None: relabelMap = dict() # Include the KnownTruth assumptions along with any provided assumptions assumptions = defaults.checkedAssumptions(assumptions) assumptions += self.assumptions # For any entrys in the subMap with Operation keys, convert # them to corresponding operator keys with Lambda substitutions. # For example f(x,y):g(x,y) would become f:[(x,y) -> g(x,y)]. # Convert to composite expressions as needed (via singleOrCompositeExpression). processedSubMap = dict() for key, sub in specializeMap.items(): sub = singleOrCompositeExpression(sub) if isinstance(key, Operation): operation = key subVar = operation.operator sub = Lambda(operation.operands, sub) processedSubMap[subVar] = sub elif isinstance(key, Variable): processedSubMap[key] = sub else: raise SpecializationFailure( self, specializeMap, relabelMap, assumptions, 'Expecting specializeMap keys to be Variables, MultiVariables, or Operations with Variable/MultiVariable operators; not %s' % str(key.__class__)) remainingSubVars = set(processedSubMap.keys()) # Determine the number of Forall eliminations. There must be at least # one (if zero is desired, relabel should be called instead). # The number is determined by the instance variables that occur as keys # in the subMap. expr = self.expr numForallEliminations = 0 while numForallEliminations == 0 or len(remainingSubVars) > 0: numForallEliminations += 1 if not isinstance(expr, Forall): raise SpecializationFailure( self, specializeMap, relabelMap, assumptions, 'May only specialize instance variables of directly nested Forall operations' ) lambdaExpr = expr.operand assert isinstance( lambdaExpr, Lambda), "Forall Operation operand must be a Lambda function" instanceVars, expr, conditions = lambdaExpr.parameterVars, lambdaExpr.body, lambdaExpr.conditions for iVar in instanceVars: if iVar in remainingSubVars: # remove this instance variable from the remaining substitution variables remainingSubVars.remove(iVar) elif iVar not in processedSubMap: # default is to map instance variables to themselves processedSubMap[iVar] = iVar return self._checkedTruth( Specialization(self, numForallEliminations=numForallEliminations, specializeMap=processedSubMap, relabelMap=relabelMap, assumptions=assumptions))
def insert(RelationClass, sequence, item, assumptions=USE_DEFAULTS): assumptions = defaults.checkedAssumptions(assumptions) return RelationClass._transitivityInsert(sequence, item, assumptions)
def conclude(self, assumptions): ''' If the instance expression, or some instance expression of nested universal quantifiers, is known to be true, conclude via generalization. Otherwise, if the domain has a 'foldForall' method, attempt to conclude this Forall statement via 'concludeAsFolded'. ''' # first try to prove via generalization without automation assumptions = defaults.checkedAssumptions(assumptions) expr = self instanceParamLists = [] conditions = [] while isinstance(expr, Forall): new_params = expr.explicitInstanceParams() instanceParamLists.append(list(new_params)) conditions += list(expr.conditions) expr = expr.instanceExpr new_assumptions = assumptions + tuple(conditions) if expr.proven(assumptions=assumptions + tuple(conditions)): proven_inst_expr = expr.prove(new_assumptions) return proven_inst_expr.generalize(instanceParamLists, conditions=conditions) # next try 'foldAsForall' on the domain (if applicable) if self.hasDomain() and hasattr(self.first_domain(), 'foldAsForall'): # try foldAsForall first try: return self.concludeAsFolded(assumptions) except: raise ProofFailure( self, assumptions, "Unable to conclude automatically; " "the 'foldAsForall' method on the " "domain failed.") else: # If there is no 'foldAsForall' strategy to try, we can # attempt a different non-trivial strategy of proving # via generalization with automation. try: conditions = list(self.conditions) proven_inst_expr = self.instanceExpr.prove( assumptions=assumptions + tuple(conditions)) instanceParamLists = [list(self.explicitInstanceParams())] # see if we can generalize multiple levels # simultaneously for a shorter proof while isinstance(proven_inst_expr.proof(), Generalization): new_params = proven_inst_expr.explicitInstanceParams() instanceParamLists.append(list(new_params)) conditions += proven_inst_expr.conditions proven_inst_expr = proven_inst_expr.proof( ).requiredTruths[0] return proven_inst_expr.generalize(instanceParamLists, conditions=conditions) except ProofFailure: raise ProofFailure( self, assumptions, "Unable to conclude automatically; " "the domain has no 'foldAsForall' method " "and automated generalization failed.") raise ProofFailure( self, assumptions, "Unable to conclude automatically; a " "universally quantified instance expression " "is not known to be true and the domain has " "no 'foldAsForall' method.")
def apply_roundingExtraction(expr, roundingExtractionThm, idx_to_extract=None, assumptions=USE_DEFAULTS): ''' Let F(x) represent the relevant Ceil(x), Floor(x), or Round(x) fxn calling the apply_roundingExtraction() method from the respective F(x).roundingExtraction() method. For the case of F(x) where the operand x = x_real + x_int, derive and return F(x) = F(x_real) + x_int (thus 'extracting' the integer component of x out from inside the function F()). The idx_to_extract is the zero-based index of the item in the operands of an Add(a, b, …, n) expression to attempt to extract. Assumptions may be necessary to deduce necessary conditions (for example, that x_int actually is an integer) for the simplification. For example, let F(x) = Ceil(x+2+y). Calling F(x).roundingExtraction( 1, assumptions=[InSet(x, Reals), InSet(y, Reals)]), will eventually end up here and return |- F(x) = Ceil(x+y) + 2 This method is utilized by the F(x).apply_reducedSimplification() method, indirectly via the F(x).roundingExtraction() method, but only after the operand x is verified to already be proven (or assumed) to be the sum of reals and integers. For the case where the entire operand x is itself an integer, see the roundingElimination() method. This works only if the operand x is an instance of the Add class at its outermost level, e.g. x = Add(a, b, …, n). The operands of that Add class can be other things, but the extraction will work only if the inner operands a, b, ..., n are simple. ''' from proveit._common_ import n, x, y from proveit.number import Add # from ._theorems_ import roundOfRealPlusInt # among other things, convert any assumptions=None # to assumptions=() to avoid later len() errors assumptions = defaults.checkedAssumptions(assumptions) # for convenience while updating our equation eq = TransRelUpdater(expr, assumptions) # first use Add.commutation to (re-)arrange operands to comform # to theorem format, using user-supplied idx_to_extract if isinstance(expr.operand, Add): expr = eq.update(expr.innerExpr().operand.commutation(idx_to_extract, len(expr.operand.operands)-1, assumptions=assumptions)) # An association step -- because the later application of # the roundOfRealPlusInt thm produces a grouping of the # Round operands in the chain of equivalences. # BUT, only perform the association if multiple operands are # needing to be associated: if len(expr.operand.operands)-1 > 1: expr = eq.update(expr.innerExpr().operand.association( 0, len(expr.operand.operands)-1, assumptions=assumptions)) # then update by applying the roundOfRealPlusInt thm x_sub = expr.operand.operands[0] n_sub = expr.operand.operands[1] expr = eq.update(roundingExtractionThm.specialize( {x:x_sub, n:n_sub}, assumptions=assumptions)) return eq.relation else: raise ValueError("In attempting f(x).apply_roundingExtraction(), " "the operand x is not of class 'Add'.")
def defaultSimplification(innerExpr, inPlace=False, mustEvaluate=False, operandsOnly=False, assumptions=USE_DEFAULTS, automation=True): ''' Default attempt to simplify the given inner expression under the given assumptions. If successful, returns a KnownTruth (using a subset of the given assumptions) that expresses an equality between the expression (on the left) and one with a simplified form for the "inner" part (in some canonical sense determined by the Operation). If inPlace is True, the top-level expression must be a KnownTruth and the simplified KnownTruth is derived instead of an equivalence relation. If mustEvaluate=True, the simplified form must be an irreducible value (see isIrreducibleValue). Specifically, this method checks to see if an appropriate simplification/evaluation has already been proven. If not, but if it is an Operation, call the simplification/evaluation method on all operands, make these substitions, then call simplification/evaluation on the expression with operands substituted for simplified forms. It also treats, as a special case, evaluating the expression to be true if it is in the set of assumptions [also see KnownTruth.evaluation and evaluateTruth]. If operandsOnlTrue, only simplify the operands of the inner expression. ''' from proveit.logic import TRUE, FALSE from proveit.logic.boolean._axioms_ import trueAxiom topLevel = innerExpr.exprHierarchy[0] inner = innerExpr.exprHierarchy[-1] if operandsOnly: # just do the reduction of the operands at the level below the "inner expression" reducedInnerExpr = reduceOperands( innerExpr, inPlace, mustEvaluate, assumptions) # OR DO WE WANT TO REDUCE AT THE INNER LEVEL?? if inPlace: return reducedInnerExpr.exprHierarchy[0].prove( assumptions ) # should have already been proven within 'reduceOperands' called above. return Equals(topLevel, reducedInnerExpr.exprHierarchy[0]).prove( assumptions ) # should have already been proven within 'reduceOperands' called above. def innerSimplification(innerEquivalence): if inPlace: return innerEquivalence.subRightSideInto(innerExpr, assumptions=assumptions) return innerEquivalence.substitution(innerExpr, assumptions=assumptions) if isinstance(inner, IrreducibleValue): IrreducibleValue.evaluation(inner) assumptionsSet = set(defaults.checkedAssumptions(assumptions)) # see if the expression is already known to be true as a special case try: inner.prove(assumptionsSet, automation=False) trueEval = evaluateTruth(inner, assumptionsSet) # A=TRUE given A if inner == topLevel: if inPlace: return trueAxiom else: return trueEval return innerSimplification(trueEval) except: pass # see if the negation of the expression is already known to be true as a special case try: inner.disprove(assumptionsSet, automation=False) falseEval = evaluateFalsehood(inner, assumptionsSet) # A=FALSE given Not(A) return innerSimplification(falseEval) except: pass # See if the expression already has a proven evaluation if inner in Equals.evaluations or (not mustEvaluate and inner in Equals.simplifications): simplifications = Equals.evaluations[ inner] if mustEvaluate else Equals.simplifications[inner] candidates = [] for knownTruth in simplifications: if knownTruth.isSufficient(assumptionsSet): candidates.append( knownTruth ) # found existing evaluation suitable for the assumptions if len(candidates) >= 1: # return the "best" candidate with respect to fewest number of steps evaluation = min( candidates, key=lambda knownTruth: knownTruth.proof().numSteps()) return innerSimplification(evaluation) if not automation: raise EvaluationError('Unknown evaluation (without automation): ' + str(inner)) # See if the expression is equal to something that has an evaluation or is # already known to be true. if inner in Equals.knownEqualities: for knownTruth in Equals.knownEqualities[inner]: try: if knownTruth.isSufficient(assumptionsSet): if inPlace: # should first substitute in the known equivalence then simplify that if inner == knownTruth.lhs: knownTruth.subRightSideInto(innerExpr, assumptions) elif inner == knownTruth.rhs: knownTruth.subLeftSideInto(innerExpr, assumptions) equivSimp = defaultSimplification( knownTruth.otherSide(inner).innerExpr(), inPlace=inPlace, mustEvaluate=mustEvaluate, assumptions=assumptions, automation=False) if inPlace: return equivSimp # returns KnownTruth with simplification innerEquiv = Equals(inner, equivSimp.rhs).concludeViaTransitivity( assumptions=assumptions) if inner == topLevel: return innerEquiv return innerEquiv.substitution(innerExpr, assumptions=assumptions) except EvaluationError: pass # try to simplify via reduction if not isinstance(inner, Operation): if mustEvaluate: raise EvaluationError('Unknown evaluation: ' + str(inner)) else: # don't know how to simplify, so keep it the same return innerSimplification(Equals(inner, inner).prove()) reducedInnerExpr = reduceOperands( innerExpr, inPlace, mustEvaluate, assumptions) # OR DO WE WANT TO REDUCE AT THE INNER LEVEL?? if reducedInnerExpr == innerExpr: if mustEvaluate: # since it wasn't irreducible to begin with, it must change in order to evaluate raise EvaluationError('Unable to evaluate: ' + str(inner)) elif inPlace: return topLevel # don't know how to simplify further else: return Equals( topLevel, topLevel).prove() # don't know how to simplify further # evaluate/simplify the reduced inner expression inner = reducedInnerExpr.exprHierarchy[-1] innerEquiv = inner.evaluation() if mustEvaluate else inner.simplification() value = innerEquiv.rhs if value == TRUE: # Attempt to evaluate via proving the expression; # This should result in a shorter proof if allowed # (e.g., if theorems are usable). try: evaluateTruth(inner, assumptions) except: pass if value == FALSE: # Attempt to evaluate via disproving the expression; # This should result in a shorter proof if allowed # (e.g., if theorems are usable). try: evaluateFalsehood(inner, assumptions) except: pass reducedSimplification = innerSimplification(innerEquiv) if inPlace: simplification = reducedSimplification else: # via transitivity, go from the original expression to the reduced expression # (simplified inner operands) and then the final simplification (simplified inner # expression). simplification = Equals( topLevel, reducedSimplification.rhs).concludeViaTransitivity(assumptions) if not inPlace and topLevel == inner: # store direct simplifications in the simplifications dictionary for next time Equals.simplifications.setdefault(topLevel, set()).add(simplification) if isinstance(value, IrreducibleValue): # also store it in the evaluations dictionary for next time # since it evaluated to an irreducible value. Equals.evaluations.setdefault(topLevel, set()).add(simplification) return simplification
def apply_reducedSimplification(expr, assumptions=USE_DEFAULTS): ''' Let F(x) represent the relevant Ceil(x), Floor(x), or Round(x) fxn calling the apply_reducedSimplification() method from the respective F(x).doReducedSimplification() method (which itself is likely called from the F(x).simplification() method). For the trivial case F(x) where the operand x is already known to be or assumed to be an integer, derive and return this F(x) expression equated with the operand itself: F(x) = x. For example, |- Round(2) = 2 or |- Floor(1) = 1. Assumptions may be necessary to deduce necessary conditions for the simplification (for example, for deducing that the operand really is an integer). For the case where the operand is of the form x = real + int, derive and return this F(x) expression equated with F(real) + int. For example, |- Floor(x + 2) = Floor(x) + 2. Again, assumptions may be necessary to deduce the appropriate set containments for the operands within the Add operand x. ''' from proveit._common_ import n, x from proveit.logic import InSet from proveit.number import Add, Integers, Reals # among other things, convert any assumptions=None # to assumptions=() (thus averting len(None) errors) assumptions = defaults.checkedAssumptions(assumptions) #-- -------------------------------------------------------- --# #-- Case (1): F(x) where entire operand x is known or --# #-- assumed to be an Integer. --# #-- -------------------------------------------------------- --# if InSet(expr.operand, Integers).proven(assumptions=assumptions): # Entire operand is known to be or assumed to be an integer # so we can simply remove the Ceil, Floor, or Round wrapper return expr.roundingElimination(assumptions) #-- -------------------------------------------------------- --# #-- Case (2): F(x) where entire operand x is not yet known --* #-- to be an Integer but can EASILY be proven --# #-- to be an Integer. --# #-- -------------------------------------------------------- --# if expr.operand in InSet.knownMemberships.keys(): from proveit.logic.set_theory import Subset, SubsetEq for kt in InSet.knownMemberships[expr.operand]: if kt.isSufficient(assumptions): if (SubsetEq(kt.expr.operands[1], Integers).proven(assumptions) or Subset(kt.expr.operands[1], Integers).proven(assumptions)): InSet(expr.operand, Integers).prove() return expr.roundingElimination(assumptions) # for updating our equivalence claim(s) for the # remaining possibilities eq = TransRelUpdater(expr, assumptions) #-- -------------------------------------------------------- --# #-- Case (3): F(x) = F(Add(a,b,...,n)), where operand x is --# #-- an Add object, not known or assumed to be an --# #-- an int, but addends might be reals and ints --# #-- -------------------------------------------------------- --# if isinstance(expr.operand, Add): # Try to partition all suboperands into Integers vs. # Non-Integers, and if there is at least one integer, try to # apply the extraction theorem (allowing an error message # if the specialization fails). subops = expr.operand.operands # Collect indices of operands known or assumed to be # ints versus reals versus neither indices_of_known_ints = [] indices_of_non_ints = [] for i in range(len(subops)): the_subop = subops[i] # (a) first perform easiest check: is the subop already known # to be an Integer? if InSet(the_subop, Integers).proven(assumptions): indices_of_known_ints.append(i) # (b) then try something just a little harder elif the_subop in InSet.knownMemberships.keys(): from proveit.logic.set_theory import Subset, SubsetEq for kt in InSet.knownMemberships[the_subop]: if kt.isSufficient(assumptions): if (SubsetEq(kt.expr.operands[1], Integers). proven(assumptions) or Subset(kt.expr.operands[1], Integers). proven(assumptions)): InSet(the_subop, Integers).prove() indices_of_known_ints.append(i) break # (c) then if the_subop is not an integer, note that instead if (i not in indices_of_known_ints): # we categorize it as a non-integer indices_of_non_ints.append(i) if len(indices_of_known_ints)>0: # Then we have at least one known integer addend, so we # rearrange and group the addends, associating the non-ints # and associating the ints original_addends = list(subops) desired_order_by_index = list( indices_of_non_ints + indices_of_known_ints) # commute to put reals first, followed by ints for i in range(len(original_addends)): init_idx = expr.operand.operands.index( original_addends[desired_order_by_index[i]]) expr = eq.update( expr.innerExpr().operand.commutation( init_idx, i, assumptions=assumptions)) # associate the non-integers (if more than 1) if len(indices_of_non_ints) > 1: # associate those elements (already re-arranged to # be at the front of the operand.operands): expr = eq.update( expr.innerExpr().operand.association( 0, len(indices_of_non_ints), assumptions=assumptions)) # associate the known integers (if more than 1) if len(indices_of_known_ints) > 1: # associate those elements (already re-arranged to # be at the end of the operand.operands): if len(indices_of_non_ints) > 0: start_idx = 1 else: start_idx = 0 expr = eq.update( expr.innerExpr().operand.association( start_idx, len(indices_of_known_ints), assumptions=assumptions)) if len(indices_of_known_ints)==len(subops): # all the addends were actually integers # could probably short-circuit this earlier! expr = eq.update(expr.roundingElimination(assumptions)) else: expr = eq.update(expr.roundingExtraction(1, assumptions)) return eq.relation else: # We did not find any integers. # Instead of returning an error, simply return the original # rounding expression equal to itself return eq.relation #-- -------------------------------------------------------- --# #-- Case (4): F(x) where operand x is not known or assumed --# #-- to be an Integer and x is not an Add object --# #-- -------------------------------------------------------- --# # apply_reducedSimplification() function is expecting simpler # operands; instead of returning an error, though, simply return # the trivial equivalence of the original expression with itself return eq.relation