def _eval_rewrite_as_ITE(self, *args, **kwargs): byfree = {} args = list(args) default = any(c == True for b, c in args) for i, (b, c) in enumerate(args): if not isinstance(b, Boolean) and b != True: raise TypeError( filldedent(''' Expecting Boolean or bool but got `%s` ''' % func_name(b))) if c == True: break # loop over independent conditions for this b for c in c.args if isinstance(c, Or) else [c]: free = c.free_symbols x = free.pop() try: byfree[x] = byfree.setdefault(x, S.EmptySet).union(c.as_set()) except NotImplementedError: if not default: raise NotImplementedError( filldedent(''' A method to determine whether a multivariate conditional is consistent with a complete coverage of all variables has not been implemented so the rewrite is being stopped after encountering `%s`. This error would not occur if a default expression like `(foo, True)` were given. ''' % c)) if byfree[x] in (S.UniversalSet, S.Reals): # collapse the ith condition to True and break args[i] = list(args[i]) c = args[i][1] = True break if c == True: break if c != True: raise ValueError( filldedent(''' Conditions must cover all reals or a final default condition `(foo, True)` must be given. ''')) last, _ = args[i] # ignore all past ith arg for a, c in reversed(args[:i]): last = ITE(c, a, last) return _canonical(last)
def _eval_rewrite_as_ITE(self, *args): byfree = {} args = list(args) default = any(c == True for b, c in args) for i, (b, c) in enumerate(args): if not isinstance(b, Boolean) and b != True: raise TypeError(filldedent(''' Expecting Boolean or bool but got `%s` ''' % func_name(b))) if c == True: break # loop over independent conditions for this b for c in c.args if isinstance(c, Or) else [c]: free = c.free_symbols x = free.pop() try: byfree[x] = byfree.setdefault( x, S.EmptySet).union(c.as_set()) except NotImplementedError: if not default: raise NotImplementedError(filldedent(''' A method to determine whether a multivariate conditional is consistent with a complete coverage of all variables has not been implemented so the rewrite is being stopped after encountering `%s`. This error would not occur if a default expression like `(foo, True)` were given. ''' % c)) if byfree[x] in (S.UniversalSet, S.Reals): # collapse the ith condition to True and break args[i] = list(args[i]) c = args[i][1] = True break if c == True: break if c != True: raise ValueError(filldedent(''' Conditions must cover all reals or a final default condition `(foo, True)` must be given. ''')) last, _ = args[i] # ignore all past ith arg for a, c in reversed(args[:i]): last = ITE(c, a, last) return _canonical(last)
def eval(cls, *_args): """Either return a modified version of the args or, if no modifications were made, return None. Modifications that are made here: 1) relationals are made canonical 2) any False conditions are dropped 3) any repeat of a previous condition is ignored 3) any args past one with a true condition are dropped If there are no args left, nan will be returned. If there is a single arg with a True condition, its corresponding expression will be returned. """ from sympy.functions.elementary.complexes import im, re if not _args: return Undefined if len(_args) == 1 and _args[0][-1] == True: return _args[0][0] newargs = [] # the unevaluated conditions current_cond = set() # the conditions up to a given e, c pair # make conditions canonical args = [] for e, c in _args: if (not c.is_Atom and not isinstance(c, Relational) and not c.has(im, re)): free = c.free_symbols if len(free) == 1: funcs = [ i for i in c.atoms(Function) if not isinstance(i, Boolean) ] if len(funcs) == 1 and len( c.xreplace({ list(funcs)[0]: Dummy() }).free_symbols) == 1: # we can treat function like a symbol free = funcs _c = c x = free.pop() try: c = c.as_set().as_relational(x) except NotImplementedError: pass else: reps = {} for i in c.atoms(Relational): ic = i.canonical if ic.rhs in (S.Infinity, S.NegativeInfinity): if not _c.has(ic.rhs): # don't accept introduction of # new Relationals with +/-oo reps[i] = S.true elif ('=' not in ic.rel_op and c.xreplace( {x: i.rhs}) != _c.xreplace({x: i.rhs})): reps[i] = Relational( i.lhs, i.rhs, i.rel_op + '=') c = c.xreplace(reps) args.append((e, _canonical(c))) for expr, cond in args: # Check here if expr is a Piecewise and collapse if one of # the conds in expr matches cond. This allows the collapsing # of Piecewise((Piecewise((x,x<0)),x<0)) to Piecewise((x,x<0)). # This is important when using piecewise_fold to simplify # multiple Piecewise instances having the same conds. # Eventually, this code should be able to collapse Piecewise's # having different intervals, but this will probably require # using the new assumptions. if isinstance(expr, Piecewise): unmatching = [] for i, (e, c) in enumerate(expr.args): if c in current_cond: # this would already have triggered continue if c == cond: if c != True: # nothing past this condition will ever # trigger and only those args before this # that didn't match a previous condition # could possibly trigger if unmatching: expr = Piecewise(*(unmatching + [(e, c)])) else: expr = e break else: unmatching.append((e, c)) # check for condition repeats got = False # -- if an And contains a condition that was # already encountered, then the And will be # False: if the previous condition was False # then the And will be False and if the previous # condition is True then then we wouldn't get to # this point. In either case, we can skip this condition. for i in ([cond] + (list(cond.args) if isinstance(cond, And) else [])): if i in current_cond: got = True break if got: continue # -- if not(c) is already in current_cond then c is # a redundant condition in an And. This does not # apply to Or, however: (e1, c), (e2, Or(~c, d)) # is not (e1, c), (e2, d) because if c and d are # both False this would give no results when the # true answer should be (e2, True) if isinstance(cond, And): nonredundant = [] for c in cond.args: if (isinstance(c, Relational) and c.negated.canonical in current_cond): continue nonredundant.append(c) cond = cond.func(*nonredundant) elif isinstance(cond, Relational): if cond.negated.canonical in current_cond: cond = S.true current_cond.add(cond) # collect successive e,c pairs when exprs or cond match if newargs: if newargs[-1].expr == expr: orcond = Or(cond, newargs[-1].cond) if isinstance(orcond, (And, Or)): orcond = distribute_and_over_or(orcond) newargs[-1] = ExprCondPair(expr, orcond) continue elif newargs[-1].cond == cond: newargs[-1] = ExprCondPair(expr, cond) continue newargs.append(ExprCondPair(expr, cond)) # some conditions may have been redundant missing = len(newargs) != len(_args) # some conditions may have changed same = all(a == b for a, b in zip(newargs, _args)) # if either change happened we return the expr with the # updated args if not newargs: raise ValueError( filldedent(''' There are no conditions (or none that are not trivially false) to define an expression.''')) if missing or not same: return cls(*newargs)
def eval(cls, *_args): """Either return a modified version of the args or, if no modifications were made, return None. Modifications that are made here: 1) relationals are made canonical 2) any False conditions are dropped 3) any repeat of a previous condition is ignored 3) any args past one with a true condition are dropped If there are no args left, nan will be returned. If there is a single arg with a True condition, its corresponding expression will be returned. """ if not _args: return Undefined if len(_args) == 1 and _args[0][-1] == True: return _args[0][0] newargs = [] # the unevaluated conditions current_cond = set() # the conditions up to a given e, c pair # make conditions canonical args = [] for e, c in _args: if not c.is_Atom and not isinstance(c, Relational): free = c.free_symbols if len(free) == 1: funcs = [i for i in c.atoms(Function) if not isinstance(i, Boolean)] if len(funcs) == 1 and len( c.xreplace({list(funcs)[0]: Dummy()} ).free_symbols) == 1: # we can treat function like a symbol free = funcs _c = c x = free.pop() try: c = c.as_set().as_relational(x) except NotImplementedError: pass else: reps = {} for i in c.atoms(Relational): ic = i.canonical if ic.rhs in (S.Infinity, S.NegativeInfinity): if not _c.has(ic.rhs): # don't accept introduction of # new Relationals with +/-oo reps[i] = S.true elif ('=' not in ic.rel_op and c.xreplace({x: i.rhs}) != _c.xreplace({x: i.rhs})): reps[i] = Relational( i.lhs, i.rhs, i.rel_op + '=') c = c.xreplace(reps) args.append((e, _canonical(c))) for expr, cond in args: # Check here if expr is a Piecewise and collapse if one of # the conds in expr matches cond. This allows the collapsing # of Piecewise((Piecewise((x,x<0)),x<0)) to Piecewise((x,x<0)). # This is important when using piecewise_fold to simplify # multiple Piecewise instances having the same conds. # Eventually, this code should be able to collapse Piecewise's # having different intervals, but this will probably require # using the new assumptions. if isinstance(expr, Piecewise): unmatching = [] for i, (e, c) in enumerate(expr.args): if c in current_cond: # this would already have triggered continue if c == cond: if c != True: # nothing past this condition will ever # trigger and only those args before this # that didn't match a previous condition # could possibly trigger if unmatching: expr = Piecewise(*( unmatching + [(e, c)])) else: expr = e break else: unmatching.append((e, c)) # check for condition repeats got = False # -- if an And contains a condition that was # already encountered, then the And will be # False: if the previous condition was False # then the And will be False and if the previous # condition is True then then we wouldn't get to # this point. In either case, we can skip this condition. for i in ([cond] + (list(cond.args) if isinstance(cond, And) else [])): if i in current_cond: got = True break if got: continue # -- if not(c) is already in current_cond then c is # a redundant condition in an And. This does not # apply to Or, however: (e1, c), (e2, Or(~c, d)) # is not (e1, c), (e2, d) because if c and d are # both False this would give no results when the # true answer should be (e2, True) if isinstance(cond, And): nonredundant = [] for c in cond.args: if (isinstance(c, Relational) and (~c).canonical in current_cond): continue nonredundant.append(c) cond = cond.func(*nonredundant) elif isinstance(cond, Relational): if (~cond).canonical in current_cond: cond = S.true current_cond.add(cond) # collect successive e,c pairs when exprs or cond match if newargs: if newargs[-1].expr == expr: orcond = Or(cond, newargs[-1].cond) if isinstance(orcond, (And, Or)): orcond = distribute_and_over_or(orcond) newargs[-1] = ExprCondPair(expr, orcond) continue elif newargs[-1].cond == cond: orexpr = Or(expr, newargs[-1].expr) if isinstance(orexpr, (And, Or)): orexpr = distribute_and_over_or(orexpr) newargs[-1] == ExprCondPair(orexpr, cond) continue newargs.append(ExprCondPair(expr, cond)) # some conditions may have been redundant missing = len(newargs) != len(_args) # some conditions may have changed same = all(a == b for a, b in zip(newargs, _args)) # if either change happened we return the expr with the # updated args if not newargs: raise ValueError(filldedent(''' There are no conditions (or none that are not trivially false) to define an expression.''')) if missing or not same: return cls(*newargs)