Skip to content

Commit

Permalink
core: change semantics of the dir kwarg of Expr.series()
Browse files Browse the repository at this point in the history
Closes #1236
  • Loading branch information
skirpichev committed Apr 24, 2022
1 parent c0346ee commit 55dffad
Show file tree
Hide file tree
Showing 6 changed files with 62 additions and 52 deletions.
58 changes: 29 additions & 29 deletions diofant/core/expr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2317,7 +2317,7 @@ def is_comparable(self):
# ####### SERIES, LEADING TERM, LIMIT, ORDER METHODS ####### #
##############################################################

def series(self, x=None, x0=0, n=6, dir='+', logx=None):
def series(self, x=None, x0=0, n=6, dir=None, logx=None):
"""Series expansion of "self" around ``x = x0`` yielding either terms of
the series one by one (the lazy series given when n=None), else
all the terms at once when n != None.
Expand Down Expand Up @@ -2346,15 +2346,15 @@ def series(self, x=None, x0=0, n=6, dir='+', logx=None):
>>> [next(term) for i in range(2)]
[1, -x**2/2]
For ``dir=+`` (default) the series is calculated from the right and
for ``dir=-`` the series from the left. For infinite ``x0`` (``oo``
For ``dir=-1`` (default) the series is calculated from the right and
for ``dir=+1`` the series from the left. For infinite ``x0`` (``oo``
or ``-oo``), the ``dir`` argument is determined from the direction
of the infinity (i.e. ``dir="-"`` for ``oo``). For smooth functions
of the infinity (i.e. ``dir=+1`` for ``oo``). For smooth functions
this flag will not alter the results.
>>> abs(x).series(dir='+')
>>> abs(x).series(dir=-1)
x
>>> abs(x).series(dir='-')
>>> abs(x).series(dir=+1)
-x
For rational expressions this method may return original expression.
Expand All @@ -2365,6 +2365,7 @@ def series(self, x=None, x0=0, n=6, dir='+', logx=None):
"""
from .function import expand_mul
from .symbol import Dummy, Symbol
from ..functions import sign
from ..series import Order
from ..simplify import collect

Expand All @@ -2385,31 +2386,30 @@ def series(self, x=None, x0=0, n=6, dir='+', logx=None):
else:
return self

if len(dir) != 1 or dir not in '+-':
raise ValueError("Dir must be '+' or '-'")

if x0 == oo:
return self.aseries(x, n)
elif x0 == -oo:
return self.subs({x: -x}).aseries(x, n).subs({x: -x})

# use rep to shift origin to x0 and change sign (if dir is negative)
# and undo the process with rep2
if x0 or dir == '-':
if dir == '-':
rep = -x + x0
rep2 = -x
rep2b = x0
else:
rep = x + x0
rep2 = x
rep2b = -x0
s = self.subs({x: rep}).series(x, x0=0, n=n, dir='+', logx=logx)
x0 = sympify(x0)

if x0.is_infinite:
dir = sign(x0).simplify()
elif dir is None:
dir = Rational(-1)
else:
dir = sympify(dir)
dir = dir/abs(dir)

if not (isinstance(dir, Expr) and dir.is_nonzero):
raise ValueError('Direction must be a nonzero Expr, got {dir}')

if x0.is_infinite:
return self.subs({x: dir*x}).aseries(x, n).subs({x: x/dir})

# use rep to shift origin to x0 and change dir to 1, then undo
if x0 or dir != -1:
s = self.subs({x: x0 - dir*x}).series(x, x0=0, n=n, dir=-1, logx=logx)
if n is None: # lseries...
return (si.subs({x: rep2 + rep2b}) for si in s) # pragma: no branch
return s.subs({x: rep2 + rep2b})
return (si.subs({x: x0/dir - x/dir}) for si in s) # pragma: no branch
return s.subs({x: x0/dir - x/dir})

# from here on it's x0=0 and dir='+' handling
# from here on it's x0=0 and dir=1 handling

if x.is_positive is x.is_negative is None or x.is_Symbol is not True:
# replace x with an x that has a positive assumption
Expand Down
2 changes: 1 addition & 1 deletion diofant/series/series.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from ..core.sympify import sympify


