Coverage for /usr/lib/python3/dist-packages/sympy/functions/elementary/piecewise.py: 13%
794 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from sympy.core import S, Function, diff, Tuple, Dummy, Mul
2from sympy.core.basic import Basic, as_Basic
3from sympy.core.numbers import Rational, NumberSymbol, _illegal
4from sympy.core.parameters import global_parameters
5from sympy.core.relational import (Lt, Gt, Eq, Ne, Relational,
6 _canonical, _canonical_coeff)
7from sympy.core.sorting import ordered
8from sympy.functions.elementary.miscellaneous import Max, Min
9from sympy.logic.boolalg import (And, Boolean, distribute_and_over_or, Not,
10 true, false, Or, ITE, simplify_logic, to_cnf, distribute_or_over_and)
11from sympy.utilities.iterables import uniq, sift, common_prefix
12from sympy.utilities.misc import filldedent, func_name
14from itertools import product
16Undefined = S.NaN # Piecewise()
18class ExprCondPair(Tuple):
19 """Represents an expression, condition pair."""
21 def __new__(cls, expr, cond):
22 expr = as_Basic(expr)
23 if cond == True:
24 return Tuple.__new__(cls, expr, true)
25 elif cond == False:
26 return Tuple.__new__(cls, expr, false)
27 elif isinstance(cond, Basic) and cond.has(Piecewise):
28 cond = piecewise_fold(cond)
29 if isinstance(cond, Piecewise):
30 cond = cond.rewrite(ITE)
32 if not isinstance(cond, Boolean):
33 raise TypeError(filldedent('''
34 Second argument must be a Boolean,
35 not `%s`''' % func_name(cond)))
36 return Tuple.__new__(cls, expr, cond)
38 @property
39 def expr(self):
40 """
41 Returns the expression of this pair.
42 """
43 return self.args[0]
45 @property
46 def cond(self):
47 """
48 Returns the condition of this pair.
49 """
50 return self.args[1]
52 @property
53 def is_commutative(self):
54 return self.expr.is_commutative
56 def __iter__(self):
57 yield self.expr
58 yield self.cond
60 def _eval_simplify(self, **kwargs):
61 return self.func(*[a.simplify(**kwargs) for a in self.args])
64class Piecewise(Function):
65 """
66 Represents a piecewise function.
68 Usage:
70 Piecewise( (expr,cond), (expr,cond), ... )
71 - Each argument is a 2-tuple defining an expression and condition
72 - The conds are evaluated in turn returning the first that is True.
73 If any of the evaluated conds are not explicitly False,
74 e.g. ``x < 1``, the function is returned in symbolic form.
75 - If the function is evaluated at a place where all conditions are False,
76 nan will be returned.
77 - Pairs where the cond is explicitly False, will be removed and no pair
78 appearing after a True condition will ever be retained. If a single
79 pair with a True condition remains, it will be returned, even when
80 evaluation is False.
82 Examples
83 ========
85 >>> from sympy import Piecewise, log, piecewise_fold
86 >>> from sympy.abc import x, y
87 >>> f = x**2
88 >>> g = log(x)
89 >>> p = Piecewise((0, x < -1), (f, x <= 1), (g, True))
90 >>> p.subs(x,1)
91 1
92 >>> p.subs(x,5)
93 log(5)
95 Booleans can contain Piecewise elements:
97 >>> cond = (x < y).subs(x, Piecewise((2, x < 0), (3, True))); cond
98 Piecewise((2, x < 0), (3, True)) < y
100 The folded version of this results in a Piecewise whose
101 expressions are Booleans:
103 >>> folded_cond = piecewise_fold(cond); folded_cond
104 Piecewise((2 < y, x < 0), (3 < y, True))
106 When a Boolean containing Piecewise (like cond) or a Piecewise
107 with Boolean expressions (like folded_cond) is used as a condition,
108 it is converted to an equivalent :class:`~.ITE` object:
110 >>> Piecewise((1, folded_cond))
111 Piecewise((1, ITE(x < 0, y > 2, y > 3)))
113 When a condition is an ``ITE``, it will be converted to a simplified
114 Boolean expression:
116 >>> piecewise_fold(_)
117 Piecewise((1, ((x >= 0) | (y > 2)) & ((y > 3) | (x < 0))))
119 See Also
120 ========
122 piecewise_fold
123 piecewise_exclusive
124 ITE
125 """
127 nargs = None
128 is_Piecewise = True
130 def __new__(cls, *args, **options):
131 if len(args) == 0:
132 raise TypeError("At least one (expr, cond) pair expected.")
133 # (Try to) sympify args first
134 newargs = []
135 for ec in args:
136 # ec could be a ExprCondPair or a tuple
137 pair = ExprCondPair(*getattr(ec, 'args', ec))
138 cond = pair.cond
139 if cond is false:
140 continue
141 newargs.append(pair)
142 if cond is true:
143 break
145 eval = options.pop('evaluate', global_parameters.evaluate)
146 if eval:
147 r = cls.eval(*newargs)
148 if r is not None:
149 return r
150 elif len(newargs) == 1 and newargs[0].cond == True:
151 return newargs[0].expr
153 return Basic.__new__(cls, *newargs, **options)
155 @classmethod
156 def eval(cls, *_args):
157 """Either return a modified version of the args or, if no
158 modifications were made, return None.
160 Modifications that are made here:
162 1. relationals are made canonical
163 2. any False conditions are dropped
164 3. any repeat of a previous condition is ignored
165 4. any args past one with a true condition are dropped
167 If there are no args left, nan will be returned.
168 If there is a single arg with a True condition, its
169 corresponding expression will be returned.
171 EXAMPLES
172 ========
174 >>> from sympy import Piecewise
175 >>> from sympy.abc import x
176 >>> cond = -x < -1
177 >>> args = [(1, cond), (4, cond), (3, False), (2, True), (5, x < 1)]
178 >>> Piecewise(*args, evaluate=False)
179 Piecewise((1, -x < -1), (4, -x < -1), (2, True))
180 >>> Piecewise(*args)
181 Piecewise((1, x > 1), (2, True))
182 """
183 if not _args:
184 return Undefined
186 if len(_args) == 1 and _args[0][-1] == True:
187 return _args[0][0]
189 newargs = _piecewise_collapse_arguments(_args)
191 # some conditions may have been redundant
192 missing = len(newargs) != len(_args)
193 # some conditions may have changed
194 same = all(a == b for a, b in zip(newargs, _args))
195 # if either change happened we return the expr with the
196 # updated args
197 if not newargs:
198 raise ValueError(filldedent('''
199 There are no conditions (or none that
200 are not trivially false) to define an
201 expression.'''))
202 if missing or not same:
203 return cls(*newargs)
205 def doit(self, **hints):
206 """
207 Evaluate this piecewise function.
208 """
209 newargs = []
210 for e, c in self.args:
211 if hints.get('deep', True):
212 if isinstance(e, Basic):
213 newe = e.doit(**hints)
214 if newe != self:
215 e = newe
216 if isinstance(c, Basic):
217 c = c.doit(**hints)
218 newargs.append((e, c))
219 return self.func(*newargs)
221 def _eval_simplify(self, **kwargs):
222 return piecewise_simplify(self, **kwargs)
224 def _eval_as_leading_term(self, x, logx=None, cdir=0):
225 for e, c in self.args:
226 if c == True or c.subs(x, 0) == True:
227 return e.as_leading_term(x)
229 def _eval_adjoint(self):
230 return self.func(*[(e.adjoint(), c) for e, c in self.args])
232 def _eval_conjugate(self):
233 return self.func(*[(e.conjugate(), c) for e, c in self.args])
235 def _eval_derivative(self, x):
236 return self.func(*[(diff(e, x), c) for e, c in self.args])
238 def _eval_evalf(self, prec):
239 return self.func(*[(e._evalf(prec), c) for e, c in self.args])
241 def _eval_is_meromorphic(self, x, a):
242 # Conditions often implicitly assume that the argument is real.
243 # Hence, there needs to be some check for as_set.
244 if not a.is_real:
245 return None
247 # Then, scan ExprCondPairs in the given order to find a piece that would contain a,
248 # possibly as a boundary point.
249 for e, c in self.args:
250 cond = c.subs(x, a)
252 if cond.is_Relational:
253 return None
254 if a in c.as_set().boundary:
255 return None
256 # Apply expression if a is an interior point of the domain of e.
257 if cond:
258 return e._eval_is_meromorphic(x, a)
260 def piecewise_integrate(self, x, **kwargs):
261 """Return the Piecewise with each expression being
262 replaced with its antiderivative. To obtain a continuous
263 antiderivative, use the :func:`~.integrate` function or method.
265 Examples
266 ========
268 >>> from sympy import Piecewise
269 >>> from sympy.abc import x
270 >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True))
271 >>> p.piecewise_integrate(x)
272 Piecewise((0, x < 0), (x, x < 1), (2*x, True))
274 Note that this does not give a continuous function, e.g.
275 at x = 1 the 3rd condition applies and the antiderivative
276 there is 2*x so the value of the antiderivative is 2:
278 >>> anti = _
279 >>> anti.subs(x, 1)
280 2
282 The continuous derivative accounts for the integral *up to*
283 the point of interest, however:
285 >>> p.integrate(x)
286 Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True))
287 >>> _.subs(x, 1)
288 1
290 See Also
291 ========
292 Piecewise._eval_integral
293 """
294 from sympy.integrals import integrate
295 return self.func(*[(integrate(e, x, **kwargs), c) for e, c in self.args])
297 def _handle_irel(self, x, handler):
298 """Return either None (if the conditions of self depend only on x) else
299 a Piecewise expression whose expressions (handled by the handler that
300 was passed) are paired with the governing x-independent relationals,
301 e.g. Piecewise((A, a(x) & b(y)), (B, c(x) | c(y)) ->
302 Piecewise(
303 (handler(Piecewise((A, a(x) & True), (B, c(x) | True)), b(y) & c(y)),
304 (handler(Piecewise((A, a(x) & True), (B, c(x) | False)), b(y)),
305 (handler(Piecewise((A, a(x) & False), (B, c(x) | True)), c(y)),
306 (handler(Piecewise((A, a(x) & False), (B, c(x) | False)), True))
307 """
308 # identify governing relationals
309 rel = self.atoms(Relational)
310 irel = list(ordered([r for r in rel if x not in r.free_symbols
311 and r not in (S.true, S.false)]))
312 if irel:
313 args = {}
314 exprinorder = []
315 for truth in product((1, 0), repeat=len(irel)):
316 reps = dict(zip(irel, truth))
317 # only store the true conditions since the false are implied
318 # when they appear lower in the Piecewise args
319 if 1 not in truth:
320 cond = None # flag this one so it doesn't get combined
321 else:
322 andargs = Tuple(*[i for i in reps if reps[i]])
323 free = list(andargs.free_symbols)
324 if len(free) == 1:
325 from sympy.solvers.inequalities import (
326 reduce_inequalities, _solve_inequality)
327 try:
328 t = reduce_inequalities(andargs, free[0])
329 # ValueError when there are potentially
330 # nonvanishing imaginary parts
331 except (ValueError, NotImplementedError):
332 # at least isolate free symbol on left
333 t = And(*[_solve_inequality(
334 a, free[0], linear=True)
335 for a in andargs])
336 else:
337 t = And(*andargs)
338 if t is S.false:
339 continue # an impossible combination
340 cond = t
341 expr = handler(self.xreplace(reps))
342 if isinstance(expr, self.func) and len(expr.args) == 1:
343 expr, econd = expr.args[0]
344 cond = And(econd, True if cond is None else cond)
345 # the ec pairs are being collected since all possibilities
346 # are being enumerated, but don't put the last one in since
347 # its expr might match a previous expression and it
348 # must appear last in the args
349 if cond is not None:
350 args.setdefault(expr, []).append(cond)
351 # but since we only store the true conditions we must maintain
352 # the order so that the expression with the most true values
353 # comes first
354 exprinorder.append(expr)
355 # convert collected conditions as args of Or
356 for k in args:
357 args[k] = Or(*args[k])
358 # take them in the order obtained
359 args = [(e, args[e]) for e in uniq(exprinorder)]
360 # add in the last arg
361 args.append((expr, True))
362 return Piecewise(*args)
364 def _eval_integral(self, x, _first=True, **kwargs):
365 """Return the indefinite integral of the
366 Piecewise such that subsequent substitution of x with a
367 value will give the value of the integral (not including
368 the constant of integration) up to that point. To only
369 integrate the individual parts of Piecewise, use the
370 ``piecewise_integrate`` method.
372 Examples
373 ========
375 >>> from sympy import Piecewise
376 >>> from sympy.abc import x
377 >>> p = Piecewise((0, x < 0), (1, x < 1), (2, True))
378 >>> p.integrate(x)
379 Piecewise((0, x < 0), (x, x < 1), (2*x - 1, True))
380 >>> p.piecewise_integrate(x)
381 Piecewise((0, x < 0), (x, x < 1), (2*x, True))
383 See Also
384 ========
385 Piecewise.piecewise_integrate
386 """
387 from sympy.integrals.integrals import integrate
389 if _first:
390 def handler(ipw):
391 if isinstance(ipw, self.func):
392 return ipw._eval_integral(x, _first=False, **kwargs)
393 else:
394 return ipw.integrate(x, **kwargs)
395 irv = self._handle_irel(x, handler)
396 if irv is not None:
397 return irv
399 # handle a Piecewise from -oo to oo with and no x-independent relationals
400 # -----------------------------------------------------------------------
401 ok, abei = self._intervals(x)
402 if not ok:
403 from sympy.integrals.integrals import Integral
404 return Integral(self, x) # unevaluated
406 pieces = [(a, b) for a, b, _, _ in abei]
407 oo = S.Infinity
408 done = [(-oo, oo, -1)]
409 for k, p in enumerate(pieces):
410 if p == (-oo, oo):
411 # all undone intervals will get this key
412 for j, (a, b, i) in enumerate(done):
413 if i == -1:
414 done[j] = a, b, k
415 break # nothing else to consider
416 N = len(done) - 1
417 for j, (a, b, i) in enumerate(reversed(done)):
418 if i == -1:
419 j = N - j
420 done[j: j + 1] = _clip(p, (a, b), k)
421 done = [(a, b, i) for a, b, i in done if a != b]
423 # append an arg if there is a hole so a reference to
424 # argument -1 will give Undefined
425 if any(i == -1 for (a, b, i) in done):
426 abei.append((-oo, oo, Undefined, -1))
428 # return the sum of the intervals
429 args = []
430 sum = None
431 for a, b, i in done:
432 anti = integrate(abei[i][-2], x, **kwargs)
433 if sum is None:
434 sum = anti
435 else:
436 sum = sum.subs(x, a)
437 e = anti._eval_interval(x, a, x)
438 if sum.has(*_illegal) or e.has(*_illegal):
439 sum = anti
440 else:
441 sum += e
442 # see if we know whether b is contained in original
443 # condition
444 if b is S.Infinity:
445 cond = True
446 elif self.args[abei[i][-1]].cond.subs(x, b) == False:
447 cond = (x < b)
448 else:
449 cond = (x <= b)
450 args.append((sum, cond))
451 return Piecewise(*args)
453 def _eval_interval(self, sym, a, b, _first=True):
454 """Evaluates the function along the sym in a given interval [a, b]"""
455 # FIXME: Currently complex intervals are not supported. A possible
456 # replacement algorithm, discussed in issue 5227, can be found in the
457 # following papers;
458 # http://portal.acm.org/citation.cfm?id=281649
459 # http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.70.4127&rep=rep1&type=pdf
461 if a is None or b is None:
462 # In this case, it is just simple substitution
463 return super()._eval_interval(sym, a, b)
464 else:
465 x, lo, hi = map(as_Basic, (sym, a, b))
467 if _first: # get only x-dependent relationals
468 def handler(ipw):
469 if isinstance(ipw, self.func):
470 return ipw._eval_interval(x, lo, hi, _first=None)
471 else:
472 return ipw._eval_interval(x, lo, hi)
473 irv = self._handle_irel(x, handler)
474 if irv is not None:
475 return irv
477 if (lo < hi) is S.false or (
478 lo is S.Infinity or hi is S.NegativeInfinity):
479 rv = self._eval_interval(x, hi, lo, _first=False)
480 if isinstance(rv, Piecewise):
481 rv = Piecewise(*[(-e, c) for e, c in rv.args])
482 else:
483 rv = -rv
484 return rv
486 if (lo < hi) is S.true or (
487 hi is S.Infinity or lo is S.NegativeInfinity):
488 pass
489 else:
490 _a = Dummy('lo')
491 _b = Dummy('hi')
492 a = lo if lo.is_comparable else _a
493 b = hi if hi.is_comparable else _b
494 pos = self._eval_interval(x, a, b, _first=False)
495 if a == _a and b == _b:
496 # it's purely symbolic so just swap lo and hi and
497 # change the sign to get the value for when lo > hi
498 neg, pos = (-pos.xreplace({_a: hi, _b: lo}),
499 pos.xreplace({_a: lo, _b: hi}))
500 else:
501 # at least one of the bounds was comparable, so allow
502 # _eval_interval to use that information when computing
503 # the interval with lo and hi reversed
504 neg, pos = (-self._eval_interval(x, hi, lo, _first=False),
505 pos.xreplace({_a: lo, _b: hi}))
507 # allow simplification based on ordering of lo and hi
508 p = Dummy('', positive=True)
509 if lo.is_Symbol:
510 pos = pos.xreplace({lo: hi - p}).xreplace({p: hi - lo})
511 neg = neg.xreplace({lo: hi + p}).xreplace({p: lo - hi})
512 elif hi.is_Symbol:
513 pos = pos.xreplace({hi: lo + p}).xreplace({p: hi - lo})
514 neg = neg.xreplace({hi: lo - p}).xreplace({p: lo - hi})
515 # evaluate limits that may have unevaluate Min/Max
516 touch = lambda _: _.replace(
517 lambda x: isinstance(x, (Min, Max)),
518 lambda x: x.func(*x.args))
519 neg = touch(neg)
520 pos = touch(pos)
521 # assemble return expression; make the first condition be Lt
522 # b/c then the first expression will look the same whether
523 # the lo or hi limit is symbolic
524 if a == _a: # the lower limit was symbolic
525 rv = Piecewise(
526 (pos,
527 lo < hi),
528 (neg,
529 True))
530 else:
531 rv = Piecewise(
532 (neg,
533 hi < lo),
534 (pos,
535 True))
537 if rv == Undefined:
538 raise ValueError("Can't integrate across undefined region.")
539 if any(isinstance(i, Piecewise) for i in (pos, neg)):
540 rv = piecewise_fold(rv)
541 return rv
543 # handle a Piecewise with lo <= hi and no x-independent relationals
544 # -----------------------------------------------------------------
545 ok, abei = self._intervals(x)
546 if not ok:
547 from sympy.integrals.integrals import Integral
548 # not being able to do the interval of f(x) can
549 # be stated as not being able to do the integral
550 # of f'(x) over the same range
551 return Integral(self.diff(x), (x, lo, hi)) # unevaluated
553 pieces = [(a, b) for a, b, _, _ in abei]
554 done = [(lo, hi, -1)]
555 oo = S.Infinity
556 for k, p in enumerate(pieces):
557 if p[:2] == (-oo, oo):
558 # all undone intervals will get this key
559 for j, (a, b, i) in enumerate(done):
560 if i == -1:
561 done[j] = a, b, k
562 break # nothing else to consider
563 N = len(done) - 1
564 for j, (a, b, i) in enumerate(reversed(done)):
565 if i == -1:
566 j = N - j
567 done[j: j + 1] = _clip(p, (a, b), k)
568 done = [(a, b, i) for a, b, i in done if a != b]
570 # return the sum of the intervals
571 sum = S.Zero
572 upto = None
573 for a, b, i in done:
574 if i == -1:
575 if upto is None:
576 return Undefined
577 # TODO simplify hi <= upto
578 return Piecewise((sum, hi <= upto), (Undefined, True))
579 sum += abei[i][-2]._eval_interval(x, a, b)
580 upto = b
581 return sum
583 def _intervals(self, sym, err_on_Eq=False):
584 r"""Return a bool and a message (when bool is False), else a
585 list of unique tuples, (a, b, e, i), where a and b
586 are the lower and upper bounds in which the expression e of
587 argument i in self is defined and $a < b$ (when involving
588 numbers) or $a \le b$ when involving symbols.
590 If there are any relationals not involving sym, or any
591 relational cannot be solved for sym, the bool will be False
592 a message be given as the second return value. The calling
593 routine should have removed such relationals before calling
594 this routine.
596 The evaluated conditions will be returned as ranges.
597 Discontinuous ranges will be returned separately with
598 identical expressions. The first condition that evaluates to
599 True will be returned as the last tuple with a, b = -oo, oo.
600 """
601 from sympy.solvers.inequalities import _solve_inequality
603 assert isinstance(self, Piecewise)
605 def nonsymfail(cond):
606 return False, filldedent('''
607 A condition not involving
608 %s appeared: %s''' % (sym, cond))
610 def _solve_relational(r):
611 if sym not in r.free_symbols:
612 return nonsymfail(r)
613 try:
614 rv = _solve_inequality(r, sym)
615 except NotImplementedError:
616 return False, 'Unable to solve relational %s for %s.' % (r, sym)
617 if isinstance(rv, Relational):
618 free = rv.args[1].free_symbols
619 if rv.args[0] != sym or sym in free:
620 return False, 'Unable to solve relational %s for %s.' % (r, sym)
621 if rv.rel_op == '==':
622 # this equality has been affirmed to have the form
623 # Eq(sym, rhs) where rhs is sym-free; it represents
624 # a zero-width interval which will be ignored
625 # whether it is an isolated condition or contained
626 # within an And or an Or
627 rv = S.false
628 elif rv.rel_op == '!=':
629 try:
630 rv = Or(sym < rv.rhs, sym > rv.rhs)
631 except TypeError:
632 # e.g. x != I ==> all real x satisfy
633 rv = S.true
634 elif rv == (S.NegativeInfinity < sym) & (sym < S.Infinity):
635 rv = S.true
636 return True, rv
638 args = list(self.args)
639 # make self canonical wrt Relationals
640 keys = self.atoms(Relational)
641 reps = {}
642 for r in keys:
643 ok, s = _solve_relational(r)
644 if ok != True:
645 return False, ok
646 reps[r] = s
647 # process args individually so if any evaluate, their position
648 # in the original Piecewise will be known
649 args = [i.xreplace(reps) for i in self.args]
651 # precondition args
652 expr_cond = []
653 default = idefault = None
654 for i, (expr, cond) in enumerate(args):
655 if cond is S.false:
656 continue
657 if cond is S.true:
658 default = expr
659 idefault = i
660 break
661 if isinstance(cond, Eq):
662 # unanticipated condition, but it is here in case a
663 # replacement caused an Eq to appear
664 if err_on_Eq:
665 return False, 'encountered Eq condition: %s' % cond
666 continue # zero width interval
668 cond = to_cnf(cond)
669 if isinstance(cond, And):
670 cond = distribute_or_over_and(cond)
672 if isinstance(cond, Or):
673 expr_cond.extend(
674 [(i, expr, o) for o in cond.args
675 if not isinstance(o, Eq)])
676 elif cond is not S.false:
677 expr_cond.append((i, expr, cond))
678 elif cond is S.true:
679 default = expr
680 idefault = i
681 break
683 # determine intervals represented by conditions
684 int_expr = []
685 for iarg, expr, cond in expr_cond:
686 if isinstance(cond, And):
687 lower = S.NegativeInfinity
688 upper = S.Infinity
689 exclude = []
690 for cond2 in cond.args:
691 if not isinstance(cond2, Relational):
692 return False, 'expecting only Relationals'
693 if isinstance(cond2, Eq):
694 lower = upper # ignore
695 if err_on_Eq:
696 return False, 'encountered secondary Eq condition'
697 break
698 elif isinstance(cond2, Ne):
699 l, r = cond2.args
700 if l == sym:
701 exclude.append(r)
702 elif r == sym:
703 exclude.append(l)
704 else:
705 return nonsymfail(cond2)
706 continue
707 elif cond2.lts == sym:
708 upper = Min(cond2.gts, upper)
709 elif cond2.gts == sym:
710 lower = Max(cond2.lts, lower)
711 else:
712 return nonsymfail(cond2) # should never get here
713 if exclude:
714 exclude = list(ordered(exclude))
715 newcond = []
716 for i, e in enumerate(exclude):
717 if e < lower == True or e > upper == True:
718 continue
719 if not newcond:
720 newcond.append((None, lower)) # add a primer
721 newcond.append((newcond[-1][1], e))
722 newcond.append((newcond[-1][1], upper))
723 newcond.pop(0) # remove the primer
724 expr_cond.extend([(iarg, expr, And(i[0] < sym, sym < i[1])) for i in newcond])
725 continue
726 elif isinstance(cond, Relational) and cond.rel_op != '!=':
727 lower, upper = cond.lts, cond.gts # part 1: initialize with givens
728 if cond.lts == sym: # part 1a: expand the side ...
729 lower = S.NegativeInfinity # e.g. x <= 0 ---> -oo <= 0
730 elif cond.gts == sym: # part 1a: ... that can be expanded
731 upper = S.Infinity # e.g. x >= 0 ---> oo >= 0
732 else:
733 return nonsymfail(cond)
734 else:
735 return False, 'unrecognized condition: %s' % cond
737 lower, upper = lower, Max(lower, upper)
738 if err_on_Eq and lower == upper:
739 return False, 'encountered Eq condition'
740 if (lower >= upper) is not S.true:
741 int_expr.append((lower, upper, expr, iarg))
743 if default is not None:
744 int_expr.append(
745 (S.NegativeInfinity, S.Infinity, default, idefault))
747 return True, list(uniq(int_expr))
749 def _eval_nseries(self, x, n, logx, cdir=0):
750 args = [(ec.expr._eval_nseries(x, n, logx), ec.cond) for ec in self.args]
751 return self.func(*args)
753 def _eval_power(self, s):
754 return self.func(*[(e**s, c) for e, c in self.args])
756 def _eval_subs(self, old, new):
757 # this is strictly not necessary, but we can keep track
758 # of whether True or False conditions arise and be
759 # somewhat more efficient by avoiding other substitutions
760 # and avoiding invalid conditions that appear after a
761 # True condition
762 args = list(self.args)
763 args_exist = False
764 for i, (e, c) in enumerate(args):
765 c = c._subs(old, new)
766 if c != False:
767 args_exist = True
768 e = e._subs(old, new)
769 args[i] = (e, c)
770 if c == True:
771 break
772 if not args_exist:
773 args = ((Undefined, True),)
774 return self.func(*args)
776 def _eval_transpose(self):
777 return self.func(*[(e.transpose(), c) for e, c in self.args])
779 def _eval_template_is_attr(self, is_attr):
780 b = None
781 for expr, _ in self.args:
782 a = getattr(expr, is_attr)
783 if a is None:
784 return
785 if b is None:
786 b = a
787 elif b is not a:
788 return
789 return b
791 _eval_is_finite = lambda self: self._eval_template_is_attr(
792 'is_finite')
793 _eval_is_complex = lambda self: self._eval_template_is_attr('is_complex')
794 _eval_is_even = lambda self: self._eval_template_is_attr('is_even')
795 _eval_is_imaginary = lambda self: self._eval_template_is_attr(
796 'is_imaginary')
797 _eval_is_integer = lambda self: self._eval_template_is_attr('is_integer')
798 _eval_is_irrational = lambda self: self._eval_template_is_attr(
799 'is_irrational')
800 _eval_is_negative = lambda self: self._eval_template_is_attr('is_negative')
801 _eval_is_nonnegative = lambda self: self._eval_template_is_attr(
802 'is_nonnegative')
803 _eval_is_nonpositive = lambda self: self._eval_template_is_attr(
804 'is_nonpositive')
805 _eval_is_nonzero = lambda self: self._eval_template_is_attr(
806 'is_nonzero')
807 _eval_is_odd = lambda self: self._eval_template_is_attr('is_odd')
808 _eval_is_polar = lambda self: self._eval_template_is_attr('is_polar')
809 _eval_is_positive = lambda self: self._eval_template_is_attr('is_positive')
810 _eval_is_extended_real = lambda self: self._eval_template_is_attr(
811 'is_extended_real')
812 _eval_is_extended_positive = lambda self: self._eval_template_is_attr(
813 'is_extended_positive')
814 _eval_is_extended_negative = lambda self: self._eval_template_is_attr(
815 'is_extended_negative')
816 _eval_is_extended_nonzero = lambda self: self._eval_template_is_attr(
817 'is_extended_nonzero')
818 _eval_is_extended_nonpositive = lambda self: self._eval_template_is_attr(
819 'is_extended_nonpositive')
820 _eval_is_extended_nonnegative = lambda self: self._eval_template_is_attr(
821 'is_extended_nonnegative')
822 _eval_is_real = lambda self: self._eval_template_is_attr('is_real')
823 _eval_is_zero = lambda self: self._eval_template_is_attr(
824 'is_zero')
826 @classmethod
827 def __eval_cond(cls, cond):
828 """Return the truth value of the condition."""
829 if cond == True:
830 return True
831 if isinstance(cond, Eq):
832 try:
833 diff = cond.lhs - cond.rhs
834 if diff.is_commutative:
835 return diff.is_zero
836 except TypeError:
837 pass
839 def as_expr_set_pairs(self, domain=None):
840 """Return tuples for each argument of self that give
841 the expression and the interval in which it is valid
842 which is contained within the given domain.
843 If a condition cannot be converted to a set, an error
844 will be raised. The variable of the conditions is
845 assumed to be real; sets of real values are returned.
847 Examples
848 ========
850 >>> from sympy import Piecewise, Interval
851 >>> from sympy.abc import x
852 >>> p = Piecewise(
853 ... (1, x < 2),
854 ... (2,(x > 0) & (x < 4)),
855 ... (3, True))
856 >>> p.as_expr_set_pairs()
857 [(1, Interval.open(-oo, 2)),
858 (2, Interval.Ropen(2, 4)),
859 (3, Interval(4, oo))]
860 >>> p.as_expr_set_pairs(Interval(0, 3))
861 [(1, Interval.Ropen(0, 2)),
862 (2, Interval(2, 3))]
863 """
864 if domain is None:
865 domain = S.Reals
866 exp_sets = []
867 U = domain
868 complex = not domain.is_subset(S.Reals)
869 cond_free = set()
870 for expr, cond in self.args:
871 cond_free |= cond.free_symbols
872 if len(cond_free) > 1:
873 raise NotImplementedError(filldedent('''
874 multivariate conditions are not handled.'''))
875 if complex:
876 for i in cond.atoms(Relational):
877 if not isinstance(i, (Eq, Ne)):
878 raise ValueError(filldedent('''
879 Inequalities in the complex domain are
880 not supported. Try the real domain by
881 setting domain=S.Reals'''))
882 cond_int = U.intersect(cond.as_set())
883 U = U - cond_int
884 if cond_int != S.EmptySet:
885 exp_sets.append((expr, cond_int))
886 return exp_sets
888 def _eval_rewrite_as_ITE(self, *args, **kwargs):
889 byfree = {}
890 args = list(args)
891 default = any(c == True for b, c in args)
892 for i, (b, c) in enumerate(args):
893 if not isinstance(b, Boolean) and b != True:
894 raise TypeError(filldedent('''
895 Expecting Boolean or bool but got `%s`
896 ''' % func_name(b)))
897 if c == True:
898 break
899 # loop over independent conditions for this b
900 for c in c.args if isinstance(c, Or) else [c]:
901 free = c.free_symbols
902 x = free.pop()
903 try:
904 byfree[x] = byfree.setdefault(
905 x, S.EmptySet).union(c.as_set())
906 except NotImplementedError:
907 if not default:
908 raise NotImplementedError(filldedent('''
909 A method to determine whether a multivariate
910 conditional is consistent with a complete coverage
911 of all variables has not been implemented so the
912 rewrite is being stopped after encountering `%s`.
913 This error would not occur if a default expression
914 like `(foo, True)` were given.
915 ''' % c))
916 if byfree[x] in (S.UniversalSet, S.Reals):
917 # collapse the ith condition to True and break
918 args[i] = list(args[i])
919 c = args[i][1] = True
920 break
921 if c == True:
922 break
923 if c != True:
924 raise ValueError(filldedent('''
925 Conditions must cover all reals or a final default
926 condition `(foo, True)` must be given.
927 '''))
928 last, _ = args[i] # ignore all past ith arg
929 for a, c in reversed(args[:i]):
930 last = ITE(c, a, last)
931 return _canonical(last)
933 def _eval_rewrite_as_KroneckerDelta(self, *args):
934 from sympy.functions.special.tensor_functions import KroneckerDelta
936 rules = {
937 And: [False, False],
938 Or: [True, True],
939 Not: [True, False],
940 Eq: [None, None],
941 Ne: [None, None]
942 }
944 class UnrecognizedCondition(Exception):
945 pass
947 def rewrite(cond):
948 if isinstance(cond, Eq):
949 return KroneckerDelta(*cond.args)
950 if isinstance(cond, Ne):
951 return 1 - KroneckerDelta(*cond.args)
953 cls, args = type(cond), cond.args
954 if cls not in rules:
955 raise UnrecognizedCondition(cls)
957 b1, b2 = rules[cls]
958 k = Mul(*[1 - rewrite(c) for c in args]) if b1 else Mul(*[rewrite(c) for c in args])
960 if b2:
961 return 1 - k
962 return k
964 conditions = []
965 true_value = None
966 for value, cond in args:
967 if type(cond) in rules:
968 conditions.append((value, cond))
969 elif cond is S.true:
970 if true_value is None:
971 true_value = value
972 else:
973 return
975 if true_value is not None:
976 result = true_value
978 for value, cond in conditions[::-1]:
979 try:
980 k = rewrite(cond)
981 result = k * value + (1 - k) * result
982 except UnrecognizedCondition:
983 return
985 return result
988def piecewise_fold(expr, evaluate=True):
989 """
990 Takes an expression containing a piecewise function and returns the
991 expression in piecewise form. In addition, any ITE conditions are
992 rewritten in negation normal form and simplified.
994 The final Piecewise is evaluated (default) but if the raw form
995 is desired, send ``evaluate=False``; if trivial evaluation is
996 desired, send ``evaluate=None`` and duplicate conditions and
997 processing of True and False will be handled.
999 Examples
1000 ========
1002 >>> from sympy import Piecewise, piecewise_fold, S
1003 >>> from sympy.abc import x
1004 >>> p = Piecewise((x, x < 1), (1, S(1) <= x))
1005 >>> piecewise_fold(x*p)
1006 Piecewise((x**2, x < 1), (x, True))
1008 See Also
1009 ========
1011 Piecewise
1012 piecewise_exclusive
1013 """
1014 if not isinstance(expr, Basic) or not expr.has(Piecewise):
1015 return expr
1017 new_args = []
1018 if isinstance(expr, (ExprCondPair, Piecewise)):
1019 for e, c in expr.args:
1020 if not isinstance(e, Piecewise):
1021 e = piecewise_fold(e)
1022 # we don't keep Piecewise in condition because
1023 # it has to be checked to see that it's complete
1024 # and we convert it to ITE at that time
1025 assert not c.has(Piecewise) # pragma: no cover
1026 if isinstance(c, ITE):
1027 c = c.to_nnf()
1028 c = simplify_logic(c, form='cnf')
1029 if isinstance(e, Piecewise):
1030 new_args.extend([(piecewise_fold(ei), And(ci, c))
1031 for ei, ci in e.args])
1032 else:
1033 new_args.append((e, c))
1034 else:
1035 # Given
1036 # P1 = Piecewise((e11, c1), (e12, c2), A)
1037 # P2 = Piecewise((e21, c1), (e22, c2), B)
1038 # ...
1039 # the folding of f(P1, P2) is trivially
1040 # Piecewise(
1041 # (f(e11, e21), c1),
1042 # (f(e12, e22), c2),
1043 # (f(Piecewise(A), Piecewise(B)), True))
1044 # Certain objects end up rewriting themselves as thus, so
1045 # we do that grouping before the more generic folding.
1046 # The following applies this idea when f = Add or f = Mul
1047 # (and the expression is commutative).
1048 if expr.is_Add or expr.is_Mul and expr.is_commutative:
1049 p, args = sift(expr.args, lambda x: x.is_Piecewise, binary=True)
1050 pc = sift(p, lambda x: tuple([c for e,c in x.args]))
1051 for c in list(ordered(pc)):
1052 if len(pc[c]) > 1:
1053 pargs = [list(i.args) for i in pc[c]]
1054 # the first one is the same; there may be more
1055 com = common_prefix(*[
1056 [i.cond for i in j] for j in pargs])
1057 n = len(com)
1058 collected = []
1059 for i in range(n):
1060 collected.append((
1061 expr.func(*[ai[i].expr for ai in pargs]),
1062 com[i]))
1063 remains = []
1064 for a in pargs:
1065 if n == len(a): # no more args
1066 continue
1067 if a[n].cond == True: # no longer Piecewise
1068 remains.append(a[n].expr)
1069 else: # restore the remaining Piecewise
1070 remains.append(
1071 Piecewise(*a[n:], evaluate=False))
1072 if remains:
1073 collected.append((expr.func(*remains), True))
1074 args.append(Piecewise(*collected, evaluate=False))
1075 continue
1076 args.extend(pc[c])
1077 else:
1078 args = expr.args
1079 # fold
1080 folded = list(map(piecewise_fold, args))
1081 for ec in product(*[
1082 (i.args if isinstance(i, Piecewise) else
1083 [(i, true)]) for i in folded]):
1084 e, c = zip(*ec)
1085 new_args.append((expr.func(*e), And(*c)))
1087 if evaluate is None:
1088 # don't return duplicate conditions, otherwise don't evaluate
1089 new_args = list(reversed([(e, c) for c, e in {
1090 c: e for e, c in reversed(new_args)}.items()]))
1091 rv = Piecewise(*new_args, evaluate=evaluate)
1092 if evaluate is None and len(rv.args) == 1 and rv.args[0].cond == True:
1093 return rv.args[0].expr
1094 if any(s.expr.has(Piecewise) for p in rv.atoms(Piecewise) for s in p.args):
1095 return piecewise_fold(rv)
1096 return rv
1099def _clip(A, B, k):
1100 """Return interval B as intervals that are covered by A (keyed
1101 to k) and all other intervals of B not covered by A keyed to -1.
1103 The reference point of each interval is the rhs; if the lhs is
1104 greater than the rhs then an interval of zero width interval will
1105 result, e.g. (4, 1) is treated like (1, 1).
1107 Examples
1108 ========
1110 >>> from sympy.functions.elementary.piecewise import _clip
1111 >>> from sympy import Tuple
1112 >>> A = Tuple(1, 3)
1113 >>> B = Tuple(2, 4)
1114 >>> _clip(A, B, 0)
1115 [(2, 3, 0), (3, 4, -1)]
1117 Interpretation: interval portion (2, 3) of interval (2, 4) is
1118 covered by interval (1, 3) and is keyed to 0 as requested;
1119 interval (3, 4) was not covered by (1, 3) and is keyed to -1.
1120 """
1121 a, b = B
1122 c, d = A
1123 c, d = Min(Max(c, a), b), Min(Max(d, a), b)
1124 a, b = Min(a, b), b
1125 p = []
1126 if a != c:
1127 p.append((a, c, -1))
1128 else:
1129 pass
1130 if c != d:
1131 p.append((c, d, k))
1132 else:
1133 pass
1134 if b != d:
1135 if d == c and p and p[-1][-1] == -1:
1136 p[-1] = p[-1][0], b, -1
1137 else:
1138 p.append((d, b, -1))
1139 else:
1140 pass
1142 return p
1145def piecewise_simplify_arguments(expr, **kwargs):
1146 from sympy.simplify.simplify import simplify
1148 # simplify conditions
1149 f1 = expr.args[0].cond.free_symbols
1150 args = None
1151 if len(f1) == 1 and not expr.atoms(Eq):
1152 x = f1.pop()
1153 # this won't return intervals involving Eq
1154 # and it won't handle symbols treated as
1155 # booleans
1156 ok, abe_ = expr._intervals(x, err_on_Eq=True)
1157 def include(c, x, a):
1158 "return True if c.subs(x, a) is True, else False"
1159 try:
1160 return c.subs(x, a) == True
1161 except TypeError:
1162 return False
1163 if ok:
1164 args = []
1165 covered = S.EmptySet
1166 from sympy.sets.sets import Interval
1167 for a, b, e, i in abe_:
1168 c = expr.args[i].cond
1169 incl_a = include(c, x, a)
1170 incl_b = include(c, x, b)
1171 iv = Interval(a, b, not incl_a, not incl_b)
1172 cset = iv - covered
1173 if not cset:
1174 continue
1175 if incl_a and incl_b:
1176 if a.is_infinite and b.is_infinite:
1177 c = S.true
1178 elif b.is_infinite:
1179 c = (x >= a)
1180 elif a in covered or a.is_infinite:
1181 c = (x <= b)
1182 else:
1183 c = And(a <= x, x <= b)
1184 elif incl_a:
1185 if a in covered or a.is_infinite:
1186 c = (x < b)
1187 else:
1188 c = And(a <= x, x < b)
1189 elif incl_b:
1190 if b.is_infinite:
1191 c = (x > a)
1192 else:
1193 c = (x <= b)
1194 else:
1195 if a in covered:
1196 c = (x < b)
1197 else:
1198 c = And(a < x, x < b)
1199 covered |= iv
1200 if a is S.NegativeInfinity and incl_a:
1201 covered |= {S.NegativeInfinity}
1202 if b is S.Infinity and incl_b:
1203 covered |= {S.Infinity}
1204 args.append((e, c))
1205 if not S.Reals.is_subset(covered):
1206 args.append((Undefined, True))
1207 if args is None:
1208 args = list(expr.args)
1209 for i in range(len(args)):
1210 e, c = args[i]
1211 if isinstance(c, Basic):
1212 c = simplify(c, **kwargs)
1213 args[i] = (e, c)
1215 # simplify expressions
1216 doit = kwargs.pop('doit', None)
1217 for i in range(len(args)):
1218 e, c = args[i]
1219 if isinstance(e, Basic):
1220 # Skip doit to avoid growth at every call for some integrals
1221 # and sums, see sympy/sympy#17165
1222 newe = simplify(e, doit=False, **kwargs)
1223 if newe != e:
1224 e = newe
1225 args[i] = (e, c)
1227 # restore kwargs flag
1228 if doit is not None:
1229 kwargs['doit'] = doit
1231 return Piecewise(*args)
1234def _piecewise_collapse_arguments(_args):
1235 newargs = [] # the unevaluated conditions
1236 current_cond = set() # the conditions up to a given e, c pair
1237 for expr, cond in _args:
1238 cond = cond.replace(
1239 lambda _: _.is_Relational, _canonical_coeff)
1240 # Check here if expr is a Piecewise and collapse if one of
1241 # the conds in expr matches cond. This allows the collapsing
1242 # of Piecewise((Piecewise((x,x<0)),x<0)) to Piecewise((x,x<0)).
1243 # This is important when using piecewise_fold to simplify
1244 # multiple Piecewise instances having the same conds.
1245 # Eventually, this code should be able to collapse Piecewise's
1246 # having different intervals, but this will probably require
1247 # using the new assumptions.
1248 if isinstance(expr, Piecewise):
1249 unmatching = []
1250 for i, (e, c) in enumerate(expr.args):
1251 if c in current_cond:
1252 # this would already have triggered
1253 continue
1254 if c == cond:
1255 if c != True:
1256 # nothing past this condition will ever
1257 # trigger and only those args before this
1258 # that didn't match a previous condition
1259 # could possibly trigger
1260 if unmatching:
1261 expr = Piecewise(*(
1262 unmatching + [(e, c)]))
1263 else:
1264 expr = e
1265 break
1266 else:
1267 unmatching.append((e, c))
1269 # check for condition repeats
1270 got = False
1271 # -- if an And contains a condition that was
1272 # already encountered, then the And will be
1273 # False: if the previous condition was False
1274 # then the And will be False and if the previous
1275 # condition is True then then we wouldn't get to
1276 # this point. In either case, we can skip this condition.
1277 for i in ([cond] +
1278 (list(cond.args) if isinstance(cond, And) else
1279 [])):
1280 if i in current_cond:
1281 got = True
1282 break
1283 if got:
1284 continue
1286 # -- if not(c) is already in current_cond then c is
1287 # a redundant condition in an And. This does not
1288 # apply to Or, however: (e1, c), (e2, Or(~c, d))
1289 # is not (e1, c), (e2, d) because if c and d are
1290 # both False this would give no results when the
1291 # true answer should be (e2, True)
1292 if isinstance(cond, And):
1293 nonredundant = []
1294 for c in cond.args:
1295 if isinstance(c, Relational):
1296 if c.negated.canonical in current_cond:
1297 continue
1298 # if a strict inequality appears after
1299 # a non-strict one, then the condition is
1300 # redundant
1301 if isinstance(c, (Lt, Gt)) and (
1302 c.weak in current_cond):
1303 cond = False
1304 break
1305 nonredundant.append(c)
1306 else:
1307 cond = cond.func(*nonredundant)
1308 elif isinstance(cond, Relational):
1309 if cond.negated.canonical in current_cond:
1310 cond = S.true
1312 current_cond.add(cond)
1314 # collect successive e,c pairs when exprs or cond match
1315 if newargs:
1316 if newargs[-1].expr == expr:
1317 orcond = Or(cond, newargs[-1].cond)
1318 if isinstance(orcond, (And, Or)):
1319 orcond = distribute_and_over_or(orcond)
1320 newargs[-1] = ExprCondPair(expr, orcond)
1321 continue
1322 elif newargs[-1].cond == cond:
1323 continue
1324 newargs.append(ExprCondPair(expr, cond))
1325 return newargs
1328_blessed = lambda e: getattr(e.lhs, '_diff_wrt', False) and (
1329 getattr(e.rhs, '_diff_wrt', None) or
1330 isinstance(e.rhs, (Rational, NumberSymbol)))
1333def piecewise_simplify(expr, **kwargs):
1334 expr = piecewise_simplify_arguments(expr, **kwargs)
1335 if not isinstance(expr, Piecewise):
1336 return expr
1337 args = list(expr.args)
1339 args = _piecewise_simplify_eq_and(args)
1340 args = _piecewise_simplify_equal_to_next_segment(args)
1341 return Piecewise(*args)
1344def _piecewise_simplify_equal_to_next_segment(args):
1345 """
1346 See if expressions valid for an Equal expression happens to evaluate
1347 to the same function as in the next piecewise segment, see:
1348 https://github.com/sympy/sympy/issues/8458
1349 """
1350 prevexpr = None
1351 for i, (expr, cond) in reversed(list(enumerate(args))):
1352 if prevexpr is not None:
1353 if isinstance(cond, And):
1354 eqs, other = sift(cond.args,
1355 lambda i: isinstance(i, Eq), binary=True)
1356 elif isinstance(cond, Eq):
1357 eqs, other = [cond], []
1358 else:
1359 eqs = other = []
1360 _prevexpr = prevexpr
1361 _expr = expr
1362 if eqs and not other:
1363 eqs = list(ordered(eqs))
1364 for e in eqs:
1365 # allow 2 args to collapse into 1 for any e
1366 # otherwise limit simplification to only simple-arg
1367 # Eq instances
1368 if len(args) == 2 or _blessed(e):
1369 _prevexpr = _prevexpr.subs(*e.args)
1370 _expr = _expr.subs(*e.args)
1371 # Did it evaluate to the same?
1372 if _prevexpr == _expr:
1373 # Set the expression for the Not equal section to the same
1374 # as the next. These will be merged when creating the new
1375 # Piecewise
1376 args[i] = args[i].func(args[i + 1][0], cond)
1377 else:
1378 # Update the expression that we compare against
1379 prevexpr = expr
1380 else:
1381 prevexpr = expr
1382 return args
1385def _piecewise_simplify_eq_and(args):
1386 """
1387 Try to simplify conditions and the expression for
1388 equalities that are part of the condition, e.g.
1389 Piecewise((n, And(Eq(n,0), Eq(n + m, 0))), (1, True))
1390 -> Piecewise((0, And(Eq(n, 0), Eq(m, 0))), (1, True))
1391 """
1392 for i, (expr, cond) in enumerate(args):
1393 if isinstance(cond, And):
1394 eqs, other = sift(cond.args,
1395 lambda i: isinstance(i, Eq), binary=True)
1396 elif isinstance(cond, Eq):
1397 eqs, other = [cond], []
1398 else:
1399 eqs = other = []
1400 if eqs:
1401 eqs = list(ordered(eqs))
1402 for j, e in enumerate(eqs):
1403 # these blessed lhs objects behave like Symbols
1404 # and the rhs are simple replacements for the "symbols"
1405 if _blessed(e):
1406 expr = expr.subs(*e.args)
1407 eqs[j + 1:] = [ei.subs(*e.args) for ei in eqs[j + 1:]]
1408 other = [ei.subs(*e.args) for ei in other]
1409 cond = And(*(eqs + other))
1410 args[i] = args[i].func(expr, cond)
1411 return args
1414def piecewise_exclusive(expr, *, skip_nan=False, deep=True):
1415 """
1416 Rewrite :class:`Piecewise` with mutually exclusive conditions.
1418 Explanation
1419 ===========
1421 SymPy represents the conditions of a :class:`Piecewise` in an
1422 "if-elif"-fashion, allowing more than one condition to be simultaneously
1423 True. The interpretation is that the first condition that is True is the
1424 case that holds. While this is a useful representation computationally it
1425 is not how a piecewise formula is typically shown in a mathematical text.
1426 The :func:`piecewise_exclusive` function can be used to rewrite any
1427 :class:`Piecewise` with more typical mutually exclusive conditions.
1429 Note that further manipulation of the resulting :class:`Piecewise`, e.g.
1430 simplifying it, will most likely make it non-exclusive. Hence, this is
1431 primarily a function to be used in conjunction with printing the Piecewise
1432 or if one would like to reorder the expression-condition pairs.
1434 If it is not possible to determine that all possibilities are covered by
1435 the different cases of the :class:`Piecewise` then a final
1436 :class:`~sympy.core.numbers.NaN` case will be included explicitly. This
1437 can be prevented by passing ``skip_nan=True``.
1439 Examples
1440 ========
1442 >>> from sympy import piecewise_exclusive, Symbol, Piecewise, S
1443 >>> x = Symbol('x', real=True)
1444 >>> p = Piecewise((0, x < 0), (S.Half, x <= 0), (1, True))
1445 >>> piecewise_exclusive(p)
1446 Piecewise((0, x < 0), (1/2, Eq(x, 0)), (1, x > 0))
1447 >>> piecewise_exclusive(Piecewise((2, x > 1)))
1448 Piecewise((2, x > 1), (nan, x <= 1))
1449 >>> piecewise_exclusive(Piecewise((2, x > 1)), skip_nan=True)
1450 Piecewise((2, x > 1))
1452 Parameters
1453 ==========
1455 expr: a SymPy expression.
1456 Any :class:`Piecewise` in the expression will be rewritten.
1457 skip_nan: ``bool`` (default ``False``)
1458 If ``skip_nan`` is set to ``True`` then a final
1459 :class:`~sympy.core.numbers.NaN` case will not be included.
1460 deep: ``bool`` (default ``True``)
1461 If ``deep`` is ``True`` then :func:`piecewise_exclusive` will rewrite
1462 any :class:`Piecewise` subexpressions in ``expr`` rather than just
1463 rewriting ``expr`` itself.
1465 Returns
1466 =======
1468 An expression equivalent to ``expr`` but where all :class:`Piecewise` have
1469 been rewritten with mutually exclusive conditions.
1471 See Also
1472 ========
1474 Piecewise
1475 piecewise_fold
1476 """
1478 def make_exclusive(*pwargs):
1480 cumcond = false
1481 newargs = []
1483 # Handle the first n-1 cases
1484 for expr_i, cond_i in pwargs[:-1]:
1485 cancond = And(cond_i, Not(cumcond)).simplify()
1486 cumcond = Or(cond_i, cumcond).simplify()
1487 newargs.append((expr_i, cancond))
1489 # For the nth case defer simplification of cumcond
1490 expr_n, cond_n = pwargs[-1]
1491 cancond_n = And(cond_n, Not(cumcond)).simplify()
1492 newargs.append((expr_n, cancond_n))
1494 if not skip_nan:
1495 cumcond = Or(cond_n, cumcond).simplify()
1496 if cumcond is not true:
1497 newargs.append((Undefined, Not(cumcond).simplify()))
1499 return Piecewise(*newargs, evaluate=False)
1501 if deep:
1502 return expr.replace(Piecewise, make_exclusive)
1503 elif isinstance(expr, Piecewise):
1504 return make_exclusive(*expr.args)
1505 else:
1506 return expr