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 | |