Spaces:
Sleeping
Sleeping
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.matrixbase 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 | |
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] | |
def kind(self): | |
return self.function.kind | |
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:] | |
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] | |
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] | |
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} | |
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) | |
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 | |
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 | |