You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1466 lines
42 KiB

5 months ago
from typing import Tuple as tTuple
from sympy.core import S, Add, Mul, sympify, Symbol, Dummy, Basic
from sympy.core.expr import Expr
from sympy.core.exprtools import factor_terms
from sympy.core.function import (Function, Derivative, ArgumentIndexError,
AppliedUndef, expand_mul)
from sympy.core.logic import fuzzy_not, fuzzy_or
from sympy.core.numbers import pi, I, oo
from sympy.core.power import Pow
from sympy.core.relational import Eq
from sympy.functions.elementary.miscellaneous import sqrt
from sympy.functions.elementary.piecewise import Piecewise
###############################################################################
######################### REAL and IMAGINARY PARTS ############################
###############################################################################
class re(Function):
"""
Returns real part of expression. This function performs only
elementary analysis and so it will fail to decompose properly
more complicated expressions. If completely simplified result
is needed then use ``Basic.as_real_imag()`` or perform complex
expansion on instance of this function.
Examples
========
>>> from sympy import re, im, I, E, symbols
>>> x, y = symbols('x y', real=True)
>>> re(2*E)
2*E
>>> re(2*I + 17)
17
>>> re(2*I)
0
>>> re(im(x) + x*I + 2)
2
>>> re(5 + I + 2)
7
Parameters
==========
arg : Expr
Real or complex expression.
Returns
=======
expr : Expr
Real part of expression.
See Also
========
im
"""
args: tTuple[Expr]
is_extended_real = True
unbranched = True # implicitly works on the projection to C
_singularities = True # non-holomorphic
@classmethod
def eval(cls, arg):
if arg is S.NaN:
return S.NaN
elif arg is S.ComplexInfinity:
return S.NaN
elif arg.is_extended_real:
return arg
elif arg.is_imaginary or (I*arg).is_extended_real:
return S.Zero
elif arg.is_Matrix:
return arg.as_real_imag()[0]
elif arg.is_Function and isinstance(arg, conjugate):
return re(arg.args[0])
else:
included, reverted, excluded = [], [], []
args = Add.make_args(arg)
for term in args:
coeff = term.as_coefficient(I)
if coeff is not None:
if not coeff.is_extended_real:
reverted.append(coeff)
elif not term.has(I) and term.is_extended_real:
excluded.append(term)
else:
# Try to do some advanced expansion. If
# impossible, don't try to do re(arg) again
# (because this is what we are trying to do now).
real_imag = term.as_real_imag(ignore=arg)
if real_imag:
excluded.append(real_imag[0])
else:
included.append(term)
if len(args) != len(included):
a, b, c = (Add(*xs) for xs in [included, reverted, excluded])
return cls(a) - im(b) + c
def as_real_imag(self, deep=True, **hints):
"""
Returns the real number with a zero imaginary part.
"""
return (self, S.Zero)
def _eval_derivative(self, x):
if x.is_extended_real or self.args[0].is_extended_real:
return re(Derivative(self.args[0], x, evaluate=True))
if x.is_imaginary or self.args[0].is_imaginary:
return -I \
* im(Derivative(self.args[0], x, evaluate=True))
def _eval_rewrite_as_im(self, arg, **kwargs):
return self.args[0] - I*im(self.args[0])
def _eval_is_algebraic(self):
return self.args[0].is_algebraic
def _eval_is_zero(self):
# is_imaginary implies nonzero
return fuzzy_or([self.args[0].is_imaginary, self.args[0].is_zero])
def _eval_is_finite(self):
if self.args[0].is_finite:
return True
def _eval_is_complex(self):
if self.args[0].is_finite:
return True
class im(Function):
"""
Returns imaginary part of expression. This function performs only
elementary analysis and so it will fail to decompose properly more
complicated expressions. If completely simplified result is needed then
use ``Basic.as_real_imag()`` or perform complex expansion on instance of
this function.
Examples
========
>>> from sympy import re, im, E, I
>>> from sympy.abc import x, y
>>> im(2*E)
0
>>> im(2*I + 17)
2
>>> im(x*I)
re(x)
>>> im(re(x) + y)
im(y)
>>> im(2 + 3*I)
3
Parameters
==========
arg : Expr
Real or complex expression.
Returns
=======
expr : Expr
Imaginary part of expression.
See Also
========
re
"""
args: tTuple[Expr]
is_extended_real = True
unbranched = True # implicitly works on the projection to C
_singularities = True # non-holomorphic
@classmethod
def eval(cls, arg):
if arg is S.NaN:
return S.NaN
elif arg is S.ComplexInfinity:
return S.NaN
elif arg.is_extended_real:
return S.Zero
elif arg.is_imaginary or (I*arg).is_extended_real:
return -I * arg
elif arg.is_Matrix:
return arg.as_real_imag()[1]
elif arg.is_Function and isinstance(arg, conjugate):
return -im(arg.args[0])
else:
included, reverted, excluded = [], [], []
args = Add.make_args(arg)
for term in args:
coeff = term.as_coefficient(I)
if coeff is not None:
if not coeff.is_extended_real:
reverted.append(coeff)
else:
excluded.append(coeff)
elif term.has(I) or not term.is_extended_real:
# Try to do some advanced expansion. If
# impossible, don't try to do im(arg) again
# (because this is what we are trying to do now).
real_imag = term.as_real_imag(ignore=arg)
if real_imag:
excluded.append(real_imag[1])
else:
included.append(term)
if len(args) != len(included):
a, b, c = (Add(*xs) for xs in [included, reverted, excluded])
return cls(a) + re(b) + c
def as_real_imag(self, deep=True, **hints):
"""
Return the imaginary part with a zero real part.
"""
return (self, S.Zero)
def _eval_derivative(self, x):
if x.is_extended_real or self.args[0].is_extended_real:
return im(Derivative(self.args[0], x, evaluate=True))
if x.is_imaginary or self.args[0].is_imaginary:
return -I \
* re(Derivative(self.args[0], x, evaluate=True))
def _eval_rewrite_as_re(self, arg, **kwargs):
return -I*(self.args[0] - re(self.args[0]))
def _eval_is_algebraic(self):
return self.args[0].is_algebraic
def _eval_is_zero(self):
return self.args[0].is_extended_real
def _eval_is_finite(self):
if self.args[0].is_finite:
return True
def _eval_is_complex(self):
if self.args[0].is_finite:
return True
###############################################################################
############### SIGN, ABSOLUTE VALUE, ARGUMENT and CONJUGATION ################
###############################################################################
class sign(Function):
"""
Returns the complex sign of an expression:
Explanation
===========
If the expression is real the sign will be:
* $1$ if expression is positive
* $0$ if expression is equal to zero
* $-1$ if expression is negative
If the expression is imaginary the sign will be:
* $I$ if im(expression) is positive
* $-I$ if im(expression) is negative
Otherwise an unevaluated expression will be returned. When evaluated, the
result (in general) will be ``cos(arg(expr)) + I*sin(arg(expr))``.
Examples
========
>>> from sympy import sign, I
>>> sign(-1)
-1
>>> sign(0)
0
>>> sign(-3*I)
-I
>>> sign(1 + I)
sign(1 + I)
>>> _.evalf()
0.707106781186548 + 0.707106781186548*I
Parameters
==========
arg : Expr
Real or imaginary expression.
Returns
=======
expr : Expr
Complex sign of expression.
See Also
========
Abs, conjugate
"""
is_complex = True
_singularities = True
def doit(self, **hints):
s = super().doit()
if s == self and self.args[0].is_zero is False:
return self.args[0] / Abs(self.args[0])
return s
@classmethod
def eval(cls, arg):
# handle what we can
if arg.is_Mul:
c, args = arg.as_coeff_mul()
unk = []
s = sign(c)
for a in args:
if a.is_extended_negative:
s = -s
elif a.is_extended_positive:
pass
else:
if a.is_imaginary:
ai = im(a)
if ai.is_comparable: # i.e. a = I*real
s *= I
if ai.is_extended_negative:
# can't use sign(ai) here since ai might not be
# a Number
s = -s
else:
unk.append(a)
else:
unk.append(a)
if c is S.One and len(unk) == len(args):
return None
return s * cls(arg._new_rawargs(*unk))
if arg is S.NaN:
return S.NaN
if arg.is_zero: # it may be an Expr that is zero
return S.Zero
if arg.is_extended_positive:
return S.One
if arg.is_extended_negative:
return S.NegativeOne
if arg.is_Function:
if isinstance(arg, sign):
return arg
if arg.is_imaginary:
if arg.is_Pow and arg.exp is S.Half:
# we catch this because non-trivial sqrt args are not expanded
# e.g. sqrt(1-sqrt(2)) --x--> to I*sqrt(sqrt(2) - 1)
return I
arg2 = -I * arg
if arg2.is_extended_positive:
return I
if arg2.is_extended_negative:
return -I
def _eval_Abs(self):
if fuzzy_not(self.args[0].is_zero):
return S.One
def _eval_conjugate(self):
return sign(conjugate(self.args[0]))
def _eval_derivative(self, x):
if self.args[0].is_extended_real:
from sympy.functions.special.delta_functions import DiracDelta
return 2 * Derivative(self.args[0], x, evaluate=True) \
* DiracDelta(self.args[0])
elif self.args[0].is_imaginary:
from sympy.functions.special.delta_functions import DiracDelta
return 2 * Derivative(self.args[0], x, evaluate=True) \
* DiracDelta(-I * self.args[0])
def _eval_is_nonnegative(self):
if self.args[0].is_nonnegative:
return True
def _eval_is_nonpositive(self):
if self.args[0].is_nonpositive:
return True
def _eval_is_imaginary(self):
return self.args[0].is_imaginary
def _eval_is_integer(self):
return self.args[0].is_extended_real
def _eval_is_zero(self):
return self.args[0].is_zero
def _eval_power(self, other):
if (
fuzzy_not(self.args[0].is_zero) and
other.is_integer and
other.is_even
):
return S.One
def _eval_nseries(self, x, n, logx, cdir=0):
arg0 = self.args[0]
x0 = arg0.subs(x, 0)
if x0 != 0:
return self.func(x0)
if cdir != 0:
cdir = arg0.dir(x, cdir)
return -S.One if re(cdir) < 0 else S.One
def _eval_rewrite_as_Piecewise(self, arg, **kwargs):
if arg.is_extended_real:
return Piecewise((1, arg > 0), (-1, arg < 0), (0, True))
def _eval_rewrite_as_Heaviside(self, arg, **kwargs):
from sympy.functions.special.delta_functions import Heaviside
if arg.is_extended_real:
return Heaviside(arg) * 2 - 1
def _eval_rewrite_as_Abs(self, arg, **kwargs):
return Piecewise((0, Eq(arg, 0)), (arg / Abs(arg), True))
def _eval_simplify(self, **kwargs):
return self.func(factor_terms(self.args[0])) # XXX include doit?
class Abs(Function):
"""
Return the absolute value of the argument.
Explanation
===========
This is an extension of the built-in function ``abs()`` to accept symbolic
values. If you pass a SymPy expression to the built-in ``abs()``, it will
pass it automatically to ``Abs()``.
Examples
========
>>> from sympy import Abs, Symbol, S, I
>>> Abs(-1)
1
>>> x = Symbol('x', real=True)
>>> Abs(-x)
Abs(x)
>>> Abs(x**2)
x**2
>>> abs(-x) # The Python built-in
Abs(x)
>>> Abs(3*x + 2*I)
sqrt(9*x**2 + 4)
>>> Abs(8*I)
8
Note that the Python built-in will return either an Expr or int depending on
the argument::
>>> type(abs(-1))
<... 'int'>
>>> type(abs(S.NegativeOne))
<class 'sympy.core.numbers.One'>
Abs will always return a SymPy object.
Parameters
==========
arg : Expr
Real or complex expression.
Returns
=======
expr : Expr
Absolute value returned can be an expression or integer depending on
input arg.
See Also
========
sign, conjugate
"""
args: tTuple[Expr]
is_extended_real = True
is_extended_negative = False
is_extended_nonnegative = True
unbranched = True
_singularities = True # non-holomorphic
def fdiff(self, argindex=1):
"""
Get the first derivative of the argument to Abs().
"""
if argindex == 1:
return sign(self.args[0])
else:
raise ArgumentIndexError(self, argindex)
@classmethod
def eval(cls, arg):
from sympy.simplify.simplify import signsimp
if hasattr(arg, '_eval_Abs'):
obj = arg._eval_Abs()
if obj is not None:
return obj
if not isinstance(arg, Expr):
raise TypeError("Bad argument type for Abs(): %s" % type(arg))
# handle what we can
arg = signsimp(arg, evaluate=False)
n, d = arg.as_numer_denom()
if d.free_symbols and not n.free_symbols:
return cls(n)/cls(d)
if arg.is_Mul:
known = []
unk = []
for t in arg.args:
if t.is_Pow and t.exp.is_integer and t.exp.is_negative:
bnew = cls(t.base)
if isinstance(bnew, cls):
unk.append(t)
else:
known.append(Pow(bnew, t.exp))
else:
tnew = cls(t)
if isinstance(tnew, cls):
unk.append(t)
else:
known.append(tnew)
known = Mul(*known)
unk = cls(Mul(*unk), evaluate=False) if unk else S.One
return known*unk
if arg is S.NaN:
return S.NaN
if arg is S.ComplexInfinity:
return oo
from sympy.functions.elementary.exponential import exp, log
if arg.is_Pow:
base, exponent = arg.as_base_exp()
if base.is_extended_real:
if exponent.is_integer:
if exponent.is_even:
return arg
if base is S.NegativeOne:
return S.One
return Abs(base)**exponent
if base.is_extended_nonnegative:
return base**re(exponent)
if base.is_extended_negative:
return (-base)**re(exponent)*exp(-pi*im(exponent))
return
elif not base.has(Symbol): # complex base
# express base**exponent as exp(exponent*log(base))
a, b = log(base).as_real_imag()
z = a + I*b
return exp(re(exponent*z))
if isinstance(arg, exp):
return exp(re(arg.args[0]))
if isinstance(arg, AppliedUndef):
if arg.is_positive:
return arg
elif arg.is_negative:
return -arg
return
if arg.is_Add and arg.has(oo, S.NegativeInfinity):
if any(a.is_infinite for a in arg.as_real_imag()):
return oo
if arg.is_zero:
return S.Zero
if arg.is_extended_nonnegative:
return arg
if arg.is_extended_nonpositive:
return -arg
if arg.is_imaginary:
arg2 = -I * arg
if arg2.is_extended_nonnegative:
return arg2
if arg.is_extended_real:
return
# reject result if all new conjugates are just wrappers around
# an expression that was already in the arg
conj = signsimp(arg.conjugate(), evaluate=False)
new_conj = conj.atoms(conjugate) - arg.atoms(conjugate)
if new_conj and all(arg.has(i.args[0]) for i in new_conj):
return
if arg != conj and arg != -conj:
ignore = arg.atoms(Abs)
abs_free_arg = arg.xreplace({i: Dummy(real=True) for i in ignore})
unk = [a for a in abs_free_arg.free_symbols if a.is_extended_real is None]
if not unk or not all(conj.has(conjugate(u)) for u in unk):
return sqrt(expand_mul(arg*conj))
def _eval_is_real(self):
if self.args[0].is_finite:
return True
def _eval_is_integer(self):
if self.args[0].is_extended_real:
return self.args[0].is_integer
def _eval_is_extended_nonzero(self):
return fuzzy_not(self._args[0].is_zero)
def _eval_is_zero(self):
return self._args[0].is_zero
def _eval_is_extended_positive(self):
return fuzzy_not(self._args[0].is_zero)
def _eval_is_rational(self):
if self.args[0].is_extended_real:
return self.args[0].is_rational
def _eval_is_even(self):
if self.args[0].is_extended_real:
return self.args[0].is_even
def _eval_is_odd(self):
if self.args[0].is_extended_real:
return self.args[0].is_odd
def _eval_is_algebraic(self):
return self.args[0].is_algebraic
def _eval_power(self, exponent):
if self.args[0].is_extended_real and exponent.is_integer:
if exponent.is_even:
return self.args[0]**exponent
elif exponent is not S.NegativeOne and exponent.is_Integer:
return self.args[0]**(exponent - 1)*self
return
def _eval_nseries(self, x, n, logx, cdir=0):
from sympy.functions.elementary.exponential import log
direction = self.args[0].leadterm(x)[0]
if direction.has(log(x)):
direction = direction.subs(log(x), logx)
s = self.args[0]._eval_nseries(x, n=n, logx=logx)
return (sign(direction)*s).expand()
def _eval_derivative(self, x):
if self.args[0].is_extended_real or self.args[0].is_imaginary:
return Derivative(self.args[0], x, evaluate=True) \
* sign(conjugate(self.args[0]))
rv = (re(self.args[0]) * Derivative(re(self.args[0]), x,
evaluate=True) + im(self.args[0]) * Derivative(im(self.args[0]),
x, evaluate=True)) / Abs(self.args[0])
return rv.rewrite(sign)
def _eval_rewrite_as_Heaviside(self, arg, **kwargs):
# Note this only holds for real arg (since Heaviside is not defined
# for complex arguments).
from sympy.functions.special.delta_functions import Heaviside
if arg.is_extended_real:
return arg*(Heaviside(arg) - Heaviside(-arg))
def _eval_rewrite_as_Piecewise(self, arg, **kwargs):
if arg.is_extended_real:
return Piecewise((arg, arg >= 0), (-arg, True))
elif arg.is_imaginary:
return Piecewise((I*arg, I*arg >= 0), (-I*arg, True))
def _eval_rewrite_as_sign(self, arg, **kwargs):
return arg/sign(arg)
def _eval_rewrite_as_conjugate(self, arg, **kwargs):
return sqrt(arg*conjugate(arg))
class arg(Function):
r"""
Returns the argument (in radians) of a complex number. The argument is
evaluated in consistent convention with ``atan2`` where the branch-cut is
taken along the negative real axis and ``arg(z)`` is in the interval
$(-\pi,\pi]$. For a positive number, the argument is always 0; the
argument of a negative number is $\pi$; and the argument of 0
is undefined and returns ``nan``. So the ``arg`` function will never nest
greater than 3 levels since at the 4th application, the result must be
nan; for a real number, nan is returned on the 3rd application.
Examples
========
>>> from sympy import arg, I, sqrt, Dummy
>>> from sympy.abc import x
>>> arg(2.0)
0
>>> arg(I)
pi/2
>>> arg(sqrt(2) + I*sqrt(2))
pi/4
>>> arg(sqrt(3)/2 + I/2)
pi/6
>>> arg(4 + 3*I)
atan(3/4)
>>> arg(0.8 + 0.6*I)
0.643501108793284
>>> arg(arg(arg(arg(x))))
nan
>>> real = Dummy(real=True)
>>> arg(arg(arg(real)))
nan
Parameters
==========
arg : Expr
Real or complex expression.
Returns
=======
value : Expr
Returns arc tangent of arg measured in radians.
"""
is_extended_real = True
is_real = True
is_finite = True
_singularities = True # non-holomorphic
@classmethod
def eval(cls, arg):
a = arg
for i in range(3):
if isinstance(a, cls):
a = a.args[0]
else:
if i == 2 and a.is_extended_real:
return S.NaN
break
else:
return S.NaN
from sympy.functions.elementary.exponential import exp_polar
if isinstance(arg, exp_polar):
return periodic_argument(arg, oo)
if not arg.is_Atom:
c, arg_ = factor_terms(arg).as_coeff_Mul()
if arg_.is_Mul:
arg_ = Mul(*[a if (sign(a) not in (-1, 1)) else
sign(a) for a in arg_.args])
arg_ = sign(c)*arg_
else:
arg_ = arg
if any(i.is_extended_positive is None for i in arg_.atoms(AppliedUndef)):
return
from sympy.functions.elementary.trigonometric import atan2
x, y = arg_.as_real_imag()
rv = atan2(y, x)
if rv.is_number:
return rv
if arg_ != arg:
return cls(arg_, evaluate=False)
def _eval_derivative(self, t):
x, y = self.args[0].as_real_imag()
return (x * Derivative(y, t, evaluate=True) - y *
Derivative(x, t, evaluate=True)) / (x**2 + y**2)
def _eval_rewrite_as_atan2(self, arg, **kwargs):
from sympy.functions.elementary.trigonometric import atan2
x, y = self.args[0].as_real_imag()
return atan2(y, x)
class conjugate(Function):
"""
Returns the *complex conjugate* [1]_ of an argument.
In mathematics, the complex conjugate of a complex number
is given by changing the sign of the imaginary part.
Thus, the conjugate of the complex number
:math:`a + ib` (where $a$ and $b$ are real numbers) is :math:`a - ib`
Examples
========
>>> from sympy import conjugate, I
>>> conjugate(2)
2
>>> conjugate(I)
-I
>>> conjugate(3 + 2*I)
3 - 2*I
>>> conjugate(5 - I)
5 + I
Parameters
==========
arg : Expr
Real or complex expression.
Returns
=======
arg : Expr
Complex conjugate of arg as real, imaginary or mixed expression.
See Also
========
sign, Abs
References
==========
.. [1] https://en.wikipedia.org/wiki/Complex_conjugation
"""
_singularities = True # non-holomorphic
@classmethod
def eval(cls, arg):
obj = arg._eval_conjugate()
if obj is not None:
return obj
def inverse(self):
return conjugate
def _eval_Abs(self):
return Abs(self.args[0], evaluate=True)
def _eval_adjoint(self):
return transpose(self.args[0])
def _eval_conjugate(self):
return self.args[0]
def _eval_derivative(self, x):
if x.is_real:
return conjugate(Derivative(self.args[0], x, evaluate=True))
elif x.is_imaginary:
return -conjugate(Derivative(self.args[0], x, evaluate=True))
def _eval_transpose(self):
return adjoint(self.args[0])
def _eval_is_algebraic(self):
return self.args[0].is_algebraic
class transpose(Function):
"""
Linear map transposition.
Examples
========
>>> from sympy import transpose, Matrix, MatrixSymbol
>>> A = MatrixSymbol('A', 25, 9)
>>> transpose(A)
A.T
>>> B = MatrixSymbol('B', 9, 22)
>>> transpose(B)
B.T
>>> transpose(A*B)
B.T*A.T
>>> M = Matrix([[4, 5], [2, 1], [90, 12]])
>>> M
Matrix([
[ 4, 5],
[ 2, 1],
[90, 12]])
>>> transpose(M)
Matrix([
[4, 2, 90],
[5, 1, 12]])
Parameters
==========
arg : Matrix
Matrix or matrix expression to take the transpose of.
Returns
=======
value : Matrix
Transpose of arg.
"""
@classmethod
def eval(cls, arg):
obj = arg._eval_transpose()
if obj is not None:
return obj
def _eval_adjoint(self):
return conjugate(self.args[0])
def _eval_conjugate(self):
return adjoint(self.args[0])
def _eval_transpose(self):
return self.args[0]
class adjoint(Function):
"""
Conjugate transpose or Hermite conjugation.
Examples
========
>>> from sympy import adjoint, MatrixSymbol
>>> A = MatrixSymbol('A', 10, 5)
>>> adjoint(A)
Adjoint(A)
Parameters
==========
arg : Matrix
Matrix or matrix expression to take the adjoint of.
Returns
=======
value : Matrix
Represents the conjugate transpose or Hermite
conjugation of arg.
"""
@classmethod
def eval(cls, arg):
obj = arg._eval_adjoint()
if obj is not None:
return obj
obj = arg._eval_transpose()
if obj is not None:
return conjugate(obj)
def _eval_adjoint(self):
return self.args[0]
def _eval_conjugate(self):
return transpose(self.args[0])
def _eval_transpose(self):
return conjugate(self.args[0])
def _latex(self, printer, exp=None, *args):
arg = printer._print(self.args[0])
tex = r'%s^{\dagger}' % arg
if exp:
tex = r'\left(%s\right)^{%s}' % (tex, exp)
return tex
def _pretty(self, printer, *args):
from sympy.printing.pretty.stringpict import prettyForm
pform = printer._print(self.args[0], *args)
if printer._use_unicode:
pform = pform**prettyForm('\N{DAGGER}')
else:
pform = pform**prettyForm('+')
return pform
###############################################################################
############### HANDLING OF POLAR NUMBERS #####################################
###############################################################################
class polar_lift(Function):
"""
Lift argument to the Riemann surface of the logarithm, using the
standard branch.
Examples
========
>>> from sympy import Symbol, polar_lift, I
>>> p = Symbol('p', polar=True)
>>> x = Symbol('x')
>>> polar_lift(4)
4*exp_polar(0)
>>> polar_lift(-4)
4*exp_polar(I*pi)
>>> polar_lift(-I)
exp_polar(-I*pi/2)
>>> polar_lift(I + 2)
polar_lift(2 + I)
>>> polar_lift(4*x)
4*polar_lift(x)
>>> polar_lift(4*p)
4*p
Parameters
==========
arg : Expr
Real or complex expression.
See Also
========
sympy.functions.elementary.exponential.exp_polar
periodic_argument
"""
is_polar = True
is_comparable = False # Cannot be evalf'd.
@classmethod
def eval(cls, arg):
from sympy.functions.elementary.complexes import arg as argument
if arg.is_number:
ar = argument(arg)
# In general we want to affirm that something is known,
# e.g. `not ar.has(argument) and not ar.has(atan)`
# but for now we will just be more restrictive and
# see that it has evaluated to one of the known values.
if ar in (0, pi/2, -pi/2, pi):
from sympy.functions.elementary.exponential import exp_polar
return exp_polar(I*ar)*abs(arg)
if arg.is_Mul:
args = arg.args
else:
args = [arg]
included = []
excluded = []
positive = []
for arg in args:
if arg.is_polar:
included += [arg]
elif arg.is_positive:
positive += [arg]
else:
excluded += [arg]
if len(excluded) < len(args):
if excluded:
return Mul(*(included + positive))*polar_lift(Mul(*excluded))
elif included:
return Mul(*(included + positive))
else:
from sympy.functions.elementary.exponential import exp_polar
return Mul(*positive)*exp_polar(0)
def _eval_evalf(self, prec):
""" Careful! any evalf of polar numbers is flaky """
return self.args[0]._eval_evalf(prec)
def _eval_Abs(self):
return Abs(self.args[0], evaluate=True)
class periodic_argument(Function):
r"""
Represent the argument on a quotient of the Riemann surface of the
logarithm. That is, given a period $P$, always return a value in
$(-P/2, P/2]$, by using $\exp(PI) = 1$.
Examples
========
>>> from sympy import exp_polar, periodic_argument
>>> from sympy import I, pi
>>> periodic_argument(exp_polar(10*I*pi), 2*pi)
0
>>> periodic_argument(exp_polar(5*I*pi), 4*pi)
pi
>>> from sympy import exp_polar, periodic_argument
>>> from sympy import I, pi
>>> periodic_argument(exp_polar(5*I*pi), 2*pi)
pi
>>> periodic_argument(exp_polar(5*I*pi), 3*pi)
-pi
>>> periodic_argument(exp_polar(5*I*pi), pi)
0
Parameters
==========
ar : Expr
A polar number.
period : Expr
The period $P$.
See Also
========
sympy.functions.elementary.exponential.exp_polar
polar_lift : Lift argument to the Riemann surface of the logarithm
principal_branch
"""
@classmethod
def _getunbranched(cls, ar):
from sympy.functions.elementary.exponential import exp_polar, log
if ar.is_Mul:
args = ar.args
else:
args = [ar]
unbranched = 0
for a in args:
if not a.is_polar:
unbranched += arg(a)
elif isinstance(a, exp_polar):
unbranched += a.exp.as_real_imag()[1]
elif a.is_Pow:
re, im = a.exp.as_real_imag()
unbranched += re*unbranched_argument(
a.base) + im*log(abs(a.base))
elif isinstance(a, polar_lift):
unbranched += arg(a.args[0])
else:
return None
return unbranched
@classmethod
def eval(cls, ar, period):
# Our strategy is to evaluate the argument on the Riemann surface of the
# logarithm, and then reduce.
# NOTE evidently this means it is a rather bad idea to use this with
# period != 2*pi and non-polar numbers.
if not period.is_extended_positive:
return None
if period == oo and isinstance(ar, principal_branch):
return periodic_argument(*ar.args)
if isinstance(ar, polar_lift) and period >= 2*pi:
return periodic_argument(ar.args[0], period)
if ar.is_Mul:
newargs = [x for x in ar.args if not x.is_positive]
if len(newargs) != len(ar.args):
return periodic_argument(Mul(*newargs), period)
unbranched = cls._getunbranched(ar)
if unbranched is None:
return None
from sympy.functions.elementary.trigonometric import atan, atan2
if unbranched.has(periodic_argument, atan2, atan):
return None
if period == oo:
return unbranched
if period != oo:
from sympy.functions.elementary.integers import ceiling
n = ceiling(unbranched/period - S.Half)*period
if not n.has(ceiling):
return unbranched - n
def _eval_evalf(self, prec):
z, period = self.args
if period == oo:
unbranched = periodic_argument._getunbranched(z)
if unbranched is None:
return self
return unbranched._eval_evalf(prec)
ub = periodic_argument(z, oo)._eval_evalf(prec)
from sympy.functions.elementary.integers import ceiling
return (ub - ceiling(ub/period - S.Half)*period)._eval_evalf(prec)
def unbranched_argument(arg):
'''
Returns periodic argument of arg with period as infinity.
Examples
========
>>> from sympy import exp_polar, unbranched_argument
>>> from sympy import I, pi
>>> unbranched_argument(exp_polar(15*I*pi))
15*pi
>>> unbranched_argument(exp_polar(7*I*pi))
7*pi
See also
========
periodic_argument
'''
return periodic_argument(arg, oo)
class principal_branch(Function):
"""
Represent a polar number reduced to its principal branch on a quotient
of the Riemann surface of the logarithm.
Explanation
===========
This is a function of two arguments. The first argument is a polar
number `z`, and the second one a positive real number or infinity, `p`.
The result is ``z mod exp_polar(I*p)``.
Examples
========
>>> from sympy import exp_polar, principal_branch, oo, I, pi
>>> from sympy.abc import z
>>> principal_branch(z, oo)
z
>>> principal_branch(exp_polar(2*pi*I)*3, 2*pi)
3*exp_polar(0)
>>> principal_branch(exp_polar(2*pi*I)*3*z, 2*pi)
3*principal_branch(z, 2*pi)
Parameters
==========
x : Expr
A polar number.
period : Expr
Positive real number or infinity.
See Also
========
sympy.functions.elementary.exponential.exp_polar
polar_lift : Lift argument to the Riemann surface of the logarithm
periodic_argument
"""
is_polar = True
is_comparable = False # cannot always be evalf'd
@classmethod
def eval(self, x, period):
from sympy.functions.elementary.exponential import exp_polar
if isinstance(x, polar_lift):
return principal_branch(x.args[0], period)
if period == oo:
return x
ub = periodic_argument(x, oo)
barg = periodic_argument(x, period)
if ub != barg and not ub.has(periodic_argument) \
and not barg.has(periodic_argument):
pl = polar_lift(x)
def mr(expr):
if not isinstance(expr, Symbol):
return polar_lift(expr)
return expr
pl = pl.replace(polar_lift, mr)
# Recompute unbranched argument
ub = periodic_argument(pl, oo)
if not pl.has(polar_lift):
if ub != barg:
res = exp_polar(I*(barg - ub))*pl
else:
res = pl
if not res.is_polar and not res.has(exp_polar):
res *= exp_polar(0)
return res
if not x.free_symbols:
c, m = x, ()
else:
c, m = x.as_coeff_mul(*x.free_symbols)
others = []
for y in m:
if y.is_positive:
c *= y
else:
others += [y]
m = tuple(others)
arg = periodic_argument(c, period)
if arg.has(periodic_argument):
return None
if arg.is_number and (unbranched_argument(c) != arg or
(arg == 0 and m != () and c != 1)):
if arg == 0:
return abs(c)*principal_branch(Mul(*m), period)
return principal_branch(exp_polar(I*arg)*Mul(*m), period)*abs(c)
if arg.is_number and ((abs(arg) < period/2) == True or arg == period/2) \
and m == ():
return exp_polar(arg*I)*abs(c)
def _eval_evalf(self, prec):
z, period = self.args
p = periodic_argument(z, period)._eval_evalf(prec)
if abs(p) > pi or p == -pi:
return self # Cannot evalf for this argument.
from sympy.functions.elementary.exponential import exp
return (abs(z)*exp(I*p))._eval_evalf(prec)
def _polarify(eq, lift, pause=False):
from sympy.integrals.integrals import Integral
if eq.is_polar:
return eq
if eq.is_number and not pause:
return polar_lift(eq)
if isinstance(eq, Symbol) and not pause and lift:
return polar_lift(eq)
elif eq.is_Atom:
return eq
elif eq.is_Add:
r = eq.func(*[_polarify(arg, lift, pause=True) for arg in eq.args])
if lift:
return polar_lift(r)
return r
elif eq.is_Pow and eq.base == S.Exp1:
return eq.func(S.Exp1, _polarify(eq.exp, lift, pause=False))
elif eq.is_Function:
return eq.func(*[_polarify(arg, lift, pause=False) for arg in eq.args])
elif isinstance(eq, Integral):
# Don't lift the integration variable
func = _polarify(eq.function, lift, pause=pause)
limits = []
for limit in eq.args[1:]:
var = _polarify(limit[0], lift=False, pause=pause)
rest = _polarify(limit[1:], lift=lift, pause=pause)
limits.append((var,) + rest)
return Integral(*((func,) + tuple(limits)))
else:
return eq.func(*[_polarify(arg, lift, pause=pause)
if isinstance(arg, Expr) else arg for arg in eq.args])
def polarify(eq, subs=True, lift=False):
"""
Turn all numbers in eq into their polar equivalents (under the standard
choice of argument).
Note that no attempt is made to guess a formal convention of adding
polar numbers, expressions like $1 + x$ will generally not be altered.
Note also that this function does not promote ``exp(x)`` to ``exp_polar(x)``.
If ``subs`` is ``True``, all symbols which are not already polar will be
substituted for polar dummies; in this case the function behaves much
like :func:`~.posify`.
If ``lift`` is ``True``, both addition statements and non-polar symbols are
changed to their ``polar_lift()``ed versions.
Note that ``lift=True`` implies ``subs=False``.
Examples
========
>>> from sympy import polarify, sin, I
>>> from sympy.abc import x, y
>>> expr = (-x)**y
>>> expr.expand()
(-x)**y
>>> polarify(expr)
((_x*exp_polar(I*pi))**_y, {_x: x, _y: y})
>>> polarify(expr)[0].expand()
_x**_y*exp_polar(_y*I*pi)
>>> polarify(x, lift=True)
polar_lift(x)
>>> polarify(x*(1+y), lift=True)
polar_lift(x)*polar_lift(y + 1)
Adds are treated carefully:
>>> polarify(1 + sin((1 + I)*x))
(sin(_x*polar_lift(1 + I)) + 1, {_x: x})
"""
if lift:
subs = False
eq = _polarify(sympify(eq), lift)
if not subs:
return eq
reps = {s: Dummy(s.name, polar=True) for s in eq.free_symbols}
eq = eq.subs(reps)
return eq, {r: s for s, r in reps.items()}
def _unpolarify(eq, exponents_only, pause=False):
if not isinstance(eq, Basic) or eq.is_Atom:
return eq
if not pause:
from sympy.functions.elementary.exponential import exp, exp_polar
if isinstance(eq, exp_polar):
return exp(_unpolarify(eq.exp, exponents_only))
if isinstance(eq, principal_branch) and eq.args[1] == 2*pi:
return _unpolarify(eq.args[0], exponents_only)
if (
eq.is_Add or eq.is_Mul or eq.is_Boolean or
eq.is_Relational and (
eq.rel_op in ('==', '!=') and 0 in eq.args or
eq.rel_op not in ('==', '!='))
):
return eq.func(*[_unpolarify(x, exponents_only) for x in eq.args])
if isinstance(eq, polar_lift):
return _unpolarify(eq.args[0], exponents_only)
if eq.is_Pow:
expo = _unpolarify(eq.exp, exponents_only)
base = _unpolarify(eq.base, exponents_only,
not (expo.is_integer and not pause))
return base**expo
if eq.is_Function and getattr(eq.func, 'unbranched', False):
return eq.func(*[_unpolarify(x, exponents_only, exponents_only)
for x in eq.args])
return eq.func(*[_unpolarify(x, exponents_only, True) for x in eq.args])
def unpolarify(eq, subs=None, exponents_only=False):
"""
If `p` denotes the projection from the Riemann surface of the logarithm to
the complex line, return a simplified version `eq'` of `eq` such that
`p(eq') = p(eq)`.
Also apply the substitution subs in the end. (This is a convenience, since
``unpolarify``, in a certain sense, undoes :func:`polarify`.)
Examples
========
>>> from sympy import unpolarify, polar_lift, sin, I
>>> unpolarify(polar_lift(I + 2))
2 + I
>>> unpolarify(sin(polar_lift(I + 7)))
sin(7 + I)
"""
if isinstance(eq, bool):
return eq
eq = sympify(eq)
if subs is not None:
return unpolarify(eq.subs(subs))
changed = True
pause = False
if exponents_only:
pause = True
while changed:
changed = False
res = _unpolarify(eq, exponents_only, pause)
if res != eq:
changed = True
eq = res
if isinstance(res, bool):
return res
# Finally, replacing Exp(0) by 1 is always correct.
# So is polar_lift(0) -> 0.
from sympy.functions.elementary.exponential import exp_polar
return res.subs({exp_polar(0): 1, polar_lift(0): 0})