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
1466 lines
42 KiB
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})
|