Skip to content

Commit

Permalink
Merge pull request #12976 from leosartaj/fps_integrate
Browse files Browse the repository at this point in the history
integrate now allows fps
  • Loading branch information
smichr committed Aug 28, 2017
2 parents e8dc654 + 4838515 commit 4009719
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 71 deletions.
129 changes: 63 additions & 66 deletions sympy/concrete/expr_with_limits.py
Expand Up @@ -6,17 +6,66 @@
from sympy.core.relational import Equality
from sympy.sets.sets import Interval
from sympy.core.singleton import S
from sympy.core.symbol import Symbol
from sympy.core.symbol import Symbol, Dummy
from sympy.core.sympify import sympify
from sympy.core.compatibility import is_sequence, range
from sympy.core.containers import Tuple
from sympy.functions.elementary.piecewise import piecewise_fold
from sympy.functions.elementary.piecewise import (piecewise_fold,
Piecewise)
from sympy.utilities import flatten
from sympy.utilities.iterables import sift
from sympy.matrices import Matrix
from sympy.tensor.indexed import Idx


def _common_new(cls, function, *symbols, **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 hasattr(function, 'func') and function.func is Equality:
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)
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 = set([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):
"""Process the list of symbols and convert them to canonical limits,
storing them as Tuple(symbol, lower, upper). The orientation of
Expand Down Expand Up @@ -75,37 +124,14 @@ class ExprWithLimits(Expr):
__slots__ = ['is_commutative']

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 function.func is Equality:
lhs = function.lhs
rhs = function.rhs
return Equality(cls(lhs, *symbols, **assumptions), \
cls(rhs, *symbols, **assumptions))
function = piecewise_fold(function)

if function is S.NaN:
return S.NaN

if symbols:
limits, orientation = _process_limits(*symbols)
pre = _common_new(cls, function, *symbols, **assumptions)
if type(pre) is tuple:
function, limits, _ = pre
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

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

Expand Down Expand Up @@ -347,43 +373,14 @@ class AddWithLimits(ExprWithLimits):
"""

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 function.func is Equality:
lhs = function.lhs
rhs = function.rhs
return Equality(cls(lhs, *symbols, **assumptions), \
cls(rhs, *symbols, **assumptions))
function = piecewise_fold(function)

if function is S.NaN:
return S.NaN

if symbols:
limits, orientation = _process_limits(*symbols)
pre = _common_new(cls, function, *symbols, **assumptions)
if type(pre) is tuple:
function, limits, orientation = pre
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
return pre

obj = Expr.__new__(cls, **assumptions)
arglist = [orientation*function]
arglist = [orientation*function] # orientation not used in ExprWithLimits
arglist.extend(limits)
obj._args = tuple(arglist)
obj.is_commutative = function.is_commutative # limits already checked
Expand Down
7 changes: 7 additions & 0 deletions sympy/integrals/integrals.py
Expand Up @@ -23,6 +23,7 @@
from sympy.functions.elementary.exponential import log
from sympy.series import limit
from sympy.series.order import Order
from sympy.series.formal import FormalPowerSeries


class Integral(AddWithLimits):
Expand Down Expand Up @@ -401,6 +402,12 @@ def doit(self, **hints):
if isinstance(function, MatrixBase):
return function.applyfunc(lambda f: self.func(f, self.limits).doit(**hints))

if isinstance(function, FormalPowerSeries):
if any(len(xab) > 1 for xab in self.limits):
return function.integrate(self.limits[0])
else:
return function.integrate(self.limits[0][0])

# There is no trivial answer, so continue

undone_limits = []
Expand Down
4 changes: 2 additions & 2 deletions sympy/series/formal.py
Expand Up @@ -1074,12 +1074,12 @@ def integrate(self, x=None):
Examples
========
>>> from sympy import fps, sin
>>> from sympy import fps, sin, integrate
>>> from sympy.abc import x
>>> f = fps(sin(x))
>>> f.integrate(x).truncate()
-1 + x**2/2 - x**4/24 + O(x**6)
>>> f.integrate((x, 0, 1))
>>> integrate(f, (x, 0, 1))
-cos(1) + 1
"""
from sympy.integrals import integrate
Expand Down
8 changes: 5 additions & 3 deletions sympy/series/tests/test_formal.py
@@ -1,6 +1,7 @@
from sympy import (symbols, factorial, sqrt, Rational, atan, I, log, fps, O,
Sum, oo, S, pi, cos, sin, Function, exp, Derivative, asin,
airyai, acos, acosh, gamma, erf, asech, Add, Integral, Mul)
airyai, acos, acosh, gamma, erf, asech, Add, Integral, Mul,
integrate)
from sympy.series.formal import (rational_algorithm, FormalPowerSeries,
rational_independent, simpleDE, exp_re,
hyper_re)
Expand Down Expand Up @@ -485,11 +486,12 @@ def test_fps__operations():
O(x**6))

assert f1.integrate((x, 0, 1)) == -cos(1) + 1
assert integrate(f1, (x, 0, 1)) == -cos(1) + 1

fi = f1.integrate(x)
fi = integrate(f1, x)
assert fi.function == -cos(x)
assert fi.truncate() == -1 + x**2/2 - x**4/24 + O(x**6)

fi = f2.integrate()
fi = f2.integrate(x)
assert fi.function == sin(x)
assert fi.truncate() == x - x**3/6 + x**5/120 + O(x**6)

0 comments on commit 4009719

Please sign in to comment.