Source code for diofant.concrete.expr_with_limits

from ..core import Add, Dummy, Equality, Expr, Mul, Symbol, Tuple, nan, sympify
from ..core.compatibility import is_sequence
from ..functions import piecewise_fold
from ..sets.sets import Interval
from ..utilities import flatten, sift

def _process_limits(*symbols):
"""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.

"""
limits = []
orientation = 1
for V in symbols:
if isinstance(V, (Dummy, Symbol)) or getattr(V, '_diff_wrt', False):
limits.append(Tuple(V))
continue
elif is_sequence(V, Tuple):
V = sympify(flatten(V))
if V.is_Symbol or getattr(V, '_diff_wrt', False):
newsymbol = V
if len(V) == 2 and isinstance(V, Interval):
V[1:] = [V.start, V.end]

if len(V) == 3:
if V is None and V is not None:
nlim = [V]
elif V is not None and V is None:
orientation *= -1
nlim = [V]
elif V is None and V is None:
nlim = []
else:
nlim = V[1:]
limits.append(Tuple(newsymbol, *nlim))
continue
elif len(V) == 1 or (len(V) == 2 and V is None):
limits.append(Tuple(newsymbol))
continue
elif len(V) == 2:
limits.append(Tuple(newsymbol, V))
continue

raise ValueError('Invalid limits given: %s' % str(symbols))

return limits, orientation

[docs]class ExprWithLimits(Expr):

def __new__(cls, function, *symbols, **assumptions):
# Any embedded piecewise functions need to be brought out to the
# top level so that integration can go into piecewise mode at the
# earliest possible moment.
function = sympify(function)
if hasattr(function, 'func') and isinstance(function, Equality):
lhs = function.lhs
rhs = function.rhs
return Equality(cls(lhs, *symbols, **assumptions),
cls(rhs, *symbols, **assumptions))
function = piecewise_fold(function)

if function is nan:
return nan

if symbols:
limits, orientation = _process_limits(*symbols)
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 = [Tuple(s) for s in free]

# denest any nested calls
while cls == type(function):
limits = list(function.limits) + limits
function = function.function

# Only limits with lower and upper bounds are supported; the indefinite form
# is not supported
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)

return obj

@property
def function(self):
"""Return the function applied across limits.

Examples
========

>>> Integral(x**2, x).function
x**2

========

diofant.concrete.expr_with_limits.ExprWithLimits.limits
diofant.concrete.expr_with_limits.ExprWithLimits.variables
diofant.concrete.expr_with_limits.ExprWithLimits.free_symbols

"""
return self.args

@property
def limits(self):
"""Return the limits of expression.

Examples
========

>>> from diofant.abc import i
>>> Integral(x**i, (i, 1, 3)).limits
((i, 1, 3),)

========

diofant.concrete.expr_with_limits.ExprWithLimits.function
diofant.concrete.expr_with_limits.ExprWithLimits.variables
diofant.concrete.expr_with_limits.ExprWithLimits.free_symbols

"""
return self.args[1:]

@property
def variables(self):
"""Return a list of the dummy variables

>>> from diofant.abc import i
>>> Sum(x**i, (i, 1, 3)).variables
[i]

========

diofant.concrete.expr_with_limits.ExprWithLimits.function
diofant.concrete.expr_with_limits.ExprWithLimits.limits
diofant.concrete.expr_with_limits.ExprWithLimits.free_symbols
diofant.concrete.expr_with_limits.ExprWithLimits.as_dummy : Rename dummy variables

"""
return [l for l in self.limits]

@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
========

>>> 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
isyms = function.free_symbols
for xab in limits:
if len(xab) == 1:
continue
# take out the target symbol
if xab in isyms:
isyms.remove(xab)
# add in the new symbols
for i in xab[1:]:
isyms.update(i.free_symbols)
return isyms

@property
def is_number(self):
"""Return True if the Sum has no free symbols, else False."""
return not self.free_symbols

