123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603 |
- from sympy.core.add import Add
- from sympy.core.containers import Tuple
- from sympy.core.expr import Expr
- from sympy.core.function import AppliedUndef, UndefinedFunction
- from sympy.core.mul import Mul
- from sympy.core.relational import Equality, Relational
- from sympy.core.singleton import S
- from sympy.core.symbol import Symbol, Dummy
- from sympy.core.sympify import sympify
- from sympy.functions.elementary.piecewise import (piecewise_fold,
- Piecewise)
- from sympy.logic.boolalg import BooleanFunction
- from sympy.matrices.matrices import MatrixBase
- from sympy.sets.sets import Interval, Set
- from sympy.sets.fancysets import Range
- from sympy.tensor.indexed import Idx
- from sympy.utilities import flatten
- from sympy.utilities.iterables import sift, is_sequence
- from sympy.utilities.exceptions import sympy_deprecation_warning
- def _common_new(cls, function, *symbols, discrete, **assumptions):
- """Return either a special return value or the tuple,
- (function, limits, orientation). This code is common to
- both ExprWithLimits and AddWithLimits."""
- function = sympify(function)
- if isinstance(function, Equality):
- # This transforms e.g. Integral(Eq(x, y)) to Eq(Integral(x), Integral(y))
- # but that is only valid for definite integrals.
- limits, orientation = _process_limits(*symbols, discrete=discrete)
- if not (limits and all(len(limit) == 3 for limit in limits)):
- sympy_deprecation_warning(
- """
- Creating a indefinite integral with an Eq() argument is
- deprecated.
- This is because indefinite integrals do not preserve equality
- due to the arbitrary constants. If you want an equality of
- indefinite integrals, use Eq(Integral(a, x), Integral(b, x))
- explicitly.
- """,
- deprecated_since_version="1.6",
- active_deprecations_target="deprecated-indefinite-integral-eq",
- stacklevel=5,
- )
- lhs = function.lhs
- rhs = function.rhs
- return Equality(cls(lhs, *symbols, **assumptions), \
- cls(rhs, *symbols, **assumptions))
- if function is S.NaN:
- return S.NaN
- if symbols:
- limits, orientation = _process_limits(*symbols, discrete=discrete)
- for i, li in enumerate(limits):
- if len(li) == 4:
- function = function.subs(li[0], li[-1])
- limits[i] = Tuple(*li[:-1])
- else:
- # symbol not provided -- we can still try to compute a general form
- free = function.free_symbols
- if len(free) != 1:
- raise ValueError(
- "specify dummy variables for %s" % function)
- limits, orientation = [Tuple(s) for s in free], 1
- # denest any nested calls
- while cls == type(function):
- limits = list(function.limits) + limits
- function = function.function
- # Any embedded piecewise functions need to be brought out to the
- # top level. We only fold Piecewise that contain the integration
- # variable.
- reps = {}
- symbols_of_integration = {i[0] for i in limits}
- for p in function.atoms(Piecewise):
- if not p.has(*symbols_of_integration):
- reps[p] = Dummy()
- # mask off those that don't
- function = function.xreplace(reps)
- # do the fold
- function = piecewise_fold(function)
- # remove the masking
- function = function.xreplace({v: k for k, v in reps.items()})
- return function, limits, orientation
- def _process_limits(*symbols, discrete=None):
- """Process the list of symbols and convert them to canonical limits,
- storing them as Tuple(symbol, lower, upper). The orientation of
- the function is also returned when the upper limit is missing
- so (x, 1, None) becomes (x, None, 1) and the orientation is changed.
- In the case that a limit is specified as (symbol, Range), a list of
- length 4 may be returned if a change of variables is needed; the
- expression that should replace the symbol in the expression is
- the fourth element in the list.
- """
- limits = []
- orientation = 1
- if discrete is None:
- err_msg = 'discrete must be True or False'
- elif discrete:
- err_msg = 'use Range, not Interval or Relational'
- else:
- err_msg = 'use Interval or Relational, not Range'
- for V in symbols:
- if isinstance(V, (Relational, BooleanFunction)):
- if discrete:
- raise TypeError(err_msg)
- variable = V.atoms(Symbol).pop()
- V = (variable, V.as_set())
- elif isinstance(V, Symbol) or getattr(V, '_diff_wrt', False):
- if isinstance(V, Idx):
- if V.lower is None or V.upper is None:
- limits.append(Tuple(V))
- else:
- limits.append(Tuple(V, V.lower, V.upper))
- else:
- limits.append(Tuple(V))
- continue
- if is_sequence(V) and not isinstance(V, Set):
- if len(V) == 2 and isinstance(V[1], Set):
- V = list(V)
- if isinstance(V[1], Interval): # includes Reals
- if discrete:
- raise TypeError(err_msg)
- V[1:] = V[1].inf, V[1].sup
- elif isinstance(V[1], Range):
- if not discrete:
- raise TypeError(err_msg)
- lo = V[1].inf
- hi = V[1].sup
- dx = abs(V[1].step) # direction doesn't matter
- if dx == 1:
- V[1:] = [lo, hi]
- else:
- if lo is not S.NegativeInfinity:
- V = [V[0]] + [0, (hi - lo)//dx, dx*V[0] + lo]
- else:
- V = [V[0]] + [0, S.Infinity, -dx*V[0] + hi]
- else:
- # more complicated sets would require splitting, e.g.
- # Union(Interval(1, 3), interval(6,10))
- raise NotImplementedError(
- 'expecting Range' if discrete else
- 'Relational or single Interval' )
- V = sympify(flatten(V)) # list of sympified elements/None
- if isinstance(V[0], (Symbol, Idx)) or getattr(V[0], '_diff_wrt', False):
- newsymbol = V[0]
- if len(V) == 3:
- # general case
- if V[2] is None and V[1] is not None:
- orientation *= -1
- V = [newsymbol] + [i for i in V[1:] if i is not None]
- lenV = len(V)
- if not isinstance(newsymbol, Idx) or lenV == 3:
- if lenV == 4:
- limits.append(Tuple(*V))
- continue
- if lenV == 3:
- if isinstance(newsymbol, Idx):
- # Idx represents an integer which may have
- # specified values it can take on; if it is
- # given such a value, an error is raised here
- # if the summation would try to give it a larger
- # or smaller value than permitted. None and Symbolic
- # values will not raise an error.
- lo, hi = newsymbol.lower, newsymbol.upper
- try:
- if lo is not None and not bool(V[1] >= lo):
- raise ValueError("Summation will set Idx value too low.")
- except TypeError:
- pass
- try:
- if hi is not None and not bool(V[2] <= hi):
- raise ValueError("Summation will set Idx value too high.")
- except TypeError:
- pass
- limits.append(Tuple(*V))
- continue
- if lenV == 1 or (lenV == 2 and V[1] is None):
- limits.append(Tuple(newsymbol))
- continue
- elif lenV == 2:
- limits.append(Tuple(newsymbol, V[1]))
- continue
- raise ValueError('Invalid limits given: %s' % str(symbols))
- return limits, orientation
- class ExprWithLimits(Expr):
- __slots__ = ('is_commutative',)
- def __new__(cls, function, *symbols, **assumptions):
- from sympy.concrete.products import Product
- pre = _common_new(cls, function, *symbols,
- discrete=issubclass(cls, Product), **assumptions)
- if isinstance(pre, tuple):
- function, limits, _ = pre
- else:
- return pre
- # limits must have upper and lower bounds; the indefinite form
- # is not supported. This restriction does not apply to AddWithLimits
- if any(len(l) != 3 or None in l for l in limits):
- raise ValueError('ExprWithLimits requires values for lower and upper bounds.')
- obj = Expr.__new__(cls, **assumptions)
- arglist = [function]
- arglist.extend(limits)
- obj._args = tuple(arglist)
- obj.is_commutative = function.is_commutative # limits already checked
- return obj
- @property
- def function(self):
- """Return the function applied across limits.
- Examples
- ========
- >>> from sympy import Integral
- >>> from sympy.abc import x
- >>> Integral(x**2, (x,)).function
- x**2
- See Also
- ========
- limits, variables, free_symbols
- """
- return self._args[0]
- @property
- def kind(self):
- return self.function.kind
- @property
- def limits(self):
- """Return the limits of expression.
- Examples
- ========
- >>> from sympy import Integral
- >>> from sympy.abc import x, i
- >>> Integral(x**i, (i, 1, 3)).limits
- ((i, 1, 3),)
- See Also
- ========
- function, variables, free_symbols
- """
- return self._args[1:]
- @property
- def variables(self):
- """Return a list of the limit variables.
- >>> from sympy import Sum
- >>> from sympy.abc import x, i
- >>> Sum(x**i, (i, 1, 3)).variables
- [i]
- See Also
- ========
- function, limits, free_symbols
- as_dummy : Rename dummy variables
- sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
- """
- return [l[0] for l in self.limits]
- @property
- def bound_symbols(self):
- """Return only variables that are dummy variables.
- Examples
- ========
- >>> from sympy import Integral
- >>> from sympy.abc import x, i, j, k
- >>> Integral(x**i, (i, 1, 3), (j, 2), k).bound_symbols
- [i, j]
- See Also
- ========
- function, limits, free_symbols
- as_dummy : Rename dummy variables
- sympy.integrals.integrals.Integral.transform : Perform mapping on the dummy variable
- """
- return [l[0] for l in self.limits if len(l) != 1]
- @property
- def free_symbols(self):
- """
- This method returns the symbols in the object, excluding those
- that take on a specific value (i.e. the dummy symbols).
- Examples
- ========
- >>> from sympy import Sum
- >>> from sympy.abc import x, y
- >>> Sum(x, (x, y, 1)).free_symbols
- {y}
- """
- # don't test for any special values -- nominal free symbols
- # should be returned, e.g. don't return set() if the
- # function is zero -- treat it like an unevaluated expression.
- function, limits = self.function, self.limits
- # mask off non-symbol integration variables that have
- # more than themself as a free symbol
- reps = {i[0]: i[0] if i[0].free_symbols == {i[0]} else Dummy()
- for i in self.limits}
- function = function.xreplace(reps)
- isyms = function.free_symbols
- for xab in limits:
- v = reps[xab[0]]
- if len(xab) == 1:
- isyms.add(v)
- continue
- # take out the target symbol
- if v in isyms:
- isyms.remove(v)
- # add in the new symbols
- for i in xab[1:]:
- isyms.update(i.free_symbols)
- reps = {v: k for k, v in reps.items()}
- return {reps.get(_, _) for _ in isyms}
- @property
- def is_number(self):
- """Return True if the Sum has no free symbols, else False."""
- return not self.free_symbols
- def _eval_interval(self, x, a, b):
- limits = [(i if i[0] != x else (x, a, b)) for i in self.limits]
- integrand = self.function
- return self.func(integrand, *limits)
- def _eval_subs(self, old, new):
- """
- Perform substitutions over non-dummy variables
- of an expression with limits. Also, can be used
- to specify point-evaluation of an abstract antiderivative.
- Examples
- ========
- >>> from sympy import Sum, oo
- >>> from sympy.abc import s, n
- >>> Sum(1/n**s, (n, 1, oo)).subs(s, 2)
- Sum(n**(-2), (n, 1, oo))
- >>> from sympy import Integral
- >>> from sympy.abc import x, a
- >>> Integral(a*x**2, x).subs(x, 4)
- Integral(a*x**2, (x, 4))
- See Also
- ========
- variables : Lists the integration variables
- transform : Perform mapping on the dummy variable for integrals
- change_index : Perform mapping on the sum and product dummy variables
- """
- func, limits = self.function, list(self.limits)
- # If one of the expressions we are replacing is used as a func index
- # one of two things happens.
- # - the old variable first appears as a free variable
- # so we perform all free substitutions before it becomes
- # a func index.
- # - the old variable first appears as a func index, in
- # which case we ignore. See change_index.
- # Reorder limits to match standard mathematical practice for scoping
- limits.reverse()
- if not isinstance(old, Symbol) or \
- old.free_symbols.intersection(self.free_symbols):
- sub_into_func = True
- for i, xab in enumerate(limits):
- if 1 == len(xab) and old == xab[0]:
- if new._diff_wrt:
- xab = (new,)
- else:
- xab = (old, old)
- limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
- if len(xab[0].free_symbols.intersection(old.free_symbols)) != 0:
- sub_into_func = False
- break
- if isinstance(old, (AppliedUndef, UndefinedFunction)):
- sy2 = set(self.variables).intersection(set(new.atoms(Symbol)))
- sy1 = set(self.variables).intersection(set(old.args))
- if not sy2.issubset(sy1):
- raise ValueError(
- "substitution cannot create dummy dependencies")
- sub_into_func = True
- if sub_into_func:
- func = func.subs(old, new)
- else:
- # old is a Symbol and a dummy variable of some limit
- for i, xab in enumerate(limits):
- if len(xab) == 3:
- limits[i] = Tuple(xab[0], *[l._subs(old, new) for l in xab[1:]])
- if old == xab[0]:
- break
- # simplify redundant limits (x, x) to (x, )
- for i, xab in enumerate(limits):
- if len(xab) == 2 and (xab[0] - xab[1]).is_zero:
- limits[i] = Tuple(xab[0], )
- # Reorder limits back to representation-form
- limits.reverse()
- return self.func(func, *limits)
- @property
- def has_finite_limits(self):
- """
- Returns True if the limits are known to be finite, either by the
- explicit bounds, assumptions on the bounds, or assumptions on the
- variables. False if known to be infinite, based on the bounds.
- None if not enough information is available to determine.
- Examples
- ========
- >>> from sympy import Sum, Integral, Product, oo, Symbol
- >>> x = Symbol('x')
- >>> Sum(x, (x, 1, 8)).has_finite_limits
- True
- >>> Integral(x, (x, 1, oo)).has_finite_limits
- False
- >>> M = Symbol('M')
- >>> Sum(x, (x, 1, M)).has_finite_limits
- >>> N = Symbol('N', integer=True)
- >>> Product(x, (x, 1, N)).has_finite_limits
- True
- See Also
- ========
- has_reversed_limits
- """
- ret_None = False
- for lim in self.limits:
- if len(lim) == 3:
- if any(l.is_infinite for l in lim[1:]):
- # Any of the bounds are +/-oo
- return False
- elif any(l.is_infinite is None for l in lim[1:]):
- # Maybe there are assumptions on the variable?
- if lim[0].is_infinite is None:
- ret_None = True
- else:
- if lim[0].is_infinite is None:
- ret_None = True
- if ret_None:
- return None
- return True
- @property
- def has_reversed_limits(self):
- """
- Returns True if the limits are known to be in reversed order, either
- by the explicit bounds, assumptions on the bounds, or assumptions on the
- variables. False if known to be in normal order, based on the bounds.
- None if not enough information is available to determine.
- Examples
- ========
- >>> from sympy import Sum, Integral, Product, oo, Symbol
- >>> x = Symbol('x')
- >>> Sum(x, (x, 8, 1)).has_reversed_limits
- True
- >>> Sum(x, (x, 1, oo)).has_reversed_limits
- False
- >>> M = Symbol('M')
- >>> Integral(x, (x, 1, M)).has_reversed_limits
- >>> N = Symbol('N', integer=True, positive=True)
- >>> Sum(x, (x, 1, N)).has_reversed_limits
- False
- >>> Product(x, (x, 2, N)).has_reversed_limits
- >>> Product(x, (x, 2, N)).subs(N, N + 2).has_reversed_limits
- False
- See Also
- ========
- sympy.concrete.expr_with_intlimits.ExprWithIntLimits.has_empty_sequence
- """
- ret_None = False
- for lim in self.limits:
- if len(lim) == 3:
- var, a, b = lim
- dif = b - a
- if dif.is_extended_negative:
- return True
- elif dif.is_extended_nonnegative:
- continue
- else:
- ret_None = True
- else:
- return None
- if ret_None:
- return None
- return False
- class AddWithLimits(ExprWithLimits):
- r"""Represents unevaluated oriented additions.
- Parent class for Integral and Sum.
- """
- __slots__ = ()
- def __new__(cls, function, *symbols, **assumptions):
- from sympy.concrete.summations import Sum
- pre = _common_new(cls, function, *symbols,
- discrete=issubclass(cls, Sum), **assumptions)
- if isinstance(pre, tuple):
- function, limits, orientation = pre
- else:
- return pre
- obj = Expr.__new__(cls, **assumptions)
- arglist = [orientation*function] # orientation not used in ExprWithLimits
- arglist.extend(limits)
- obj._args = tuple(arglist)
- obj.is_commutative = function.is_commutative # limits already checked
- return obj
- def _eval_adjoint(self):
- if all(x.is_real for x in flatten(self.limits)):
- return self.func(self.function.adjoint(), *self.limits)
- return None
- def _eval_conjugate(self):
- if all(x.is_real for x in flatten(self.limits)):
- return self.func(self.function.conjugate(), *self.limits)
- return None
- def _eval_transpose(self):
- if all(x.is_real for x in flatten(self.limits)):
- return self.func(self.function.transpose(), *self.limits)
- return None
- def _eval_factor(self, **hints):
- if 1 == len(self.limits):
- summand = self.function.factor(**hints)
- if summand.is_Mul:
- out = sift(summand.args, lambda w: w.is_commutative \
- and not set(self.variables) & w.free_symbols)
- return Mul(*out[True])*self.func(Mul(*out[False]), \
- *self.limits)
- else:
- summand = self.func(self.function, *self.limits[0:-1]).factor()
- if not summand.has(self.variables[-1]):
- return self.func(1, [self.limits[-1]]).doit()*summand
- elif isinstance(summand, Mul):
- return self.func(summand, self.limits[-1]).factor()
- return self
- def _eval_expand_basic(self, **hints):
- summand = self.function.expand(**hints)
- force = hints.get('force', False)
- if (summand.is_Add and (force or summand.is_commutative and
- self.has_finite_limits is not False)):
- return Add(*[self.func(i, *self.limits) for i in summand.args])
- elif isinstance(summand, MatrixBase):
- return summand.applyfunc(lambda x: self.func(x, *self.limits))
- elif summand != self.function:
- return self.func(summand, *self.limits)
- return self
|