1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372 |
- from sympy.core import S, Function, diff, Tuple, Dummy
- from sympy.core.basic import Basic, as_Basic
- from sympy.core.numbers import Rational, NumberSymbol
- 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.polys.polyutils import illegal
- from sympy.sets.sets import Interval
- 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)
- @property
- def expr(self):
- """
- Returns the expression of this pair.
- """
- return self.args[0]
- @property
- def cond(self):
- """
- Returns the condition of this pair.
- """
- return self.args[1]
- @property
- def is_commutative(self):
- return self.expr.is_commutative
- def __iter__(self):
- yield self.expr
- yield self.cond
- def _eval_simplify(self, **kwargs):
- return self.func(*[a.simplify(**kwargs) for a in self.args])
- class Piecewise(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, 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)
- @classmethod
- def eval(cls, *_args):
- """Either return a modified version of the args or, if no
- modifications were made, return None.
- Modifications that are made here:
- 1. relationals are made canonical
- 2. any False conditions are dropped
- 3. any repeat of a previous condition is ignored
- 4. any args past one with a true condition are dropped
- If there are no args left, nan will be returned.
- If there is a single arg with a True condition, its
- corresponding expression will be returned.
- EXAMPLES
- ========
- >>> from sympy import Piecewise
- >>> from sympy.abc import x
- >>> cond = -x < -1
- >>> args = [(1, cond), (4, cond), (3, False), (2, True), (5, x < 1)]
- >>> Piecewise(*args, evaluate=False)
- Piecewise((1, -x < -1), (4, -x < -1), (2, True))
- >>> Piecewise(*args)
- Piecewise((1, x > 1), (2, True))
- """
- if not _args:
- return Undefined
- if len(_args) == 1 and _args[0][-1] == True:
- return _args[0][0]
- newargs = [] # 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:
- newargs[-1] = ExprCondPair(expr, cond)
- continue
- newargs.append(ExprCondPair(expr, cond))
- # 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 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')
- @classmethod
- def __eval_cond(cls, cond):
- """Return the truth value of the condition."""
- if cond == True:
- return True
- if isinstance(cond, Eq):
- try:
- diff = cond.lhs - cond.rhs
- if diff.is_commutative:
- return diff.is_zero
- except TypeError:
- pass
- def as_expr_set_pairs(self, domain=None):
- """Return tuples for each argument of self that give
- the expression and the interval in which it is valid
- which is contained within the given domain.
- If a condition cannot be converted to a set, an error
- will be raised. The variable of the conditions is
- assumed to be real; sets of real values are returned.
- Examples
- ========
- >>> from sympy import Piecewise, Interval
- >>> from sympy.abc import x
- >>> p = Piecewise(
- ... (1, x < 2),
- ... (2,(x > 0) & (x < 4)),
- ... (3, True))
- >>> p.as_expr_set_pairs()
- [(1, Interval.open(-oo, 2)),
- (2, Interval.Ropen(2, 4)),
- (3, Interval(4, oo))]
- >>> p.as_expr_set_pairs(Interval(0, 3))
- [(1, Interval.Ropen(0, 2)),
- (2, Interval(2, 3))]
- """
- if domain is None:
- domain = S.Reals
- exp_sets = []
- U = domain
- complex = not domain.is_subset(S.Reals)
- cond_free = set()
- for expr, cond in self.args:
- cond_free |= cond.free_symbols
- if len(cond_free) > 1:
- raise NotImplementedError(filldedent('''
- multivariate conditions are not handled.'''))
- if complex:
- for i in cond.atoms(Relational):
- if not isinstance(i, (Eq, Ne)):
- raise ValueError(filldedent('''
- Inequalities in the complex domain are
- not supported. Try the real domain by
- setting domain=S.Reals'''))
- cond_int = U.intersect(cond.as_set())
- U = U - cond_int
- if cond_int != S.EmptySet:
- exp_sets.append((expr, cond_int))
- return exp_sets
- def _eval_rewrite_as_ITE(self, *args, **kwargs):
- byfree = {}
- args = list(args)
- default = any(c == True for b, c in args)
- for i, (b, c) in enumerate(args):
- if not isinstance(b, Boolean) and b != True:
- raise TypeError(filldedent('''
- Expecting Boolean or bool but got `%s`
- ''' % func_name(b)))
- if c == True:
- break
- # 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):
- 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 = 1
- for c in args:
- if b1:
- k *= 1 - rewrite(c)
- else:
- k *= rewrite(c)
- 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
- """
- 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
- 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
- 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
- if incl_a and incl_b:
- if a.is_infinite and b.is_infinite:
- c = S.true
- elif b.is_infinite:
- c = (x >= a)
- elif a in covered or a.is_infinite:
- c = (x <= b)
- else:
- c = And(a <= x, x <= b)
- elif incl_a:
- if a in covered or a.is_infinite:
- c = (x < b)
- else:
- c = And(a <= x, x < b)
- elif incl_b:
- if b.is_infinite:
- c = (x > a)
- else:
- c = (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_simplify(expr, **kwargs):
- expr = piecewise_simplify_arguments(expr, **kwargs)
- if not isinstance(expr, Piecewise):
- return expr
- args = list(expr.args)
- _blessed = lambda e: getattr(e.lhs, '_diff_wrt', False) and (
- getattr(e.rhs, '_diff_wrt', None) or
- isinstance(e.rhs, (Rational, NumberSymbol)))
- for i, (expr, cond) in enumerate(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))
- 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)
- # 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 Piecewise(*args)
|