[docs]    def as_dummy(self):
"""
Replace instances of the given dummy variables with explicit dummy
counterparts to make clear what are dummy variables and what
are real-world symbols in an object.

Examples
========

>>> Integral(x, (x, x, y), (y, x, y)).as_dummy()
Integral(_x, (_x, x, _y), (_y, x, y))

If the object supports the "integral at" limit (x,) it
is not treated as a dummy, but the explicit form, (x, x)
of length 2 does treat the variable as a dummy.

>>> Integral(x, x).as_dummy()
Integral(x, x)
>>> Integral(x, (x, x)).as_dummy()
Integral(_x, (_x, x))

If there were no dummies in the original expression, then the
the symbols which cannot be changed by subs() are clearly seen as
those with an underscore prefix.

========

diofant.concrete.expr_with_limits.ExprWithLimits.variables : Lists the integration variables

"""
reps = {}
f = self.function
limits = list(self.limits)
for i in range(-1, -len(limits) - 1, -1):
xab = list(limits[i])
if len(xab) == 1:
continue
x = xab
xab = x.as_dummy()
for j in range(1, len(xab)):
xab[j] = xab[j].subs(reps)
reps[x] = xab
limits[i] = xab
f = f.subs(reps)
return self.func(f, *limits)

def _eval_interval(self, x, a, b):
limits = [(i if i != 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 diofant.abc import s
>>> Sum(1/n**s, (n, 1, oo)).subs({s: 2})
Sum(n**(-2), (n, 1, oo))

>>> Integral(a*x**2, x).subs({x: 4})
Integral(a*x**2, (x, 4))

========

variables : Lists the integration variables
change_index : Perform mapping on the sum and product dummy variables

"""

from ..core.function import AppliedUndef, UndefinedFunction
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:
xab = (old, old)
limits[i] = Tuple(xab, *[l._subs(old, new) for l in xab[1:]])
if len(xab.free_symbols.intersection(old.free_symbols)) != 0:
sub_into_func = False
break
if isinstance(old, AppliedUndef) or isinstance(old, 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 can not 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, *[l._subs(old, new) for l in xab[1:]])
if old == xab:
break
# simplify redundant limits (x, x)  to (x, )
for i, xab in enumerate(limits):
if len(xab) == 2 and (xab - xab).is_zero:
limits[i] = Tuple(xab, )

# Reorder limits back to representation-form
limits.reverse()

return self.func(func, *limits)

def _eval_is_commutative(self):
return self.function.is_commutative

Parent class for Integral and Sum.

"""

def __new__(cls, function, *symbols, **assumptions):
# Any embedded piecewise functions need to be brought out to the
# top level so that integration can go into piecewise mode at the
# earliest possible moment.
#
# This constructor only differs from ExprWithLimits
# in the application of the orientation variable.  Perhaps merge?
function = sympify(function)
if hasattr(function, 'func') and isinstance(function, Equality):
lhs = function.lhs
rhs = function.rhs
return Equality(cls(lhs, *symbols, **assumptions),
cls(rhs, *symbols, **assumptions))
function = piecewise_fold(function)

if function is nan:
return nan

if symbols:
limits, orientation = _process_limits(*symbols)
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. If the integrand contains"
" more than one free symbol, an integration variable should"
" be supplied explicitly e.g., integrate(f(x, y), x)"
% 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

obj = Expr.__new__(cls, **assumptions)
arglist = [orientation*function]
arglist.extend(limits)
obj._args = tuple(arglist)

return obj

if all(x.is_extended_real for x in flatten(self.limits)):
return

def _eval_conjugate(self):
if all(x.is_extended_real for x in flatten(self.limits)):
return self.func(self.function.conjugate(), *self.limits)
return

def _eval_transpose(self):
if all(x.is_extended_real for x in flatten(self.limits)):
return self.func(self.function.transpose(), *self.limits)
return

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
return self

def _eval_expand_basic(self, **hints):
summand = self.function.expand(**hints)