Spaces:
Sleeping
Sleeping
| from sympy.core import S, Function, diff, Tuple, Dummy, Mul | |
| from sympy.core.basic import Basic, as_Basic | |
| from sympy.core.numbers import Rational, NumberSymbol, _illegal | |
| from sympy.core.parameters import global_parameters | |
| from sympy.core.relational import (Lt, Gt, Eq, Ne, Relational, | |
| _canonical, _canonical_coeff) | |
| from sympy.core.sorting import ordered | |
| from sympy.functions.elementary.miscellaneous import Max, Min | |
| from sympy.logic.boolalg import (And, Boolean, distribute_and_over_or, Not, | |
| true, false, Or, ITE, simplify_logic, to_cnf, distribute_or_over_and) | |
| from sympy.utilities.iterables import uniq, sift, common_prefix | |
| from sympy.utilities.misc import filldedent, func_name | |
| from itertools import product | |
| Undefined = S.NaN # Piecewise() | |
| class ExprCondPair(Tuple): | |
| """Represents an expression, condition pair.""" | |
| def __new__(cls, expr, cond): | |
| expr = as_Basic(expr) | |
| if cond == True: | |
| return Tuple.__new__(cls, expr, true) | |
| elif cond == False: | |
| return Tuple.__new__(cls, expr, false) | |
| elif isinstance(cond, Basic) and cond.has(Piecewise): | |
| cond = piecewise_fold(cond) | |
| if isinstance(cond, Piecewise): | |
| cond = cond.rewrite(ITE) | |
| if not isinstance(cond, Boolean): | |
| raise TypeError(filldedent(''' | |
| Second argument must be a Boolean, | |
| not `%s`''' % func_name(cond))) | |
| return Tuple.__new__(cls, expr, cond) | |
| def expr(self): | |
| """ | |
| Returns the expression of this pair. | |
| """ | |
| return self.args[0] | |
| def cond(self): | |
| """ | |
| Returns the condition of this pair. | |
| """ | |
| return self.args[1] | |
| def is_commutative(self): | |
| return self.expr.is_commutative | |
| def __iter__(self): | |
| yield self.expr | |
| yield self.cond | |
| def _eval_simplify(self, **kwargs): | |
| return self.func(*[a.simplify(**kwargs) for a in self.args]) | |
| class Piecewise(Function): | |
| """ | |
| Represents a piecewise function. | |
| Usage: | |
| Piecewise( (expr,cond), (expr,cond), ... ) | |
| - Each argument is a 2-tuple defining an expression and condition | |
| - The conds are evaluated in turn returning the first that is True. | |
| If any of the evaluated conds are not explicitly False, | |
| e.g. ``x < 1``, the function is returned in symbolic form. | |
| - If the function is evaluated at a place where all conditions are False, | |
| nan will be returned. | |
| - Pairs where the cond is explicitly False, will be removed and no pair | |
| appearing after a True condition will ever be retained. If a single | |
| pair with a True condition remains, it will be returned, even when | |
| evaluation is False. | |
| Examples | |
| ======== | |
| >>> from sympy import Piecewise, log, piecewise_fold | |
| >>> from sympy.abc import x, y | |
| >>> f = x**2 | |
| >>> g = log(x) | |
| >>> p = Piecewise((0, x < -1), (f, x <= 1), (g, True)) | |
| >>> p.subs(x,1) | |
| 1 | |
| >>> p.subs(x,5) | |
| log(5) | |
| Booleans can contain Piecewise elements: | |
| >>> cond = (x < y).subs(x, Piecewise((2, x < 0), (3, True))); cond | |
| Piecewise((2, x < 0), (3, True)) < y | |
| The folded version of this results in a Piecewise whose | |
| expressions are Booleans: | |
| >>> folded_cond = piecewise_fold(cond); folded_cond | |
| Piecewise((2 < y, x < 0), (3 < y, True)) | |
| When a Boolean containing Piecewise (like cond) or a Piecewise | |
| with Boolean expressions (like folded_cond) is used as a condition, | |
| it is converted to an equivalent :class:`~.ITE` object: | |
| >>> Piecewise((1, folded_cond)) | |
| Piecewise((1, ITE(x < 0, y > 2, y > 3))) | |
| When a condition is an ``ITE``, it will be converted to a simplified | |
| Boolean expression: | |
| >>> piecewise_fold(_) | |
| Piecewise((1, ((x >= 0) | (y > 2)) & ((y > 3) | (x < 0)))) | |
| See Also | |
| ======== | |
| piecewise_fold | |
| piecewise_exclusive | |
| ITE | |
| """ | |
| nargs = None | |
| is_Piecewise = True | |
| def __new__(cls, *args, **options): | |
| if len(args) == 0: | |
| raise TypeError("At least one (expr, cond) pair expected.") | |
| # (Try to) sympify args first | |
| newargs = [] | |
| for ec in args: | |
| # ec could be a ExprCondPair or a tuple | |
| pair = ExprCondPair(*getattr(ec, 'args', ec)) | |
| cond = pair.cond | |
| if cond is false: | |
| continue | |
| newargs.append(pair) | |
| if cond is true: | |
| break | |
| eval = options.pop('evaluate', global_parameters.evaluate) | |
| if eval: | |
| r = cls.eval(*newargs) | |
| if r is not None: | |
| return r | |
| elif len(newargs) == 1 and newargs[0].cond == True: | |
| return newargs[0].expr | |
| return Basic.__new__(cls, *newargs, **options) | |
| 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 | |
| 4. 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. | |
| EXAMPLES | |
| ======== | |
| >>> from sympy import Piecewise | |
| >>> from sympy.abc import x | |
| >>> cond = -x < -1 | |
| >>> args = [(1, cond), (4, cond), (3, False), (2, True), (5, x < 1)] | |
| >>> Piecewise(*args, evaluate=False) | |
| Piecewise((1, -x < -1), (4, -x < -1), (2, True)) | |
| >>> Piecewise(*args) | |
| Piecewise((1, x > 1), (2, True)) | |
| """ | |
| if not _args: | |
| return Undefined | |
| if len(_args) == 1 and _args[0][-1] == True: | |
| return _args[0][0] | |
| newargs = _piecewise_collapse_arguments(_args) | |
| # 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 doit(self, **hints): | |
| """ | |
| Evaluate this piecewise function. | |
| """ | |
| newargs = [] | |
| for e, c in self.args: | |
| if hints.get('deep', True): | |
| if isinstance(e, Basic): | |
| newe = e.doit(**hints) | |
| if newe != self: | |
| e = newe | |
| if isinstance(c, Basic): | |
| c = c.doit(**hints) | |
| newargs.append((e, c)) | |
| return self.func(*newargs) | |
| def _eval_simplify(self, **kwargs): | |
| return piecewise_simplify(self, **kwargs) | |
| def _eval_as_leading_term(self, x, logx=None, cdir=0): | |
| for e, c in self.args: | |
| if c == True or c.subs(x, 0) == True: | |
| return e.as_leading_term(x) | |
| def _eval_adjoint(self): | |
| return self.func(*[(e.adjoint(), c) for e, c in self.args]) | |
| def _eval_conjugate(self): | |
| return self.func(*[(e.conjugate(), c) for e, c in self.args]) | |
| def _eval_derivative(self, x): | |
| return self.func(*[(diff(e, x), c) for e, c in self.args]) | |
| def _eval_evalf(self, prec): | |
| return self.func(*[(e._evalf(prec), c) for e, c in self.args]) | |
| def _eval_is_meromorphic(self, x, a): | |
| # Conditions often implicitly assume that the argument is real. | |
| # Hence, there needs to be some check for as_set. | |
| if not a.is_real: | |
| return None | |
| # Then, scan ExprCondPairs in the given order to find a piece that would contain a, | |
| # possibly as a boundary point. | |
| for e, c in self.args: | |
| cond = c.subs(x, a) | |
| if cond.is_Relational: | |
| return None | |
| if a in c.as_set().boundary: | |
| return None | |
| # Apply expression if a is an interior point of the domain of e. | |
| if cond: | |
| return e._eval_is_meromorphic(x, a) | |
| def piecewise_integrate(self, x, **kwargs): | |
| """Return the Piecewise with each expression being | |
| replaced with its antiderivative. To obtain a continuous | |
| antiderivative, use the :func:`~.integrate` function or method. | |
| Examples | |
| ======== | |
| >>> from sympy import Piecewise | |
| >>> from sympy.abc import x | |
| >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True)) | |
| >>> p.piecewise_integrate(x) | |
| Piecewise((0, x < 0), (x, x < 1), (2*x, True)) | |
| Note that this does not give a continuous function, e.g. | |
| at x = 1 the 3rd condition applies and the antiderivative | |
| there is 2*x so the value of the antiderivative is 2: | |
| >>> anti = _ | |
| >>> anti.subs(x, 1) | |
| 2 | |
| The continuous derivative accounts for the integral *up to* | |
| the point of interest, however: | |
| >>> p.integrate(x) | |
| Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True)) | |
| >>> _.subs(x, 1) | |
| 1 | |
| See Also | |
| ======== | |
| Piecewise._eval_integral | |
| """ | |
| from sympy.integrals import integrate | |
| return self.func(*[(integrate(e, x, **kwargs), c) for e, c in self.args]) | |
| def _handle_irel(self, x, handler): | |
| """Return either None (if the conditions of self depend only on x) else | |
| a Piecewise expression whose expressions (handled by the handler that | |
| was passed) are paired with the governing x-independent relationals, | |
| e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) -> | |
| Piecewise( | |
| (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)), | |
| (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)), | |
| (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)), | |
| (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True)) | |
| """ | |
| # identify governing relationals | |
| rel = self.atoms(Relational) | |
| irel = list(ordered([r for r in rel if x not in r.free_symbols | |
| and r not in (S.true, S.false)])) | |
| if irel: | |
| args = {} | |
| exprinorder = [] | |
| for truth in product((1, 0), repeat=len(irel)): | |
| reps = dict(zip(irel, truth)) | |
| # only store the true conditions since the false are implied | |
| # when they appear lower in the Piecewise args | |
| if 1 not in truth: | |
| cond = None # flag this one so it doesn't get combined | |
| else: | |
| andargs = Tuple(*[i for i in reps if reps[i]]) | |
| free = list(andargs.free_symbols) | |
| if len(free) == 1: | |
| from sympy.solvers.inequalities import ( | |
| reduce_inequalities, _solve_inequality) | |
| try: | |
| t = reduce_inequalities(andargs, free[0]) | |
| # ValueError when there are potentially | |
| # nonvanishing imaginary parts | |
| except (ValueError, NotImplementedError): | |
| # at least isolate free symbol on left | |
| t = And(*[_solve_inequality( | |
| a, free[0], linear=True) | |
| for a in andargs]) | |
| else: | |
| t = And(*andargs) | |
| if t is S.false: | |
| continue # an impossible combination | |
| cond = t | |
| expr = handler(self.xreplace(reps)) | |
| if isinstance(expr, self.func) and len(expr.args) == 1: | |
| expr, econd = expr.args[0] | |
| cond = And(econd, True if cond is None else cond) | |
| # the ec pairs are being collected since all possibilities | |
| # are being enumerated, but don't put the last one in since | |
| # its expr might match a previous expression and it | |
| # must appear last in the args | |
| if cond is not None: | |
| args.setdefault(expr, []).append(cond) | |
| # but since we only store the true conditions we must maintain | |
| # the order so that the expression with the most true values | |
| # comes first | |
| exprinorder.append(expr) | |
| # convert collected conditions as args of Or | |
| for k in args: | |
| args[k] = Or(*args[k]) | |
| # take them in the order obtained | |
| args = [(e, args[e]) for e in uniq(exprinorder)] | |
| # add in the last arg | |
| args.append((expr, True)) | |
| return Piecewise(*args) | |
| def _eval_integral(self, x, _first=True, **kwargs): | |
| """Return the indefinite integral of the | |
| Piecewise such that subsequent substitution of x with a | |
| value will give the value of the integral (not including | |
| the constant of integration) up to that point. To only | |
| integrate the individual parts of Piecewise, use the | |
| ``piecewise_integrate`` method. | |
| Examples | |
| ======== | |
| >>> from sympy import Piecewise | |
| >>> from sympy.abc import x | |
| >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True)) | |
| >>> p.integrate(x) | |
| Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True)) | |
| >>> p.piecewise_integrate(x) | |
| Piecewise((0, x < 0), (x, x < 1), (2*x, True)) | |
| See Also | |
| ======== | |
| Piecewise.piecewise_integrate | |
| """ | |
| from sympy.integrals.integrals import integrate | |
| if _first: | |
| def handler(ipw): | |
| if isinstance(ipw, self.func): | |
| return ipw._eval_integral(x, _first=False, **kwargs) | |
| else: | |
| return ipw.integrate(x, **kwargs) | |
| irv = self._handle_irel(x, handler) | |
| if irv is not None: | |
| return irv | |
| # handle a Piecewise from -oo to oo with and no x-independent relationals | |
| # ----------------------------------------------------------------------- | |
| ok, abei = self._intervals(x) | |
| if not ok: | |
| from sympy.integrals.integrals import Integral | |
| return Integral(self, x) # unevaluated | |
| pieces = [(a, b) for a, b, _, _ in abei] | |
| oo = S.Infinity | |
| done = [(-oo, oo, -1)] | |
| for k, p in enumerate(pieces): | |
| if p == (-oo, oo): | |
| # all undone intervals will get this key | |
| for j, (a, b, i) in enumerate(done): | |
| if i == -1: | |
| done[j] = a, b, k | |
| break # nothing else to consider | |
| N = len(done) - 1 | |
| for j, (a, b, i) in enumerate(reversed(done)): | |
| if i == -1: | |
| j = N - j | |
| done[j: j + 1] = _clip(p, (a, b), k) | |
| done = [(a, b, i) for a, b, i in done if a != b] | |
| # append an arg if there is a hole so a reference to | |
| # argument -1 will give Undefined | |
| if any(i == -1 for (a, b, i) in done): | |
| abei.append((-oo, oo, Undefined, -1)) | |
| # return the sum of the intervals | |
| args = [] | |
| sum = None | |
| for a, b, i in done: | |
| anti = integrate(abei[i][-2], x, **kwargs) | |
| if sum is None: | |
| sum = anti | |
| else: | |
| sum = sum.subs(x, a) | |
| e = anti._eval_interval(x, a, x) | |
| if sum.has(*_illegal) or e.has(*_illegal): | |
| sum = anti | |
| else: | |
| sum += e | |
| # see if we know whether b is contained in original | |
| # condition | |
| if b is S.Infinity: | |
| cond = True | |
| elif self.args[abei[i][-1]].cond.subs(x, b) == False: | |
| cond = (x < b) | |
| else: | |
| cond = (x <= b) | |
| args.append((sum, cond)) | |
| return Piecewise(*args) | |
| def _eval_interval(self, sym, a, b, _first=True): | |
| """Evaluates the function along the sym in a given interval [a, b]""" | |
| # FIXME: Currently complex intervals are not supported. A possible | |
| # replacement algorithm, discussed in issue 5227, can be found in the | |
| # following papers; | |
| # http://portal.acm.org/citation.cfm?id=281649 | |
| # http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.4127&rep=rep1&type=pdf | |
| if a is None or b is None: | |
| # In this case, it is just simple substitution | |
| return super()._eval_interval(sym, a, b) | |
| else: | |
| x, lo, hi = map(as_Basic, (sym, a, b)) | |
| if _first: # get only x-dependent relationals | |
| def handler(ipw): | |
| if isinstance(ipw, self.func): | |
| return ipw._eval_interval(x, lo, hi, _first=None) | |
| else: | |
| return ipw._eval_interval(x, lo, hi) | |
| irv = self._handle_irel(x, handler) | |
| if irv is not None: | |
| return irv | |
| if (lo < hi) is S.false or ( | |
| lo is S.Infinity or hi is S.NegativeInfinity): | |
| rv = self._eval_interval(x, hi, lo, _first=False) | |
| if isinstance(rv, Piecewise): | |
| rv = Piecewise(*[(-e, c) for e, c in rv.args]) | |
| else: | |
| rv = -rv | |
| return rv | |
| if (lo < hi) is S.true or ( | |
| hi is S.Infinity or lo is S.NegativeInfinity): | |
| pass | |
| else: | |
| _a = Dummy('lo') | |
| _b = Dummy('hi') | |
| a = lo if lo.is_comparable else _a | |
| b = hi if hi.is_comparable else _b | |
| pos = self._eval_interval(x, a, b, _first=False) | |
| if a == _a and b == _b: | |
| # it's purely symbolic so just swap lo and hi and | |
| # change the sign to get the value for when lo > hi | |
| neg, pos = (-pos.xreplace({_a: hi, _b: lo}), | |
| pos.xreplace({_a: lo, _b: hi})) | |
| else: | |
| # at least one of the bounds was comparable, so allow | |
| # _eval_interval to use that information when computing | |
| # the interval with lo and hi reversed | |
| neg, pos = (-self._eval_interval(x, hi, lo, _first=False), | |
| pos.xreplace({_a: lo, _b: hi})) | |
| # allow simplification based on ordering of lo and hi | |
| p = Dummy('', positive=True) | |
| if lo.is_Symbol: | |
| pos = pos.xreplace({lo: hi - p}).xreplace({p: hi - lo}) | |
| neg = neg.xreplace({lo: hi + p}).xreplace({p: lo - hi}) | |
| elif hi.is_Symbol: | |
| pos = pos.xreplace({hi: lo + p}).xreplace({p: hi - lo}) | |
| neg = neg.xreplace({hi: lo - p}).xreplace({p: lo - hi}) | |
| # evaluate limits that may have unevaluate Min/Max | |
| touch = lambda _: _.replace( | |
| lambda x: isinstance(x, (Min, Max)), | |
| lambda x: x.func(*x.args)) | |
| neg = touch(neg) | |
| pos = touch(pos) | |
| # assemble return expression; make the first condition be Lt | |
| # b/c then the first expression will look the same whether | |
| # the lo or hi limit is symbolic | |
| if a == _a: # the lower limit was symbolic | |
| rv = Piecewise( | |
| (pos, | |
| lo < hi), | |
| (neg, | |
| True)) | |
| else: | |
| rv = Piecewise( | |
| (neg, | |
| hi < lo), | |
| (pos, | |
| True)) | |
| if rv == Undefined: | |
| raise ValueError("Can't integrate across undefined region.") | |
| if any(isinstance(i, Piecewise) for i in (pos, neg)): | |
| rv = piecewise_fold(rv) | |
| return rv | |
| # handle a Piecewise with lo <= hi and no x-independent relationals | |
| # ----------------------------------------------------------------- | |
| ok, abei = self._intervals(x) | |
| if not ok: | |
| from sympy.integrals.integrals import Integral | |
| # not being able to do the interval of f(x) can | |
| # be stated as not being able to do the integral | |
| # of f'(x) over the same range | |
| return Integral(self.diff(x), (x, lo, hi)) # unevaluated | |
| pieces = [(a, b) for a, b, _, _ in abei] | |
| done = [(lo, hi, -1)] | |
| oo = S.Infinity | |
| for k, p in enumerate(pieces): | |
| if p[:2] == (-oo, oo): | |
| # all undone intervals will get this key | |
| for j, (a, b, i) in enumerate(done): | |
| if i == -1: | |
| done[j] = a, b, k | |
| break # nothing else to consider | |
| N = len(done) - 1 | |
| for j, (a, b, i) in enumerate(reversed(done)): | |
| if i == -1: | |
| j = N - j | |
| done[j: j + 1] = _clip(p, (a, b), k) | |
| done = [(a, b, i) for a, b, i in done if a != b] | |
| # return the sum of the intervals | |
| sum = S.Zero | |
| upto = None | |
| for a, b, i in done: | |
| if i == -1: | |
| if upto is None: | |
| return Undefined | |
| # TODO simplify hi <= upto | |
| return Piecewise((sum, hi <= upto), (Undefined, True)) | |
| sum += abei[i][-2]._eval_interval(x, a, b) | |
| upto = b | |
| return sum | |
| def _intervals(self, sym, err_on_Eq=False): | |
| r"""Return a bool and a message (when bool is False), else a | |
| list of unique tuples, (a, b, e, i), where a and b | |
| are the lower and upper bounds in which the expression e of | |
| argument i in self is defined and $a < b$ (when involving | |
| numbers) or $a \le b$ when involving symbols. | |
| If there are any relationals not involving sym, or any | |
| relational cannot be solved for sym, the bool will be False | |
| a message be given as the second return value. The calling | |
| routine should have removed such relationals before calling | |
| this routine. | |
| The evaluated conditions will be returned as ranges. | |
| Discontinuous ranges will be returned separately with | |
| identical expressions. The first condition that evaluates to | |
| True will be returned as the last tuple with a, b = -oo, oo. | |
| """ | |
| from sympy.solvers.inequalities import _solve_inequality | |
| assert isinstance(self, Piecewise) | |
| def nonsymfail(cond): | |
| return False, filldedent(''' | |
| A condition not involving | |
| %s appeared: %s''' % (sym, cond)) | |
| def _solve_relational(r): | |
| if sym not in r.free_symbols: | |
| return nonsymfail(r) | |
| try: | |
| rv = _solve_inequality(r, sym) | |
| except NotImplementedError: | |
| return False, 'Unable to solve relational %s for %s.' % (r, sym) | |
| if isinstance(rv, Relational): | |
| free = rv.args[1].free_symbols | |
| if rv.args[0] != sym or sym in free: | |
| return False, 'Unable to solve relational %s for %s.' % (r, sym) | |
| if rv.rel_op == '==': | |
| # this equality has been affirmed to have the form | |
| # Eq(sym, rhs) where rhs is sym-free; it represents | |
| # a zero-width interval which will be ignored | |
| # whether it is an isolated condition or contained | |
| # within an And or an Or | |
| rv = S.false | |
| elif rv.rel_op == '!=': | |
| try: | |
| rv = Or(sym < rv.rhs, sym > rv.rhs) | |
| except TypeError: | |
| # e.g. x != I ==> all real x satisfy | |
| rv = S.true | |
| elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): | |
| rv = S.true | |
| return True, rv | |
| args = list(self.args) | |
| # make self canonical wrt Relationals | |
| keys = self.atoms(Relational) | |
| reps = {} | |
| for r in keys: | |
| ok, s = _solve_relational(r) | |
| if ok != True: | |
| return False, ok | |
| reps[r] = s | |
| # process args individually so if any evaluate, their position | |
| # in the original Piecewise will be known | |
| args = [i.xreplace(reps) for i in self.args] | |
| # precondition args | |
| expr_cond = [] | |
| default = idefault = None | |
| for i, (expr, cond) in enumerate(args): | |
| if cond is S.false: | |
| continue | |
| if cond is S.true: | |
| default = expr | |
| idefault = i | |
| break | |
| if isinstance(cond, Eq): | |
| # unanticipated condition, but it is here in case a | |
| # replacement caused an Eq to appear | |
| if err_on_Eq: | |
| return False, 'encountered Eq condition: %s' % cond | |
| continue # zero width interval | |
| cond = to_cnf(cond) | |
| if isinstance(cond, And): | |
| cond = distribute_or_over_and(cond) | |
| if isinstance(cond, Or): | |
| expr_cond.extend( | |
| [(i, expr, o) for o in cond.args | |
| if not isinstance(o, Eq)]) | |
| elif cond is not S.false: | |
| expr_cond.append((i, expr, cond)) | |
| elif cond is S.true: | |
| default = expr | |
| idefault = i | |
| break | |
| # determine intervals represented by conditions | |
| int_expr = [] | |
| for iarg, expr, cond in expr_cond: | |
| if isinstance(cond, And): | |
| lower = S.NegativeInfinity | |
| upper = S.Infinity | |
| exclude = [] | |
| for cond2 in cond.args: | |
| if not isinstance(cond2, Relational): | |
| return False, 'expecting only Relationals' | |
| if isinstance(cond2, Eq): | |
| lower = upper # ignore | |
| if err_on_Eq: | |
| return False, 'encountered secondary Eq condition' | |
| break | |
| elif isinstance(cond2, Ne): | |
| l, r = cond2.args | |
| if l == sym: | |
| exclude.append(r) | |
| elif r == sym: | |
| exclude.append(l) | |
| else: | |
| return nonsymfail(cond2) | |
| continue | |
| elif cond2.lts == sym: | |
| upper = Min(cond2.gts, upper) | |
| elif cond2.gts == sym: | |
| lower = Max(cond2.lts, lower) | |
| else: | |
| return nonsymfail(cond2) # should never get here | |
| if exclude: | |
| exclude = list(ordered(exclude)) | |
| newcond = [] | |
| for i, e in enumerate(exclude): | |
| if e < lower == True or e > upper == True: | |
| continue | |
| if not newcond: | |
| newcond.append((None, lower)) # add a primer | |
| newcond.append((newcond[-1][1], e)) | |
| newcond.append((newcond[-1][1], upper)) | |
| newcond.pop(0) # remove the primer | |
| expr_cond.extend([(iarg, expr, And(i[0] < sym, sym < i[1])) for i in newcond]) | |
| continue | |
| elif isinstance(cond, Relational) and cond.rel_op != '!=': | |
| lower, upper = cond.lts, cond.gts # part 1: initialize with givens | |
| if cond.lts == sym: # part 1a: expand the side ... | |
| lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0 | |
| elif cond.gts == sym: # part 1a: ... that can be expanded | |
| upper = S.Infinity # e.g. x >= 0 ---> oo >= 0 | |
| else: | |
| return nonsymfail(cond) | |
| else: | |
| return False, 'unrecognized condition: %s' % cond | |
| lower, upper = lower, Max(lower, upper) | |
| if err_on_Eq and lower == upper: | |
| return False, 'encountered Eq condition' | |
| if (lower >= upper) is not S.true: | |
| int_expr.append((lower, upper, expr, iarg)) | |
| if default is not None: | |
| int_expr.append( | |
| (S.NegativeInfinity, S.Infinity, default, idefault)) | |
| return True, list(uniq(int_expr)) | |
| def _eval_nseries(self, x, n, logx, cdir=0): | |
| args = [(ec.expr._eval_nseries(x, n, logx), ec.cond) for ec in self.args] | |
| return self.func(*args) | |
| def _eval_power(self, s): | |
| return self.func(*[(e**s, c) for e, c in self.args]) | |
| def _eval_subs(self, old, new): | |
| # this is strictly not necessary, but we can keep track | |
| # of whether True or False conditions arise and be | |
| # somewhat more efficient by avoiding other substitutions | |
| # and avoiding invalid conditions that appear after a | |
| # True condition | |
| args = list(self.args) | |
| args_exist = False | |
| for i, (e, c) in enumerate(args): | |
| c = c._subs(old, new) | |
| if c != False: | |
| args_exist = True | |
| e = e._subs(old, new) | |
| args[i] = (e, c) | |
| if c == True: | |
| break | |
| if not args_exist: | |
| args = ((Undefined, True),) | |
| return self.func(*args) | |
| def _eval_transpose(self): | |
| return self.func(*[(e.transpose(), c) for e, c in self.args]) | |
| def _eval_template_is_attr(self, is_attr): | |
| b = None | |
| for expr, _ in self.args: | |
| a = getattr(expr, is_attr) | |
| if a is None: | |
| return | |
| if b is None: | |
| b = a | |
| elif b is not a: | |
| return | |
| return b | |
| _eval_is_finite = lambda self: self._eval_template_is_attr( | |
| 'is_finite') | |
| _eval_is_complex = lambda self: self._eval_template_is_attr('is_complex') | |
| _eval_is_even = lambda self: self._eval_template_is_attr('is_even') | |
| _eval_is_imaginary = lambda self: self._eval_template_is_attr( | |
| 'is_imaginary') | |
| _eval_is_integer = lambda self: self._eval_template_is_attr('is_integer') | |
| _eval_is_irrational = lambda self: self._eval_template_is_attr( | |
| 'is_irrational') | |
| _eval_is_negative = lambda self: self._eval_template_is_attr('is_negative') | |
| _eval_is_nonnegative = lambda self: self._eval_template_is_attr( | |
| 'is_nonnegative') | |
| _eval_is_nonpositive = lambda self: self._eval_template_is_attr( | |
| 'is_nonpositive') | |
| _eval_is_nonzero = lambda self: self._eval_template_is_attr( | |
| 'is_nonzero') | |
| _eval_is_odd = lambda self: self._eval_template_is_attr('is_odd') | |
| _eval_is_polar = lambda self: self._eval_template_is_attr('is_polar') | |
| _eval_is_positive = lambda self: self._eval_template_is_attr('is_positive') | |
| _eval_is_extended_real = lambda self: self._eval_template_is_attr( | |
| 'is_extended_real') | |
| _eval_is_extended_positive = lambda self: self._eval_template_is_attr( | |
| 'is_extended_positive') | |
| _eval_is_extended_negative = lambda self: self._eval_template_is_attr( | |
| 'is_extended_negative') | |
| _eval_is_extended_nonzero = lambda self: self._eval_template_is_attr( | |
| 'is_extended_nonzero') | |
| _eval_is_extended_nonpositive = lambda self: self._eval_template_is_attr( | |
| 'is_extended_nonpositive') | |
| _eval_is_extended_nonnegative = lambda self: self._eval_template_is_attr( | |
| 'is_extended_nonnegative') | |
| _eval_is_real = lambda self: self._eval_template_is_attr('is_real') | |
| _eval_is_zero = lambda self: self._eval_template_is_attr( | |
| 'is_zero') | |
| def __eval_cond(cls, cond): | |
| """Return the truth value of the condition.""" | |
| if cond == True: | |
| return True | |
| if isinstance(cond, Eq): | |
| try: | |
| diff = cond.lhs - cond.rhs | |
| if diff.is_commutative: | |
| return diff.is_zero | |
| except TypeError: | |
| pass | |
| def as_expr_set_pairs(self, domain=None): | |
| """Return tuples for each argument of self that give | |
| the expression and the interval in which it is valid | |
| which is contained within the given domain. | |
| If a condition cannot be converted to a set, an error | |
| will be raised. The variable of the conditions is | |
| assumed to be real; sets of real values are returned. | |
| Examples | |
| ======== | |
| >>> from sympy import Piecewise, Interval | |
| >>> from sympy.abc import x | |
| >>> p = Piecewise( | |
| ... (1, x < 2), | |
| ... (2,(x > 0) & (x < 4)), | |
| ... (3, True)) | |
| >>> p.as_expr_set_pairs() | |
| [(1, Interval.open(-oo, 2)), | |
| (2, Interval.Ropen(2, 4)), | |
| (3, Interval(4, oo))] | |
| >>> p.as_expr_set_pairs(Interval(0, 3)) | |
| [(1, Interval.Ropen(0, 2)), | |
| (2, Interval(2, 3))] | |
| """ | |
| if domain is None: | |
| domain = S.Reals | |
| exp_sets = [] | |
| U = domain | |
| complex = not domain.is_subset(S.Reals) | |
| cond_free = set() | |
| for expr, cond in self.args: | |
| cond_free |= cond.free_symbols | |
| if len(cond_free) > 1: | |
| raise NotImplementedError(filldedent(''' | |
| multivariate conditions are not handled.''')) | |
| if complex: | |
| for i in cond.atoms(Relational): | |
| if not isinstance(i, (Eq, Ne)): | |
| raise ValueError(filldedent(''' | |
| Inequalities in the complex domain are | |
| not supported. Try the real domain by | |
| setting domain=S.Reals''')) | |
| cond_int = U.intersect(cond.as_set()) | |
| U = U - cond_int | |
| if cond_int != S.EmptySet: | |
| exp_sets.append((expr, cond_int)) | |
| return exp_sets | |
| 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_KroneckerDelta(self, *args, **kwargs): | |
| from sympy.functions.special.tensor_functions import KroneckerDelta | |
| rules = { | |
| And: [False, False], | |
| Or: [True, True], | |
| Not: [True, False], | |
| Eq: [None, None], | |
| Ne: [None, None] | |
| } | |
| class UnrecognizedCondition(Exception): | |
| pass | |
| def rewrite(cond): | |
| if isinstance(cond, Eq): | |
| return KroneckerDelta(*cond.args) | |
| if isinstance(cond, Ne): | |
| return 1 - KroneckerDelta(*cond.args) | |
| cls, args = type(cond), cond.args | |
| if cls not in rules: | |
| raise UnrecognizedCondition(cls) | |
| b1, b2 = rules[cls] | |
| k = Mul(*[1 - rewrite(c) for c in args]) if b1 else Mul(*[rewrite(c) for c in args]) | |
| if b2: | |
| return 1 - k | |
| return k | |
| conditions = [] | |
| true_value = None | |
| for value, cond in args: | |
| if type(cond) in rules: | |
| conditions.append((value, cond)) | |
| elif cond is S.true: | |
| if true_value is None: | |
| true_value = value | |
| else: | |
| return | |
| if true_value is not None: | |
| result = true_value | |
| for value, cond in conditions[::-1]: | |
| try: | |
| k = rewrite(cond) | |
| result = k * value + (1 - k) * result | |
| except UnrecognizedCondition: | |
| return | |
| return result | |
| def piecewise_fold(expr, evaluate=True): | |
| """ | |
| Takes an expression containing a piecewise function and returns the | |
| expression in piecewise form. In addition, any ITE conditions are | |
| rewritten in negation normal form and simplified. | |
| The final Piecewise is evaluated (default) but if the raw form | |
| is desired, send ``evaluate=False``; if trivial evaluation is | |
| desired, send ``evaluate=None`` and duplicate conditions and | |
| processing of True and False will be handled. | |
| Examples | |
| ======== | |
| >>> from sympy import Piecewise, piecewise_fold, S | |
| >>> from sympy.abc import x | |
| >>> p = Piecewise((x, x < 1), (1, S(1) <= x)) | |
| >>> piecewise_fold(x*p) | |
| Piecewise((x**2, x < 1), (x, True)) | |
| See Also | |
| ======== | |
| Piecewise | |
| piecewise_exclusive | |
| """ | |
| if not isinstance(expr, Basic) or not expr.has(Piecewise): | |
| return expr | |
| new_args = [] | |
| if isinstance(expr, (ExprCondPair, Piecewise)): | |
| for e, c in expr.args: | |
| if not isinstance(e, Piecewise): | |
| e = piecewise_fold(e) | |
| # we don't keep Piecewise in condition because | |
| # it has to be checked to see that it's complete | |
| # and we convert it to ITE at that time | |
| assert not c.has(Piecewise) # pragma: no cover | |
| if isinstance(c, ITE): | |
| c = c.to_nnf() | |
| c = simplify_logic(c, form='cnf') | |
| if isinstance(e, Piecewise): | |
| new_args.extend([(piecewise_fold(ei), And(ci, c)) | |
| for ei, ci in e.args]) | |
| else: | |
| new_args.append((e, c)) | |
| else: | |
| # Given | |
| # P1 = Piecewise((e11, c1), (e12, c2), A) | |
| # P2 = Piecewise((e21, c1), (e22, c2), B) | |
| # ... | |
| # the folding of f(P1, P2) is trivially | |
| # Piecewise( | |
| # (f(e11, e21), c1), | |
| # (f(e12, e22), c2), | |
| # (f(Piecewise(A), Piecewise(B)), True)) | |
| # Certain objects end up rewriting themselves as thus, so | |
| # we do that grouping before the more generic folding. | |
| # The following applies this idea when f = Add or f = Mul | |
| # (and the expression is commutative). | |
| if expr.is_Add or expr.is_Mul and expr.is_commutative: | |
| p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True) | |
| pc = sift(p, lambda x: tuple([c for e,c in x.args])) | |
| for c in list(ordered(pc)): | |
| if len(pc[c]) > 1: | |
| pargs = [list(i.args) for i in pc[c]] | |
| # the first one is the same; there may be more | |
| com = common_prefix(*[ | |
| [i.cond for i in j] for j in pargs]) | |
| n = len(com) | |
| collected = [] | |
| for i in range(n): | |
| collected.append(( | |
| expr.func(*[ai[i].expr for ai in pargs]), | |
| com[i])) | |
| remains = [] | |
| for a in pargs: | |
| if n == len(a): # no more args | |
| continue | |
| if a[n].cond == True: # no longer Piecewise | |
| remains.append(a[n].expr) | |
| else: # restore the remaining Piecewise | |
| remains.append( | |
| Piecewise(*a[n:], evaluate=False)) | |
| if remains: | |
| collected.append((expr.func(*remains), True)) | |
| args.append(Piecewise(*collected, evaluate=False)) | |
| continue | |
| args.extend(pc[c]) | |
| else: | |
| args = expr.args | |
| # fold | |
| folded = list(map(piecewise_fold, args)) | |
| for ec in product(*[ | |
| (i.args if isinstance(i, Piecewise) else | |
| [(i, true)]) for i in folded]): | |
| e, c = zip(*ec) | |
| new_args.append((expr.func(*e), And(*c))) | |
| if evaluate is None: | |
| # don't return duplicate conditions, otherwise don't evaluate | |
| new_args = list(reversed([(e, c) for c, e in { | |
| c: e for e, c in reversed(new_args)}.items()])) | |
| rv = Piecewise(*new_args, evaluate=evaluate) | |
| if evaluate is None and len(rv.args) == 1 and rv.args[0].cond == True: | |
| return rv.args[0].expr | |
| if any(s.expr.has(Piecewise) for p in rv.atoms(Piecewise) for s in p.args): | |
| return piecewise_fold(rv) | |
| return rv | |
| def _clip(A, B, k): | |
| """Return interval B as intervals that are covered by A (keyed | |
| to k) and all other intervals of B not covered by A keyed to -1. | |
| The reference point of each interval is the rhs; if the lhs is | |
| greater than the rhs then an interval of zero width interval will | |
| result, e.g. (4, 1) is treated like (1, 1). | |
| Examples | |
| ======== | |
| >>> from sympy.functions.elementary.piecewise import _clip | |
| >>> from sympy import Tuple | |
| >>> A = Tuple(1, 3) | |
| >>> B = Tuple(2, 4) | |
| >>> _clip(A, B, 0) | |
| [(2, 3, 0), (3, 4, -1)] | |
| Interpretation: interval portion (2, 3) of interval (2, 4) is | |
| covered by interval (1, 3) and is keyed to 0 as requested; | |
| interval (3, 4) was not covered by (1, 3) and is keyed to -1. | |
| """ | |
| a, b = B | |
| c, d = A | |
| c, d = Min(Max(c, a), b), Min(Max(d, a), b) | |
| a, b = Min(a, b), b | |
| p = [] | |
| if a != c: | |
| p.append((a, c, -1)) | |
| else: | |
| pass | |
| if c != d: | |
| p.append((c, d, k)) | |
| else: | |
| pass | |
| if b != d: | |
| if d == c and p and p[-1][-1] == -1: | |
| p[-1] = p[-1][0], b, -1 | |
| else: | |
| p.append((d, b, -1)) | |
| else: | |
| pass | |
| return p | |
| def piecewise_simplify_arguments(expr, **kwargs): | |
| from sympy.simplify.simplify import simplify | |
| # simplify conditions | |
| f1 = expr.args[0].cond.free_symbols | |
| args = None | |
| if len(f1) == 1 and not expr.atoms(Eq): | |
| x = f1.pop() | |
| # this won't return intervals involving Eq | |
| # and it won't handle symbols treated as | |
| # booleans | |
| ok, abe_ = expr._intervals(x, err_on_Eq=True) | |
| def include(c, x, a): | |
| "return True if c.subs(x, a) is True, else False" | |
| try: | |
| return c.subs(x, a) == True | |
| except TypeError: | |
| return False | |
| if ok: | |
| args = [] | |
| covered = S.EmptySet | |
| from sympy.sets.sets import Interval | |
| for a, b, e, i in abe_: | |
| c = expr.args[i].cond | |
| incl_a = include(c, x, a) | |
| incl_b = include(c, x, b) | |
| iv = Interval(a, b, not incl_a, not incl_b) | |
| cset = iv - covered | |
| if not cset: | |
| continue | |
| try: | |
| a = cset.inf | |
| except NotImplementedError: | |
| pass # continue with the given `a` | |
| else: | |
| incl_a = include(c, x, a) | |
| if incl_a and incl_b: | |
| if a.is_infinite and b.is_infinite: | |
| c = S.true | |
| elif b.is_infinite: | |
| c = (x > a) if a in covered else (x >= a) | |
| elif a.is_infinite: | |
| c = (x <= b) | |
| elif a in covered: | |
| c = And(a < x, x <= b) | |
| else: | |
| c = And(a <= x, x <= b) | |
| elif incl_a: | |
| if a.is_infinite: | |
| c = (x < b) | |
| elif a in covered: | |
| c = And(a < x, x < b) | |
| else: | |
| c = And(a <= x, x < b) | |
| elif incl_b: | |
| if b.is_infinite: | |
| c = (x > a) | |
| else: | |
| c = And(a < x, x <= b) | |
| else: | |
| if a in covered: | |
| c = (x < b) | |
| else: | |
| c = And(a < x, x < b) | |
| covered |= iv | |
| if a is S.NegativeInfinity and incl_a: | |
| covered |= {S.NegativeInfinity} | |
| if b is S.Infinity and incl_b: | |
| covered |= {S.Infinity} | |
| args.append((e, c)) | |
| if not S.Reals.is_subset(covered): | |
| args.append((Undefined, True)) | |
| if args is None: | |
| args = list(expr.args) | |
| for i in range(len(args)): | |
| e, c = args[i] | |
| if isinstance(c, Basic): | |
| c = simplify(c, **kwargs) | |
| args[i] = (e, c) | |
| # simplify expressions | |
| doit = kwargs.pop('doit', None) | |
| for i in range(len(args)): | |
| e, c = args[i] | |
| if isinstance(e, Basic): | |
| # Skip doit to avoid growth at every call for some integrals | |
| # and sums, see sympy/sympy#17165 | |
| newe = simplify(e, doit=False, **kwargs) | |
| if newe != e: | |
| e = newe | |
| args[i] = (e, c) | |
| # restore kwargs flag | |
| if doit is not None: | |
| kwargs['doit'] = doit | |
| return Piecewise(*args) | |
| def _piecewise_collapse_arguments(_args): | |
| newargs = [] # the unevaluated conditions | |
| current_cond = set() # the conditions up to a given e, c pair | |
| for expr, cond in _args: | |
| cond = cond.replace( | |
| lambda _: _.is_Relational, _canonical_coeff) | |
| # 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): | |
| if c.negated.canonical in current_cond: | |
| continue | |
| # if a strict inequality appears after | |
| # a non-strict one, then the condition is | |
| # redundant | |
| if isinstance(c, (Lt, Gt)) and ( | |
| c.weak in current_cond): | |
| cond = False | |
| break | |
| nonredundant.append(c) | |
| else: | |
| 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: | |
| continue | |
| newargs.append(ExprCondPair(expr, cond)) | |
| return newargs | |
| _blessed = lambda e: getattr(e.lhs, '_diff_wrt', False) and ( | |
| getattr(e.rhs, '_diff_wrt', None) or | |
| isinstance(e.rhs, (Rational, NumberSymbol))) | |
| def piecewise_simplify(expr, **kwargs): | |
| expr = piecewise_simplify_arguments(expr, **kwargs) | |
| if not isinstance(expr, Piecewise): | |
| return expr | |
| args = list(expr.args) | |
| args = _piecewise_simplify_eq_and(args) | |
| args = _piecewise_simplify_equal_to_next_segment(args) | |
| return Piecewise(*args) | |
| def _piecewise_simplify_equal_to_next_segment(args): | |
| """ | |
| See if expressions valid for an Equal expression happens to evaluate | |
| to the same function as in the next piecewise segment, see: | |
| https://github.com/sympy/sympy/issues/8458 | |
| """ | |
| prevexpr = None | |
| for i, (expr, cond) in reversed(list(enumerate(args))): | |
| if prevexpr is not None: | |
| if isinstance(cond, And): | |
| eqs, other = sift(cond.args, | |
| lambda i: isinstance(i, Eq), binary=True) | |
| elif isinstance(cond, Eq): | |
| eqs, other = [cond], [] | |
| else: | |
| eqs = other = [] | |
| _prevexpr = prevexpr | |
| _expr = expr | |
| if eqs and not other: | |
| eqs = list(ordered(eqs)) | |
| for e in eqs: | |
| # allow 2 args to collapse into 1 for any e | |
| # otherwise limit simplification to only simple-arg | |
| # Eq instances | |
| if len(args) == 2 or _blessed(e): | |
| _prevexpr = _prevexpr.subs(*e.args) | |
| _expr = _expr.subs(*e.args) | |
| # Did it evaluate to the same? | |
| if _prevexpr == _expr: | |
| # Set the expression for the Not equal section to the same | |
| # as the next. These will be merged when creating the new | |
| # Piecewise | |
| args[i] = args[i].func(args[i + 1][0], cond) | |
| else: | |
| # Update the expression that we compare against | |
| prevexpr = expr | |
| else: | |
| prevexpr = expr | |
| return args | |
| def _piecewise_simplify_eq_and(args): | |
| """ | |
| Try to simplify conditions and the expression for | |
| equalities that are part of the condition, e.g. | |
| Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True)) | |
| -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True)) | |
| """ | |
| for i, (expr, cond) in enumerate(args): | |
| if isinstance(cond, And): | |
| eqs, other = sift(cond.args, | |
| lambda i: isinstance(i, Eq), binary=True) | |
| elif isinstance(cond, Eq): | |
| eqs, other = [cond], [] | |
| else: | |
| eqs = other = [] | |
| if eqs: | |
| eqs = list(ordered(eqs)) | |
| for j, e in enumerate(eqs): | |
| # these blessed lhs objects behave like Symbols | |
| # and the rhs are simple replacements for the "symbols" | |
| if _blessed(e): | |
| expr = expr.subs(*e.args) | |
| eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]] | |
| other = [ei.subs(*e.args) for ei in other] | |
| cond = And(*(eqs + other)) | |
| args[i] = args[i].func(expr, cond) | |
| return args | |
| def piecewise_exclusive(expr, *, skip_nan=False, deep=True): | |
| """ | |
| Rewrite :class:`Piecewise` with mutually exclusive conditions. | |
| Explanation | |
| =========== | |
| SymPy represents the conditions of a :class:`Piecewise` in an | |
| "if-elif"-fashion, allowing more than one condition to be simultaneously | |
| True. The interpretation is that the first condition that is True is the | |
| case that holds. While this is a useful representation computationally it | |
| is not how a piecewise formula is typically shown in a mathematical text. | |
| The :func:`piecewise_exclusive` function can be used to rewrite any | |
| :class:`Piecewise` with more typical mutually exclusive conditions. | |
| Note that further manipulation of the resulting :class:`Piecewise`, e.g. | |
| simplifying it, will most likely make it non-exclusive. Hence, this is | |
| primarily a function to be used in conjunction with printing the Piecewise | |
| or if one would like to reorder the expression-condition pairs. | |
| If it is not possible to determine that all possibilities are covered by | |
| the different cases of the :class:`Piecewise` then a final | |
| :class:`~sympy.core.numbers.NaN` case will be included explicitly. This | |
| can be prevented by passing ``skip_nan=True``. | |
| Examples | |
| ======== | |
| >>> from sympy import piecewise_exclusive, Symbol, Piecewise, S | |
| >>> x = Symbol('x', real=True) | |
| >>> p = Piecewise((0, x < 0), (S.Half, x <= 0), (1, True)) | |
| >>> piecewise_exclusive(p) | |
| Piecewise((0, x < 0), (1/2, Eq(x, 0)), (1, x > 0)) | |
| >>> piecewise_exclusive(Piecewise((2, x > 1))) | |
| Piecewise((2, x > 1), (nan, x <= 1)) | |
| >>> piecewise_exclusive(Piecewise((2, x > 1)), skip_nan=True) | |
| Piecewise((2, x > 1)) | |
| Parameters | |
| ========== | |
| expr: a SymPy expression. | |
| Any :class:`Piecewise` in the expression will be rewritten. | |
| skip_nan: ``bool`` (default ``False``) | |
| If ``skip_nan`` is set to ``True`` then a final | |
| :class:`~sympy.core.numbers.NaN` case will not be included. | |
| deep: ``bool`` (default ``True``) | |
| If ``deep`` is ``True`` then :func:`piecewise_exclusive` will rewrite | |
| any :class:`Piecewise` subexpressions in ``expr`` rather than just | |
| rewriting ``expr`` itself. | |
| Returns | |
| ======= | |
| An expression equivalent to ``expr`` but where all :class:`Piecewise` have | |
| been rewritten with mutually exclusive conditions. | |
| See Also | |
| ======== | |
| Piecewise | |
| piecewise_fold | |
| """ | |
| def make_exclusive(*pwargs): | |
| cumcond = false | |
| newargs = [] | |
| # Handle the first n-1 cases | |
| for expr_i, cond_i in pwargs[:-1]: | |
| cancond = And(cond_i, Not(cumcond)).simplify() | |
| cumcond = Or(cond_i, cumcond).simplify() | |
| newargs.append((expr_i, cancond)) | |
| # For the nth case defer simplification of cumcond | |
| expr_n, cond_n = pwargs[-1] | |
| cancond_n = And(cond_n, Not(cumcond)).simplify() | |
| newargs.append((expr_n, cancond_n)) | |
| if not skip_nan: | |
| cumcond = Or(cond_n, cumcond).simplify() | |
| if cumcond is not true: | |
| newargs.append((Undefined, Not(cumcond).simplify())) | |
| return Piecewise(*newargs, evaluate=False) | |
| if deep: | |
| return expr.replace(Piecewise, make_exclusive) | |
| elif isinstance(expr, Piecewise): | |
| return make_exclusive(*expr.args) | |
| else: | |
| return expr | |