Skip to content

Commit

Permalink
Merge pull request #23892 from oscargus/piecewise_exclusive1.11
Browse files Browse the repository at this point in the history
Piecewise_exclusive for 1.11
  • Loading branch information
oscarbenjamin committed Aug 8, 2022
2 parents 8d092c7 + e10bbad commit ac3b08a
Show file tree
Hide file tree
Showing 5 changed files with 146 additions and 14 deletions.
2 changes: 2 additions & 0 deletions doc/src/modules/functions/elementary.rst
Expand Up @@ -329,6 +329,8 @@ Piecewise

.. automethod:: sympy.functions.elementary.piecewise.Piecewise._eval_integral

.. autofunction:: sympy.functions.elementary.piecewise.piecewise_exclusive

.. autofunction:: sympy.functions.elementary.piecewise.piecewise_fold


Expand Down
20 changes: 10 additions & 10 deletions sympy/__init__.py
Expand Up @@ -118,11 +118,11 @@ def __sympy_debug():
asin, acos, atan, asec, acsc, acot, atan2, exp_polar, exp, ln, log,
LambertW, sinh, cosh, tanh, coth, sech, csch, asinh, acosh, atanh,
acoth, asech, acsch, floor, ceiling, frac, Piecewise, piecewise_fold,
erf, erfc, erfi, erf2, erfinv, erfcinv, erf2inv, Ei, expint, E1, li,
Li, Si, Ci, Shi, Chi, fresnels, fresnelc, gamma, lowergamma,
uppergamma, polygamma, loggamma, digamma, trigamma, multigamma,
dirichlet_eta, zeta, lerchphi, polylog, stieltjes, Eijk, LeviCivita,
KroneckerDelta, SingularityFunction, DiracDelta, Heaviside,
piecewise_exclusive, erf, erfc, erfi, erf2, erfinv, erfcinv, erf2inv,
Ei, expint, E1, li, Li, Si, Ci, Shi, Chi, fresnels, fresnelc, gamma,
lowergamma, uppergamma, polygamma, loggamma, digamma, trigamma,
multigamma, dirichlet_eta, zeta, lerchphi, polylog, stieltjes, Eijk,
LeviCivita, KroneckerDelta, SingularityFunction, DiracDelta, Heaviside,
bspline_basis, bspline_basis_set, interpolating_spline, besselj,
bessely, besseli, besselk, hankel1, hankel2, jn, yn, jn_zeros, hn1,
hn2, airyai, airybi, airyaiprime, airybiprime, marcumq, hyper,
Expand Down Expand Up @@ -338,11 +338,11 @@ def __sympy_debug():
'acot', 'atan2', 'exp_polar', 'exp', 'ln', 'log', 'LambertW', 'sinh',
'cosh', 'tanh', 'coth', 'sech', 'csch', 'asinh', 'acosh', 'atanh',
'acoth', 'asech', 'acsch', 'floor', 'ceiling', 'frac', 'Piecewise',
'piecewise_fold', 'erf', 'erfc', 'erfi', 'erf2', 'erfinv', 'erfcinv',
'erf2inv', 'Ei', 'expint', 'E1', 'li', 'Li', 'Si', 'Ci', 'Shi', 'Chi',
'fresnels', 'fresnelc', 'gamma', 'lowergamma', 'uppergamma', 'polygamma',
'loggamma', 'digamma', 'trigamma', 'multigamma', 'dirichlet_eta', 'zeta',
'lerchphi', 'polylog', 'stieltjes', 'Eijk', 'LeviCivita',
'piecewise_fold', 'piecewise_exclusive', 'erf', 'erfc', 'erfi', 'erf2',
'erfinv', 'erfcinv', 'erf2inv', 'Ei', 'expint', 'E1', 'li', 'Li', 'Si',
'Ci', 'Shi', 'Chi', 'fresnels', 'fresnelc', 'gamma', 'lowergamma',
'uppergamma', 'polygamma', 'loggamma', 'digamma', 'trigamma', 'multigamma',
'dirichlet_eta', 'zeta', 'lerchphi', 'polylog', 'stieltjes', 'Eijk', 'LeviCivita',
'KroneckerDelta', 'SingularityFunction', 'DiracDelta', 'Heaviside',
'bspline_basis', 'bspline_basis_set', 'interpolating_spline', 'besselj',
'bessely', 'besseli', 'besselk', 'hankel1', 'hankel2', 'jn', 'yn',
Expand Down
5 changes: 3 additions & 2 deletions sympy/functions/__init__.py
Expand Up @@ -21,7 +21,8 @@
from sympy.functions.elementary.hyperbolic import (sinh, cosh, tanh, coth,
sech, csch, asinh, acosh, atanh, acoth, asech, acsch)
from sympy.functions.elementary.integers import floor, ceiling, frac
from sympy.functions.elementary.piecewise import Piecewise, piecewise_fold
from sympy.functions.elementary.piecewise import (Piecewise, piecewise_fold,
piecewise_exclusive)
from sympy.functions.special.error_functions import (erf, erfc, erfi, erf2,
erfinv, erfcinv, erf2inv, Ei, expint, E1, li, Li, Si, Ci, Shi, Chi,
fresnels, fresnelc)
Expand Down Expand Up @@ -71,7 +72,7 @@

