| from sympy.core import S, diff, Tuple, Dummy, Mul |
| from sympy.core.basic import Basic, as_Basic |
| from sympy.core.function import DefinedFunction |
| 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 |
|
|
| 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) |
|
|
| @property |
| def expr(self): |
| """ |
| Returns the expression of this pair. |
| """ |
| return self.args[0] |
|
|
| @property |
| def cond(self): |
| """ |
| Returns the condition of this pair. |
| """ |
| return self.args[1] |
|
|
| @property |
| 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(DefinedFunction): |
| """ |
| 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.") |
| |
| newargs = [] |
| for ec in args: |
| |
| 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) |
|
|
| @classmethod |
| 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) |
|
|
| |
| missing = len(newargs) != len(_args) |
| |
| same = all(a == b for a, b in zip(newargs, _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, cdir): |
| 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): |
| |
| |
| if not a.is_real: |
| return None |
|
|
| |
| |
| 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 |
| |
| 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)) |
| """ |
| |
| 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)) |
| |
| |
| if 1 not in truth: |
| cond = None |
| 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]) |
| |
| |
| except (ValueError, NotImplementedError): |
| |
| t = And(*[_solve_inequality( |
| a, free[0], linear=True) |
| for a in andargs]) |
| else: |
| t = And(*andargs) |
| if t is S.false: |
| continue |
| 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) |
| |
| |
| |
| |
| if cond is not None: |
| args.setdefault(expr, []).append(cond) |
| |
| |
| |
| exprinorder.append(expr) |
| |
| for k in args: |
| args[k] = Or(*args[k]) |
| |
| args = [(e, args[e]) for e in uniq(exprinorder)] |
| |
| 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 |
|
|
| |
| |
| ok, abei = self._intervals(x) |
| if not ok: |
| from sympy.integrals.integrals import Integral |
| return Integral(self, x) |
|
|
| 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): |
| |
| for j, (a, b, i) in enumerate(done): |
| if i == -1: |
| done[j] = a, b, k |
| break |
| 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] |
|
|
| |
| |
| if any(i == -1 for (a, b, i) in done): |
| abei.append((-oo, oo, Undefined, -1)) |
|
|
| |
| 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 |
| |
| |
| 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]""" |
| |
| |
| |
| |
| |
|
|
| if a is None or b is None: |
| |
| return super()._eval_interval(sym, a, b) |
| else: |
| x, lo, hi = map(as_Basic, (sym, a, b)) |
|
|
| if _first: |
| 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: |
| |
| |
| neg, pos = (-pos.xreplace({_a: hi, _b: lo}), |
| pos.xreplace({_a: lo, _b: hi})) |
| else: |
| |
| |
| |
| neg, pos = (-self._eval_interval(x, hi, lo, _first=False), |
| pos.xreplace({_a: lo, _b: 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}) |
| |
| touch = lambda _: _.replace( |
| lambda x: isinstance(x, (Min, Max)), |
| lambda x: x.func(*x.args)) |
| neg = touch(neg) |
| pos = touch(pos) |
| |
| |
| |
| if a == _a: |
| 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 |
|
|
| |
| |
| ok, abei = self._intervals(x) |
| if not ok: |
| from sympy.integrals.integrals import Integral |
| |
| |
| |
| return Integral(self.diff(x), (x, lo, hi)) |
|
|
| 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): |
| |
| for j, (a, b, i) in enumerate(done): |
| if i == -1: |
| done[j] = a, b, k |
| break |
| 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] |
|
|
| |
| sum = S.Zero |
| upto = None |
| for a, b, i in done: |
| if i == -1: |
| if upto is None: |
| return Undefined |
| |
| 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 == '==': |
| |
| |
| |
| |
| |
| rv = S.false |
| elif rv.rel_op == '!=': |
| try: |
| rv = Or(sym < rv.rhs, sym > rv.rhs) |
| except TypeError: |
| |
| rv = S.true |
| elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity): |
| rv = S.true |
| return True, rv |
|
|
| args = list(self.args) |
| |
| keys = self.atoms(Relational) |
| reps = {} |
| for r in keys: |
| ok, s = _solve_relational(r) |
| if ok != True: |
| return False, ok |
| reps[r] = s |
| |
| |
| args = [i.xreplace(reps) for i in self.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): |
| |
| |
| if err_on_Eq: |
| return False, 'encountered Eq condition: %s' % cond |
| continue |
|
|
| 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 |
|
|
| |
| 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 |
| 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) |
| 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)) |
| newcond.append((newcond[-1][1], e)) |
| newcond.append((newcond[-1][1], upper)) |
| newcond.pop(0) |
| 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 |
| if cond.lts == sym: |
| lower = S.NegativeInfinity |
| elif cond.gts == sym: |
| upper = S.Infinity |
| else: |
| return nonsymfail(cond) |
| else: |
| return False, 'unrecognized condition: %s' % cond |
|
|
| upper = 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): |
| |
| |
| |
| |
| |
| 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') |
|
|
| @classmethod |
| 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 |
| |
| 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): |
| |
| 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] |
| 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) |
| |
| |
| |
| assert not c.has(Piecewise) |
| 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: |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| 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]] |
| |
| 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): |
| continue |
| if a[n].cond == True: |
| remains.append(a[n].expr) |
| else: |
| 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 |
| |
| 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: |
| |
| 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 = Min(a, 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 |
|
|
| |
| f1 = expr.args[0].cond.free_symbols |
| args = None |
| if len(f1) == 1 and not expr.atoms(Eq): |
| x = f1.pop() |
| |
| |
| |
| 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 |
| 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) |
|
|
| |
| doit = kwargs.pop('doit', None) |
| for i in range(len(args)): |
| e, c = args[i] |
| if isinstance(e, Basic): |
| |
| |
| newe = simplify(e, doit=False, **kwargs) |
| if newe != e: |
| e = newe |
| args[i] = (e, c) |
|
|
| |
| if doit is not None: |
| kwargs['doit'] = doit |
|
|
| return Piecewise(*args) |
|
|
|
|
| def _piecewise_collapse_arguments(_args): |
| newargs = [] |
| current_cond = set() |
| for expr, cond in _args: |
| cond = cond.replace( |
| lambda _: _.is_Relational, _canonical_coeff) |
| |
| |
| |
| |
| |
| |
| |
| |
| if isinstance(expr, Piecewise): |
| unmatching = [] |
| for i, (e, c) in enumerate(expr.args): |
| if c in current_cond: |
| |
| continue |
| if c == cond: |
| if c != True: |
| |
| |
| |
| |
| if unmatching: |
| expr = Piecewise(*( |
| unmatching + [(e, c)])) |
| else: |
| expr = e |
| break |
| else: |
| unmatching.append((e, c)) |
|
|
| |
| got = False |
| |
| |
| |
| |
| |
| |
| for i in ([cond] + |
| (list(cond.args) if isinstance(cond, And) else |
| [])): |
| if i in current_cond: |
| got = True |
| break |
| if got: |
| continue |
|
|
| |
| |
| |
| |
| |
| |
| if isinstance(cond, And): |
| nonredundant = [] |
| for c in cond.args: |
| if isinstance(c, Relational): |
| if c.negated.canonical in current_cond: |
| continue |
| |
| |
| |
| 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) |
|
|
| |
| 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: |
| |
| |
| |
| if len(args) == 2 or _blessed(e): |
| _prevexpr = _prevexpr.subs(*e.args) |
| _expr = _expr.subs(*e.args) |
| |
| if _prevexpr == _expr: |
| |
| |
| |
| args[i] = args[i].func(args[i + 1][0], cond) |
| else: |
| |
| 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): |
| |
| |
| 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 = [] |
|
|
| |
| 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)) |
|
|
| |
| 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 |
|
|