604 lines
21 KiB
Python
604 lines
21 KiB
Python
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
|