def series(expr, x=None, x0=0, n=6, dir='+'):
def series(expr, x=None, x0=0, n=6, dir=None):
"""Series expansion of ``expr`` in ``x`` around point ``x0``.
See Also
Expand Down
8 changes: 4 additions & 4 deletions diofant/tests/functions/test_trigonometric.py
Original file line number Diff line number Diff line change
Expand Up @@ -1050,10 +1050,10 @@ def test_aseries():
def t(n, v, d, e):
assert abs(n(1/v).evalf(strict=False) -
n(1/x).series(x, dir=d).removeO().subs({x: v})) < e
t(atan, 0.1, '+', 1e-5)
t(atan, -0.1, '-', 1e-5)
t(acot, 0.1, '+', 1e-5)
t(acot, -0.1, '-', 1e-5)
t(atan, 0.1, -1, 1e-5)
t(atan, -0.1, +1, 1e-5)
t(acot, 0.1, -1, 1e-5)
t(acot, -0.1, +1, 1e-5)
pytest.raises(PoleError, lambda: atan(y/x).series(x, n=1))
pytest.raises(PoleError, lambda: acot(y/x).series(x, n=1))

Expand Down
8 changes: 6 additions & 2 deletions diofant/tests/series/test_aseries.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pytest

from diofant import (O, PoleError, Rational, Symbol, cos, exp, log, oo, sin,
sqrt, symbols)
from diofant import (I, O, PoleError, Rational, Symbol, cos, exp, log, oo, pi,
sin, sqrt, symbols)
from diofant.abc import x


Expand Down Expand Up @@ -72,3 +72,7 @@ def test_issue_1231():
log(x) + O(x**(-6), (x, oo)))
assert e.series(x, -oo) == (3/(32*x**4) - 1/(4*x**2) - log(2) -
log(-x) + O(x**(-6), (x, -oo)))
assert e.series(x, x0=+I*oo) == (-3/(32*x**4) + 1/(4*x**2) + I*pi/2 +
log(2) + log(-I*x) + O(x**(-6), (x, oo*I)))
assert e.series(x, x0=-I*oo) == (+3/(32*x**4) - 1/(4*x**2) - I*pi/2 -
log(2) - log(+I*x) + O(x**(-6), (x, -oo*I)))
22 changes: 14 additions & 8 deletions diofant/tests/series/test_nseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from diofant import (Derivative, E, I, O, PoleError, Rational, Symbol, acosh,
acoth, asin, asinh, atanh, besselk, cbrt, ceiling, cos,
cosh, cot, coth, exp, floor, limit, ln, log, pi, sign,
cosh, cot, coth, exp, floor, limit, ln, log, oo, pi, sign,
sin, sinh, sqrt, tan, tanh)
from diofant.abc import a, b, l, w, x, y, z

Expand Down Expand Up @@ -110,6 +110,12 @@ def test_log3():
assert e.series(x, n=4, logx=l) == 1/(-l + log(-1))


def test_log4():
assert (log(1 + x).series(x, x0=I*oo) ==
1/(5*x**5) - 1/(4*x**4) + 1/(3*x**3) - 1/(2*x**2) + 1/x +
I*pi/2 + log(-I*x) + O(x**(-6), (x, oo*I)))


def test_x0():
# issue sympy/sympy#5654
assert ((1/(x**2 + a**2)**2).series(x, x0=I*a, n=0) ==
Expand Down Expand Up @@ -457,13 +463,13 @@ def test_abs():


def test_dir():
assert abs(x).series(x, 0, dir='+') == x
assert abs(x).series(x, 0, dir='-') == -x
assert floor(x + 2).series(x, 0, dir='+') == 2
assert floor(x + 2).series(x, 0, dir='-') == 1
assert floor(x + 2.2).series(x, 0, dir='-') == 2
assert ceiling(x + 2.2).series(x, 0, dir='-') == 3
assert sin(x + y).series(x, 0, dir='-') == sin(x + y).series(x, 0, dir='+')
assert abs(x).series(x, 0, dir=-1) == x
assert abs(x).series(x, 0, dir=+1) == -x
assert floor(x + 2).series(x, 0, dir=-1) == 2
assert floor(x + 2).series(x, 0, dir=+1) == 1
assert floor(x + 2.2).series(x, 0, dir=+1) == 2
assert ceiling(x + 2.2).series(x, 0, dir=+1) == 3
assert sin(x + y).series(x, 0, dir=+1) == sin(x + y).series(x, 0, dir=-1)


def test_sympyissue_3504():
Expand Down
16 changes: 8 additions & 8 deletions diofant/tests/series/test_series.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ def test_simple():
assert (cos(x).series(x, 1) -
cos(x + 1).series(x).subs({x: x - 1})).removeO() == 0

assert abs(x).series(x, oo, n=5, dir='+') == x
assert abs(x).series(x, -oo, n=5, dir='-') == -x
assert abs(-x).series(x, oo, n=5, dir='+') == x
assert abs(-x).series(x, -oo, n=5, dir='-') == -x
assert abs(x).series(x, oo, n=5, dir=-1) == x
assert abs(x).series(x, -oo, n=5, dir=+1) == -x
assert abs(-x).series(x, oo, n=5, dir=-1) == x
assert abs(-x).series(x, -oo, n=5, dir=+1) == -x

# issue sympy/sympy#7203
assert series(cos(x), x, pi, 3) == -1 + (x - pi)**2/2 + O((x - pi)**3,
Expand All @@ -73,11 +73,11 @@ def test_sympyissue_5223():

e = cos(x).series(x, 1, n=None)
assert [next(e) for i in range(2)] == [cos(1), -((x - 1)*sin(1))]
e = cos(x).series(x, 1, n=None, dir='-')
e = cos(x).series(x, 1, n=None, dir=+1)
assert [next(e) for i in range(2)] == [cos(1), (1 - x)*sin(1)]
# the following test is exact so no need for x -> x - 1 replacement
assert abs(x).series(x, 1, dir='-') == x
assert exp(x).series(x, 1, dir='-', n=3).removeO() == \
assert abs(x).series(x, 1, dir=+1) == x
assert exp(x).series(x, 1, dir=+1, n=3).removeO() == \
E - E*(-x + 1) + E*(-x + 1)**2/2

assert next(Derivative(cos(x), x).series(n=None)) == Derivative(1, x)
Expand Down Expand Up @@ -106,7 +106,7 @@ def test_sympyissue_5223():


def test_sympyissue_3978():
assert f(x).series(x, 0, 3, dir='-') == \
assert f(x).series(x, 0, 3, dir=+1) == \
f(0) + x*Subs(Derivative(f(x), x), (x, 0)) + \
x**2*Subs(Derivative(f(x), x, x), (x, 0))/2 + O(x**3)
assert f(x).series(x, 0, 3) == \
Expand Down

0 comments on commit 55dffad

Please sign in to comment.