'floor', 'ceiling', 'frac',

'Piecewise', 'piecewise_fold',
'Piecewise', 'piecewise_fold', 'piecewise_exclusive',

'erf', 'erfc', 'erfi', 'erf2', 'erfinv', 'erfcinv', 'erf2inv', 'Ei',
'expint', 'E1', 'li', 'Li', 'Si', 'Ci', 'Shi', 'Chi', 'fresnels',
Expand Down
101 changes: 100 additions & 1 deletion sympy/functions/elementary/piecewise.py
Expand Up @@ -117,7 +117,10 @@ class Piecewise(Function):
See Also
========
piecewise_fold, ITE
piecewise_fold
piecewise_exclusive
ITE
"""

nargs = None
Expand Down Expand Up @@ -1077,6 +1080,7 @@ def piecewise_fold(expr, evaluate=True):
========
Piecewise
piecewise_exclusive
"""
if not isinstance(expr, Basic) or not expr.has(Piecewise):
return expr
Expand Down Expand Up @@ -1364,3 +1368,98 @@ def piecewise_simplify(expr, **kwargs):
else:
prevexpr = expr
return Piecewise(*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
32 changes: 31 additions & 1 deletion sympy/functions/elementary/tests/test_piecewise.py
Expand Up @@ -14,7 +14,7 @@
from sympy.functions.elementary.exponential import (exp, log)
from sympy.functions.elementary.miscellaneous import (Max, Min, sqrt)
from sympy.functions.elementary.piecewise import (Piecewise,
piecewise_fold, Undefined, ExprCondPair)
piecewise_fold, piecewise_exclusive, Undefined, ExprCondPair)
from sympy.functions.elementary.trigonometric import (cos, sin)
from sympy.functions.special.delta_functions import (DiracDelta, Heaviside)
from sympy.functions.special.tensor_functions import KroneckerDelta
Expand Down Expand Up @@ -698,6 +698,36 @@ def test_piecewise_interval():
(S.Half, True))


def test_piecewise_exclusive():
p = Piecewise((0, x < 0), (S.Half, x <= 0), (1, True))
assert piecewise_exclusive(p) == Piecewise((0, x < 0), (S.Half, Eq(x, 0)),
(1, x > 0), evaluate=False)
assert piecewise_exclusive(p + 2) == Piecewise((0, x < 0), (S.Half, Eq(x, 0)),
(1, x > 0), evaluate=False) + 2
assert piecewise_exclusive(Piecewise((1, y <= 0),
(-Piecewise((2, y >= 0)), True))) == \
Piecewise((1, y <= 0),
(-Piecewise((2, y >= 0),
(S.NaN, y < 0), evaluate=False), y > 0), evaluate=False)
assert piecewise_exclusive(Piecewise((1, x > y))) == Piecewise((1, x > y),
(S.NaN, x <= y),
evaluate=False)
assert piecewise_exclusive(Piecewise((1, x > y)),
skip_nan=True) == Piecewise((1, x > y))

xr, yr = symbols('xr, yr', real=True)

p1 = Piecewise((1, xr < 0), (2, True), evaluate=False)
p1x = Piecewise((1, xr < 0), (2, xr >= 0), evaluate=False)

p2 = Piecewise((p1, yr < 0), (3, True), evaluate=False)
p2x = Piecewise((p1, yr < 0), (3, yr >= 0), evaluate=False)
p2xx = Piecewise((p1x, yr < 0), (3, yr >= 0), evaluate=False)

assert piecewise_exclusive(p2) == p2xx
assert piecewise_exclusive(p2, deep=False) == p2x


def test_piecewise_collapse():
assert Piecewise((x, True)) == x
a = x < 1
Expand Down

0 comments on commit ac3b08a

Please sign in to comment.