Spaces:
Sleeping
Sleeping
from itertools import product | |
from typing import Tuple as tTuple | |
from sympy.core.add import Add | |
from sympy.core.cache import cacheit | |
from sympy.core.expr import Expr | |
from sympy.core.function import (Function, ArgumentIndexError, expand_log, | |
expand_mul, FunctionClass, PoleError, expand_multinomial, expand_complex) | |
from sympy.core.logic import fuzzy_and, fuzzy_not, fuzzy_or | |
from sympy.core.mul import Mul | |
from sympy.core.numbers import Integer, Rational, pi, I | |
from sympy.core.parameters import global_parameters | |
from sympy.core.power import Pow | |
from sympy.core.relational import Ge | |
from sympy.core.singleton import S | |
from sympy.core.symbol import Wild, Dummy | |
from sympy.core.sympify import sympify | |
from sympy.functions.combinatorial.factorials import factorial | |
from sympy.functions.elementary.complexes import arg, unpolarify, im, re, Abs | |
from sympy.functions.elementary.miscellaneous import sqrt | |
from sympy.ntheory import multiplicity, perfect_power | |
from sympy.ntheory.factor_ import factorint | |
# NOTE IMPORTANT | |
# The series expansion code in this file is an important part of the gruntz | |
# algorithm for determining limits. _eval_nseries has to return a generalized | |
# power series with coefficients in C(log(x), log). | |
# In more detail, the result of _eval_nseries(self, x, n) must be | |
# c_0*x**e_0 + ... (finitely many terms) | |
# where e_i are numbers (not necessarily integers) and c_i involve only | |
# numbers, the function log, and log(x). [This also means it must not contain | |
# log(x(1+p)), this *has* to be expanded to log(x)+log(1+p) if x.is_positive and | |
# p.is_positive.] | |
class ExpBase(Function): | |
unbranched = True | |
_singularities = (S.ComplexInfinity,) | |
def kind(self): | |
return self.exp.kind | |
def inverse(self, argindex=1): | |
""" | |
Returns the inverse function of ``exp(x)``. | |
""" | |
return log | |
def as_numer_denom(self): | |
""" | |
Returns this with a positive exponent as a 2-tuple (a fraction). | |
Examples | |
======== | |
>>> from sympy import exp | |
>>> from sympy.abc import x | |
>>> exp(-x).as_numer_denom() | |
(1, exp(x)) | |
>>> exp(x).as_numer_denom() | |
(exp(x), 1) | |
""" | |
# this should be the same as Pow.as_numer_denom wrt | |
# exponent handling | |
if not self.is_commutative: | |
return self, S.One | |
exp = self.exp | |
neg_exp = exp.is_negative | |
if not neg_exp and not (-exp).is_negative: | |
neg_exp = exp.could_extract_minus_sign() | |
if neg_exp: | |
return S.One, self.func(-exp) | |
return self, S.One | |
def exp(self): | |
""" | |
Returns the exponent of the function. | |
""" | |
return self.args[0] | |
def as_base_exp(self): | |
""" | |
Returns the 2-tuple (base, exponent). | |
""" | |
return self.func(1), Mul(*self.args) | |
def _eval_adjoint(self): | |
return self.func(self.exp.adjoint()) | |
def _eval_conjugate(self): | |
return self.func(self.exp.conjugate()) | |
def _eval_transpose(self): | |
return self.func(self.exp.transpose()) | |
def _eval_is_finite(self): | |
arg = self.exp | |
if arg.is_infinite: | |
if arg.is_extended_negative: | |
return True | |
if arg.is_extended_positive: | |
return False | |
if arg.is_finite: | |
return True | |
def _eval_is_rational(self): | |
s = self.func(*self.args) | |
if s.func == self.func: | |
z = s.exp.is_zero | |
if z: | |
return True | |
elif s.exp.is_rational and fuzzy_not(z): | |
return False | |
else: | |
return s.is_rational | |
def _eval_is_zero(self): | |
return self.exp is S.NegativeInfinity | |
def _eval_power(self, other): | |
"""exp(arg)**e -> exp(arg*e) if assumptions allow it. | |
""" | |
b, e = self.as_base_exp() | |
return Pow._eval_power(Pow(b, e, evaluate=False), other) | |
def _eval_expand_power_exp(self, **hints): | |
from sympy.concrete.products import Product | |
from sympy.concrete.summations import Sum | |
arg = self.args[0] | |
if arg.is_Add and arg.is_commutative: | |
return Mul.fromiter(self.func(x) for x in arg.args) | |
elif isinstance(arg, Sum) and arg.is_commutative: | |
return Product(self.func(arg.function), *arg.limits) | |
return self.func(arg) | |
class exp_polar(ExpBase): | |
r""" | |
Represent a *polar number* (see g-function Sphinx documentation). | |
Explanation | |
=========== | |
``exp_polar`` represents the function | |
`Exp: \mathbb{C} \rightarrow \mathcal{S}`, sending the complex number | |
`z = a + bi` to the polar number `r = exp(a), \theta = b`. It is one of | |
the main functions to construct polar numbers. | |
Examples | |
======== | |
>>> from sympy import exp_polar, pi, I, exp | |
The main difference is that polar numbers do not "wrap around" at `2 \pi`: | |
>>> exp(2*pi*I) | |
1 | |
>>> exp_polar(2*pi*I) | |
exp_polar(2*I*pi) | |
apart from that they behave mostly like classical complex numbers: | |
>>> exp_polar(2)*exp_polar(3) | |
exp_polar(5) | |
See Also | |
======== | |
sympy.simplify.powsimp.powsimp | |
polar_lift | |
periodic_argument | |
principal_branch | |
""" | |
is_polar = True | |
is_comparable = False # cannot be evalf'd | |
def _eval_Abs(self): # Abs is never a polar number | |
return exp(re(self.args[0])) | |
def _eval_evalf(self, prec): | |
""" Careful! any evalf of polar numbers is flaky """ | |
i = im(self.args[0]) | |
try: | |
bad = (i <= -pi or i > pi) | |
except TypeError: | |
bad = True | |
if bad: | |
return self # cannot evalf for this argument | |
res = exp(self.args[0])._eval_evalf(prec) | |
if i > 0 and im(res) < 0: | |
# i ~ pi, but exp(I*i) evaluated to argument slightly bigger than pi | |
return re(res) | |
return res | |
def _eval_power(self, other): | |
return self.func(self.args[0]*other) | |
def _eval_is_extended_real(self): | |
if self.args[0].is_extended_real: | |
return True | |
def as_base_exp(self): | |
# XXX exp_polar(0) is special! | |
if self.args[0] == 0: | |
return self, S.One | |
return ExpBase.as_base_exp(self) | |
class ExpMeta(FunctionClass): | |
def __instancecheck__(cls, instance): | |
if exp in instance.__class__.__mro__: | |
return True | |
return isinstance(instance, Pow) and instance.base is S.Exp1 | |
class exp(ExpBase, metaclass=ExpMeta): | |
""" | |
The exponential function, :math:`e^x`. | |
Examples | |
======== | |
>>> from sympy import exp, I, pi | |
>>> from sympy.abc import x | |
>>> exp(x) | |
exp(x) | |
>>> exp(x).diff(x) | |
exp(x) | |
>>> exp(I*pi) | |
-1 | |
Parameters | |
========== | |
arg : Expr | |
See Also | |
======== | |
log | |
""" | |
def fdiff(self, argindex=1): | |
""" | |
Returns the first derivative of this function. | |
""" | |
if argindex == 1: | |
return self | |
else: | |
raise ArgumentIndexError(self, argindex) | |
def _eval_refine(self, assumptions): | |
from sympy.assumptions import ask, Q | |
arg = self.args[0] | |
if arg.is_Mul: | |
Ioo = I*S.Infinity | |
if arg in [Ioo, -Ioo]: | |
return S.NaN | |
coeff = arg.as_coefficient(pi*I) | |
if coeff: | |
if ask(Q.integer(2*coeff)): | |
if ask(Q.even(coeff)): | |
return S.One | |
elif ask(Q.odd(coeff)): | |
return S.NegativeOne | |
elif ask(Q.even(coeff + S.Half)): | |
return -I | |
elif ask(Q.odd(coeff + S.Half)): | |
return I | |
def eval(cls, arg): | |
from sympy.calculus import AccumBounds | |
from sympy.matrices.matrixbase import MatrixBase | |
from sympy.sets.setexpr import SetExpr | |
from sympy.simplify.simplify import logcombine | |
if isinstance(arg, MatrixBase): | |
return arg.exp() | |
elif global_parameters.exp_is_pow: | |
return Pow(S.Exp1, arg) | |
elif arg.is_Number: | |
if arg is S.NaN: | |
return S.NaN | |
elif arg.is_zero: | |
return S.One | |
elif arg is S.One: | |
return S.Exp1 | |
elif arg is S.Infinity: | |
return S.Infinity | |
elif arg is S.NegativeInfinity: | |
return S.Zero | |
elif arg is S.ComplexInfinity: | |
return S.NaN | |
elif isinstance(arg, log): | |
return arg.args[0] | |
elif isinstance(arg, AccumBounds): | |
return AccumBounds(exp(arg.min), exp(arg.max)) | |
elif isinstance(arg, SetExpr): | |
return arg._eval_func(cls) | |
elif arg.is_Mul: | |
coeff = arg.as_coefficient(pi*I) | |
if coeff: | |
if (2*coeff).is_integer: | |
if coeff.is_even: | |
return S.One | |
elif coeff.is_odd: | |
return S.NegativeOne | |
elif (coeff + S.Half).is_even: | |
return -I | |
elif (coeff + S.Half).is_odd: | |
return I | |
elif coeff.is_Rational: | |
ncoeff = coeff % 2 # restrict to [0, 2pi) | |
if ncoeff > 1: # restrict to (-pi, pi] | |
ncoeff -= 2 | |
if ncoeff != coeff: | |
return cls(ncoeff*pi*I) | |
# Warning: code in risch.py will be very sensitive to changes | |
# in this (see DifferentialExtension). | |
# look for a single log factor | |
coeff, terms = arg.as_coeff_Mul() | |
# but it can't be multiplied by oo | |
if coeff in [S.NegativeInfinity, S.Infinity]: | |
if terms.is_number: | |
if coeff is S.NegativeInfinity: | |
terms = -terms | |
if re(terms).is_zero and terms is not S.Zero: | |
return S.NaN | |
if re(terms).is_positive and im(terms) is not S.Zero: | |
return S.ComplexInfinity | |
if re(terms).is_negative: | |
return S.Zero | |
return None | |
coeffs, log_term = [coeff], None | |
for term in Mul.make_args(terms): | |
term_ = logcombine(term) | |
if isinstance(term_, log): | |
if log_term is None: | |
log_term = term_.args[0] | |
else: | |
return None | |
elif term.is_comparable: | |
coeffs.append(term) | |
else: | |
return None | |
return log_term**Mul(*coeffs) if log_term else None | |
elif arg.is_Add: | |
out = [] | |
add = [] | |
argchanged = False | |
for a in arg.args: | |
if a is S.One: | |
add.append(a) | |
continue | |
newa = cls(a) | |
if isinstance(newa, cls): | |
if newa.args[0] != a: | |
add.append(newa.args[0]) | |
argchanged = True | |
else: | |
add.append(a) | |
else: | |
out.append(newa) | |
if out or argchanged: | |
return Mul(*out)*cls(Add(*add), evaluate=False) | |
if arg.is_zero: | |
return S.One | |
def base(self): | |
""" | |
Returns the base of the exponential function. | |
""" | |
return S.Exp1 | |
def taylor_term(n, x, *previous_terms): | |
""" | |
Calculates the next term in the Taylor series expansion. | |
""" | |
if n < 0: | |
return S.Zero | |
if n == 0: | |
return S.One | |
x = sympify(x) | |
if previous_terms: | |
p = previous_terms[-1] | |
if p is not None: | |
return p * x / n | |
return x**n/factorial(n) | |
def as_real_imag(self, deep=True, **hints): | |
""" | |
Returns this function as a 2-tuple representing a complex number. | |
Examples | |
======== | |
>>> from sympy import exp, I | |
>>> from sympy.abc import x | |
>>> exp(x).as_real_imag() | |
(exp(re(x))*cos(im(x)), exp(re(x))*sin(im(x))) | |
>>> exp(1).as_real_imag() | |
(E, 0) | |
>>> exp(I).as_real_imag() | |
(cos(1), sin(1)) | |
>>> exp(1+I).as_real_imag() | |
(E*cos(1), E*sin(1)) | |
See Also | |
======== | |
sympy.functions.elementary.complexes.re | |
sympy.functions.elementary.complexes.im | |
""" | |
from sympy.functions.elementary.trigonometric import cos, sin | |
re, im = self.args[0].as_real_imag() | |
if deep: | |
re = re.expand(deep, **hints) | |
im = im.expand(deep, **hints) | |
cos, sin = cos(im), sin(im) | |
return (exp(re)*cos, exp(re)*sin) | |
def _eval_subs(self, old, new): | |
# keep processing of power-like args centralized in Pow | |
if old.is_Pow: # handle (exp(3*log(x))).subs(x**2, z) -> z**(3/2) | |
old = exp(old.exp*log(old.base)) | |
elif old is S.Exp1 and new.is_Function: | |
old = exp | |
if isinstance(old, exp) or old is S.Exp1: | |
f = lambda a: Pow(*a.as_base_exp(), evaluate=False) if ( | |
a.is_Pow or isinstance(a, exp)) else a | |
return Pow._eval_subs(f(self), f(old), new) | |
if old is exp and not new.is_Function: | |
return new**self.exp._subs(old, new) | |
return Function._eval_subs(self, old, new) | |
def _eval_is_extended_real(self): | |
if self.args[0].is_extended_real: | |
return True | |
elif self.args[0].is_imaginary: | |
arg2 = -S(2) * I * self.args[0] / pi | |
return arg2.is_even | |
def _eval_is_complex(self): | |
def complex_extended_negative(arg): | |
yield arg.is_complex | |
yield arg.is_extended_negative | |
return fuzzy_or(complex_extended_negative(self.args[0])) | |
def _eval_is_algebraic(self): | |
if (self.exp / pi / I).is_rational: | |
return True | |
if fuzzy_not(self.exp.is_zero): | |
if self.exp.is_algebraic: | |
return False | |
elif (self.exp / pi).is_rational: | |
return False | |
def _eval_is_extended_positive(self): | |
if self.exp.is_extended_real: | |
return self.args[0] is not S.NegativeInfinity | |
elif self.exp.is_imaginary: | |
arg2 = -I * self.args[0] / pi | |
return arg2.is_even | |
def _eval_nseries(self, x, n, logx, cdir=0): | |
# NOTE Please see the comment at the beginning of this file, labelled | |
# IMPORTANT. | |
from sympy.functions.elementary.complexes import sign | |
from sympy.functions.elementary.integers import ceiling | |
from sympy.series.limits import limit | |
from sympy.series.order import Order | |
from sympy.simplify.powsimp import powsimp | |
arg = self.exp | |
arg_series = arg._eval_nseries(x, n=n, logx=logx) | |
if arg_series.is_Order: | |
return 1 + arg_series | |
arg0 = limit(arg_series.removeO(), x, 0) | |
if arg0 is S.NegativeInfinity: | |
return Order(x**n, x) | |
if arg0 is S.Infinity: | |
return self | |
if arg0.is_infinite: | |
raise PoleError("Cannot expand %s around 0" % (self)) | |
# checking for indecisiveness/ sign terms in arg0 | |
if any(isinstance(arg, sign) for arg in arg0.args): | |
return self | |
t = Dummy("t") | |
nterms = n | |
try: | |
cf = Order(arg.as_leading_term(x, logx=logx), x).getn() | |
except (NotImplementedError, PoleError): | |
cf = 0 | |
if cf and cf > 0: | |
nterms = ceiling(n/cf) | |
exp_series = exp(t)._taylor(t, nterms) | |
r = exp(arg0)*exp_series.subs(t, arg_series - arg0) | |
rep = {logx: log(x)} if logx is not None else {} | |
if r.subs(rep) == self: | |
return r | |
if cf and cf > 1: | |
r += Order((arg_series - arg0)**n, x)/x**((cf-1)*n) | |
else: | |
r += Order((arg_series - arg0)**n, x) | |
r = r.expand() | |
r = powsimp(r, deep=True, combine='exp') | |
# powsimp may introduce unexpanded (-1)**Rational; see PR #17201 | |
simplerat = lambda x: x.is_Rational and x.q in [3, 4, 6] | |
w = Wild('w', properties=[simplerat]) | |
r = r.replace(S.NegativeOne**w, expand_complex(S.NegativeOne**w)) | |
return r | |
def _taylor(self, x, n): | |
l = [] | |
g = None | |
for i in range(n): | |
g = self.taylor_term(i, self.args[0], g) | |
g = g.nseries(x, n=n) | |
l.append(g.removeO()) | |
return Add(*l) | |
def _eval_as_leading_term(self, x, logx=None, cdir=0): | |
from sympy.calculus.util import AccumBounds | |
arg = self.args[0].cancel().as_leading_term(x, logx=logx) | |
arg0 = arg.subs(x, 0) | |
if arg is S.NaN: | |
return S.NaN | |
if isinstance(arg0, AccumBounds): | |
# This check addresses a corner case involving AccumBounds. | |
# if isinstance(arg, AccumBounds) is True, then arg0 can either be 0, | |
# AccumBounds(-oo, 0) or AccumBounds(-oo, oo). | |
# Check out function: test_issue_18473() in test_exponential.py and | |
# test_limits.py for more information. | |
if re(cdir) < S.Zero: | |
return exp(-arg0) | |
return exp(arg0) | |
if arg0 is S.NaN: | |
arg0 = arg.limit(x, 0) | |
if arg0.is_infinite is False: | |
return exp(arg0) | |
raise PoleError("Cannot expand %s around 0" % (self)) | |
def _eval_rewrite_as_sin(self, arg, **kwargs): | |
from sympy.functions.elementary.trigonometric import sin | |
return sin(I*arg + pi/2) - I*sin(I*arg) | |
def _eval_rewrite_as_cos(self, arg, **kwargs): | |
from sympy.functions.elementary.trigonometric import cos | |
return cos(I*arg) + I*cos(I*arg + pi/2) | |
def _eval_rewrite_as_tanh(self, arg, **kwargs): | |
from sympy.functions.elementary.hyperbolic import tanh | |
return (1 + tanh(arg/2))/(1 - tanh(arg/2)) | |
def _eval_rewrite_as_sqrt(self, arg, **kwargs): | |
from sympy.functions.elementary.trigonometric import sin, cos | |
if arg.is_Mul: | |
coeff = arg.coeff(pi*I) | |
if coeff and coeff.is_number: | |
cosine, sine = cos(pi*coeff), sin(pi*coeff) | |
if not isinstance(cosine, cos) and not isinstance (sine, sin): | |
return cosine + I*sine | |
def _eval_rewrite_as_Pow(self, arg, **kwargs): | |
if arg.is_Mul: | |
logs = [a for a in arg.args if isinstance(a, log) and len(a.args) == 1] | |
if logs: | |
return Pow(logs[0].args[0], arg.coeff(logs[0])) | |
def match_real_imag(expr): | |
r""" | |
Try to match expr with $a + Ib$ for real $a$ and $b$. | |
``match_real_imag`` returns a tuple containing the real and imaginary | |
parts of expr or ``(None, None)`` if direct matching is not possible. Contrary | |
to :func:`~.re()`, :func:`~.im()``, and ``as_real_imag()``, this helper will not force things | |
by returning expressions themselves containing ``re()`` or ``im()`` and it | |
does not expand its argument either. | |
""" | |
r_, i_ = expr.as_independent(I, as_Add=True) | |
if i_ == 0 and r_.is_real: | |
return (r_, i_) | |
i_ = i_.as_coefficient(I) | |
if i_ and i_.is_real and r_.is_real: | |
return (r_, i_) | |
else: | |
return (None, None) # simpler to check for than None | |
class log(Function): | |
r""" | |
The natural logarithm function `\ln(x)` or `\log(x)`. | |
Explanation | |
=========== | |
Logarithms are taken with the natural base, `e`. To get | |
a logarithm of a different base ``b``, use ``log(x, b)``, | |
which is essentially short-hand for ``log(x)/log(b)``. | |
``log`` represents the principal branch of the natural | |
logarithm. As such it has a branch cut along the negative | |
real axis and returns values having a complex argument in | |
`(-\pi, \pi]`. | |
Examples | |
======== | |
>>> from sympy import log, sqrt, S, I | |
>>> log(8, 2) | |
3 | |
>>> log(S(8)/3, 2) | |
-log(3)/log(2) + 3 | |
>>> log(-1 + I*sqrt(3)) | |
log(2) + 2*I*pi/3 | |
See Also | |
======== | |
exp | |
""" | |
args: tTuple[Expr] | |
_singularities = (S.Zero, S.ComplexInfinity) | |
def fdiff(self, argindex=1): | |
""" | |
Returns the first derivative of the function. | |
""" | |
if argindex == 1: | |
return 1/self.args[0] | |
else: | |
raise ArgumentIndexError(self, argindex) | |
def inverse(self, argindex=1): | |
r""" | |
Returns `e^x`, the inverse function of `\log(x)`. | |
""" | |
return exp | |
def eval(cls, arg, base=None): | |
from sympy.calculus import AccumBounds | |
from sympy.sets.setexpr import SetExpr | |
arg = sympify(arg) | |
if base is not None: | |
base = sympify(base) | |
if base == 1: | |
if arg == 1: | |
return S.NaN | |
else: | |
return S.ComplexInfinity | |
try: | |
# handle extraction of powers of the base now | |
# or else expand_log in Mul would have to handle this | |
n = multiplicity(base, arg) | |
if n: | |
return n + log(arg / base**n) / log(base) | |
else: | |
return log(arg)/log(base) | |
except ValueError: | |
pass | |
if base is not S.Exp1: | |
return cls(arg)/cls(base) | |
else: | |
return cls(arg) | |
if arg.is_Number: | |
if arg.is_zero: | |
return S.ComplexInfinity | |
elif arg is S.One: | |
return S.Zero | |
elif arg is S.Infinity: | |
return S.Infinity | |
elif arg is S.NegativeInfinity: | |
return S.Infinity | |
elif arg is S.NaN: | |
return S.NaN | |
elif arg.is_Rational and arg.p == 1: | |
return -cls(arg.q) | |
if arg.is_Pow and arg.base is S.Exp1 and arg.exp.is_extended_real: | |
return arg.exp | |
if isinstance(arg, exp) and arg.exp.is_extended_real: | |
return arg.exp | |
elif isinstance(arg, exp) and arg.exp.is_number: | |
r_, i_ = match_real_imag(arg.exp) | |
if i_ and i_.is_comparable: | |
i_ %= 2*pi | |
if i_ > pi: | |
i_ -= 2*pi | |
return r_ + expand_mul(i_ * I, deep=False) | |
elif isinstance(arg, exp_polar): | |
return unpolarify(arg.exp) | |
elif isinstance(arg, AccumBounds): | |
if arg.min.is_positive: | |
return AccumBounds(log(arg.min), log(arg.max)) | |
elif arg.min.is_zero: | |
return AccumBounds(S.NegativeInfinity, log(arg.max)) | |
else: | |
return S.NaN | |
elif isinstance(arg, SetExpr): | |
return arg._eval_func(cls) | |
if arg.is_number: | |
if arg.is_negative: | |
return pi * I + cls(-arg) | |
elif arg is S.ComplexInfinity: | |
return S.ComplexInfinity | |
elif arg is S.Exp1: | |
return S.One | |
if arg.is_zero: | |
return S.ComplexInfinity | |
# don't autoexpand Pow or Mul (see the issue 3351): | |
if not arg.is_Add: | |
coeff = arg.as_coefficient(I) | |
if coeff is not None: | |
if coeff is S.Infinity: | |
return S.Infinity | |
elif coeff is S.NegativeInfinity: | |
return S.Infinity | |
elif coeff.is_Rational: | |
if coeff.is_nonnegative: | |
return pi * I * S.Half + cls(coeff) | |
else: | |
return -pi * I * S.Half + cls(-coeff) | |
if arg.is_number and arg.is_algebraic: | |
# Match arg = coeff*(r_ + i_*I) with coeff>0, r_ and i_ real. | |
coeff, arg_ = arg.as_independent(I, as_Add=False) | |
if coeff.is_negative: | |
coeff *= -1 | |
arg_ *= -1 | |
arg_ = expand_mul(arg_, deep=False) | |
r_, i_ = arg_.as_independent(I, as_Add=True) | |
i_ = i_.as_coefficient(I) | |
if coeff.is_real and i_ and i_.is_real and r_.is_real: | |
if r_.is_zero: | |
if i_.is_positive: | |
return pi * I * S.Half + cls(coeff * i_) | |
elif i_.is_negative: | |
return -pi * I * S.Half + cls(coeff * -i_) | |
else: | |
from sympy.simplify import ratsimp | |
# Check for arguments involving rational multiples of pi | |
t = (i_/r_).cancel() | |
t1 = (-t).cancel() | |
atan_table = _log_atan_table() | |
if t in atan_table: | |
modulus = ratsimp(coeff * Abs(arg_)) | |
if r_.is_positive: | |
return cls(modulus) + I * atan_table[t] | |
else: | |
return cls(modulus) + I * (atan_table[t] - pi) | |
elif t1 in atan_table: | |
modulus = ratsimp(coeff * Abs(arg_)) | |
if r_.is_positive: | |
return cls(modulus) + I * (-atan_table[t1]) | |
else: | |
return cls(modulus) + I * (pi - atan_table[t1]) | |
def as_base_exp(self): | |
""" | |
Returns this function in the form (base, exponent). | |
""" | |
return self, S.One | |
def taylor_term(n, x, *previous_terms): # of log(1+x) | |
r""" | |
Returns the next term in the Taylor series expansion of `\log(1+x)`. | |
""" | |
from sympy.simplify.powsimp import powsimp | |
if n < 0: | |
return S.Zero | |
x = sympify(x) | |
if n == 0: | |
return x | |
if previous_terms: | |
p = previous_terms[-1] | |
if p is not None: | |
return powsimp((-n) * p * x / (n + 1), deep=True, combine='exp') | |
return (1 - 2*(n % 2)) * x**(n + 1)/(n + 1) | |
def _eval_expand_log(self, deep=True, **hints): | |
from sympy.concrete import Sum, Product | |
force = hints.get('force', False) | |
factor = hints.get('factor', False) | |
if (len(self.args) == 2): | |
return expand_log(self.func(*self.args), deep=deep, force=force) | |
arg = self.args[0] | |
if arg.is_Integer: | |
# remove perfect powers | |
p = perfect_power(arg) | |
logarg = None | |
coeff = 1 | |
if p is not False: | |
arg, coeff = p | |
logarg = self.func(arg) | |
# expand as product of its prime factors if factor=True | |
if factor: | |
p = factorint(arg) | |
if arg not in p.keys(): | |
logarg = sum(n*log(val) for val, n in p.items()) | |
if logarg is not None: | |
return coeff*logarg | |
elif arg.is_Rational: | |
return log(arg.p) - log(arg.q) | |
elif arg.is_Mul: | |
expr = [] | |
nonpos = [] | |
for x in arg.args: | |
if force or x.is_positive or x.is_polar: | |
a = self.func(x) | |
if isinstance(a, log): | |
expr.append(self.func(x)._eval_expand_log(**hints)) | |
else: | |
expr.append(a) | |
elif x.is_negative: | |
a = self.func(-x) | |
expr.append(a) | |
nonpos.append(S.NegativeOne) | |
else: | |
nonpos.append(x) | |
return Add(*expr) + log(Mul(*nonpos)) | |
elif arg.is_Pow or isinstance(arg, exp): | |
if force or (arg.exp.is_extended_real and (arg.base.is_positive or ((arg.exp+1) | |
.is_positive and (arg.exp-1).is_nonpositive))) or arg.base.is_polar: | |
b = arg.base | |
e = arg.exp | |
a = self.func(b) | |
if isinstance(a, log): | |
return unpolarify(e) * a._eval_expand_log(**hints) | |
else: | |
return unpolarify(e) * a | |
elif isinstance(arg, Product): | |
if force or arg.function.is_positive: | |
return Sum(log(arg.function), *arg.limits) | |
return self.func(arg) | |
def _eval_simplify(self, **kwargs): | |
from sympy.simplify.simplify import expand_log, simplify, inversecombine | |
if len(self.args) == 2: # it's unevaluated | |
return simplify(self.func(*self.args), **kwargs) | |
expr = self.func(simplify(self.args[0], **kwargs)) | |
if kwargs['inverse']: | |
expr = inversecombine(expr) | |
expr = expand_log(expr, deep=True) | |
return min([expr, self], key=kwargs['measure']) | |
def as_real_imag(self, deep=True, **hints): | |
""" | |
Returns this function as a complex coordinate. | |
Examples | |
======== | |
>>> from sympy import I, log | |
>>> from sympy.abc import x | |
>>> log(x).as_real_imag() | |
(log(Abs(x)), arg(x)) | |
>>> log(I).as_real_imag() | |
(0, pi/2) | |
>>> log(1 + I).as_real_imag() | |
(log(sqrt(2)), pi/4) | |
>>> log(I*x).as_real_imag() | |
(log(Abs(x)), arg(I*x)) | |
""" | |
sarg = self.args[0] | |
if deep: | |
sarg = self.args[0].expand(deep, **hints) | |
sarg_abs = Abs(sarg) | |
if sarg_abs == sarg: | |
return self, S.Zero | |
sarg_arg = arg(sarg) | |
if hints.get('log', False): # Expand the log | |
hints['complex'] = False | |
return (log(sarg_abs).expand(deep, **hints), sarg_arg) | |
else: | |
return log(sarg_abs), sarg_arg | |
def _eval_is_rational(self): | |
s = self.func(*self.args) | |
if s.func == self.func: | |
if (self.args[0] - 1).is_zero: | |
return True | |
if s.args[0].is_rational and fuzzy_not((self.args[0] - 1).is_zero): | |
return False | |
else: | |
return s.is_rational | |
def _eval_is_algebraic(self): | |
s = self.func(*self.args) | |
if s.func == self.func: | |
if (self.args[0] - 1).is_zero: | |
return True | |
elif fuzzy_not((self.args[0] - 1).is_zero): | |
if self.args[0].is_algebraic: | |
return False | |
else: | |
return s.is_algebraic | |
def _eval_is_extended_real(self): | |
return self.args[0].is_extended_positive | |
def _eval_is_complex(self): | |
z = self.args[0] | |
return fuzzy_and([z.is_complex, fuzzy_not(z.is_zero)]) | |
def _eval_is_finite(self): | |
arg = self.args[0] | |
if arg.is_zero: | |
return False | |
return arg.is_finite | |
def _eval_is_extended_positive(self): | |
return (self.args[0] - 1).is_extended_positive | |
def _eval_is_zero(self): | |
return (self.args[0] - 1).is_zero | |
def _eval_is_extended_nonnegative(self): | |
return (self.args[0] - 1).is_extended_nonnegative | |
def _eval_nseries(self, x, n, logx, cdir=0): | |
# NOTE Please see the comment at the beginning of this file, labelled | |
# IMPORTANT. | |
from sympy.series.order import Order | |
from sympy.simplify.simplify import logcombine | |
from sympy.core.symbol import Dummy | |
if self.args[0] == x: | |
return log(x) if logx is None else logx | |
arg = self.args[0] | |
t = Dummy('t', positive=True) | |
if cdir == 0: | |
cdir = 1 | |
z = arg.subs(x, cdir*t) | |
k, l = Wild("k"), Wild("l") | |
r = z.match(k*t**l) | |
if r is not None: | |
k, l = r[k], r[l] | |
if l != 0 and not l.has(t) and not k.has(t): | |
r = l*log(x) if logx is None else l*logx | |
r += log(k) - l*log(cdir) # XXX true regardless of assumptions? | |
return r | |
def coeff_exp(term, x): | |
coeff, exp = S.One, S.Zero | |
for factor in Mul.make_args(term): | |
if factor.has(x): | |
base, exp = factor.as_base_exp() | |
if base != x: | |
try: | |
return term.leadterm(x) | |
except ValueError: | |
return term, S.Zero | |
else: | |
coeff *= factor | |
return coeff, exp | |
# TODO new and probably slow | |
try: | |
a, b = z.leadterm(t, logx=logx, cdir=1) | |
except (ValueError, NotImplementedError, PoleError): | |
s = z._eval_nseries(t, n=n, logx=logx, cdir=1) | |
while s.is_Order: | |
n += 1 | |
s = z._eval_nseries(t, n=n, logx=logx, cdir=1) | |
try: | |
a, b = s.removeO().leadterm(t, cdir=1) | |
except ValueError: | |
a, b = s.removeO().as_leading_term(t, cdir=1), S.Zero | |
p = (z/(a*t**b) - 1)._eval_nseries(t, n=n, logx=logx, cdir=1) | |
if p.has(exp): | |
p = logcombine(p) | |
if isinstance(p, Order): | |
n = p.getn() | |
_, d = coeff_exp(p, t) | |
logx = log(x) if logx is None else logx | |
if not d.is_positive: | |
res = log(a) - b*log(cdir) + b*logx | |
_res = res | |
logflags = {"deep": True, "log": True, "mul": False, "power_exp": False, | |
"power_base": False, "multinomial": False, "basic": False, "force": True, | |
"factor": False} | |
expr = self.expand(**logflags) | |
if (not a.could_extract_minus_sign() and | |
logx.could_extract_minus_sign()): | |
_res = _res.subs(-logx, -log(x)).expand(**logflags) | |
else: | |
_res = _res.subs(logx, log(x)).expand(**logflags) | |
if _res == expr: | |
return res | |
return res + Order(x**n, x) | |
def mul(d1, d2): | |
res = {} | |
for e1, e2 in product(d1, d2): | |
ex = e1 + e2 | |
if ex < n: | |
res[ex] = res.get(ex, S.Zero) + d1[e1]*d2[e2] | |
return res | |
pterms = {} | |
for term in Add.make_args(p.removeO()): | |
co1, e1 = coeff_exp(term, t) | |
pterms[e1] = pterms.get(e1, S.Zero) + co1 | |
k = S.One | |
terms = {} | |
pk = pterms | |
while k*d < n: | |
coeff = -S.NegativeOne**k/k | |
for ex in pk: | |
_ = terms.get(ex, S.Zero) + coeff*pk[ex] | |
terms[ex] = _.nsimplify() | |
pk = mul(pk, pterms) | |
k += S.One | |
res = log(a) - b*log(cdir) + b*logx | |
for ex in terms: | |
res += terms[ex]*t**(ex) | |
if a.is_negative and im(z) != 0: | |
from sympy.functions.special.delta_functions import Heaviside | |
for i, term in enumerate(z.lseries(t)): | |
if not term.is_real or i == 5: | |
break | |
if i < 5: | |
coeff, _ = term.as_coeff_exponent(t) | |
res += -2*I*pi*Heaviside(-im(coeff), 0) | |
res = res.subs(t, x/cdir) | |
return res + Order(x**n, x) | |
def _eval_as_leading_term(self, x, logx=None, cdir=0): | |
# NOTE | |
# Refer https://github.com/sympy/sympy/pull/23592 for more information | |
# on each of the following steps involved in this method. | |
arg0 = self.args[0].together() | |
# STEP 1 | |
t = Dummy('t', positive=True) | |
if cdir == 0: | |
cdir = 1 | |
z = arg0.subs(x, cdir*t) | |
# STEP 2 | |
try: | |
c, e = z.leadterm(t, logx=logx, cdir=1) | |
except ValueError: | |
arg = arg0.as_leading_term(x, logx=logx, cdir=cdir) | |
return log(arg) | |
if c.has(t): | |
c = c.subs(t, x/cdir) | |
if e != 0: | |
raise PoleError("Cannot expand %s around 0" % (self)) | |
return log(c) | |
# STEP 3 | |
if c == S.One and e == S.Zero: | |
return (arg0 - S.One).as_leading_term(x, logx=logx) | |
# STEP 4 | |
res = log(c) - e*log(cdir) | |
logx = log(x) if logx is None else logx | |
res += e*logx | |
# STEP 5 | |
if c.is_negative and im(z) != 0: | |
from sympy.functions.special.delta_functions import Heaviside | |
for i, term in enumerate(z.lseries(t)): | |
if not term.is_real or i == 5: | |
break | |
if i < 5: | |
coeff, _ = term.as_coeff_exponent(t) | |
res += -2*I*pi*Heaviside(-im(coeff), 0) | |
return res | |
class LambertW(Function): | |
r""" | |
The Lambert W function $W(z)$ is defined as the inverse | |
function of $w \exp(w)$ [1]_. | |
Explanation | |
=========== | |
In other words, the value of $W(z)$ is such that $z = W(z) \exp(W(z))$ | |
for any complex number $z$. The Lambert W function is a multivalued | |
function with infinitely many branches $W_k(z)$, indexed by | |
$k \in \mathbb{Z}$. Each branch gives a different solution $w$ | |
of the equation $z = w \exp(w)$. | |
The Lambert W function has two partially real branches: the | |
principal branch ($k = 0$) is real for real $z > -1/e$, and the | |
$k = -1$ branch is real for $-1/e < z < 0$. All branches except | |
$k = 0$ have a logarithmic singularity at $z = 0$. | |
Examples | |
======== | |
>>> from sympy import LambertW | |
>>> LambertW(1.2) | |
0.635564016364870 | |
>>> LambertW(1.2, -1).n() | |
-1.34747534407696 - 4.41624341514535*I | |
>>> LambertW(-1).is_real | |
False | |
References | |
========== | |
.. [1] https://en.wikipedia.org/wiki/Lambert_W_function | |
""" | |
_singularities = (-Pow(S.Exp1, -1, evaluate=False), S.ComplexInfinity) | |
def eval(cls, x, k=None): | |
if k == S.Zero: | |
return cls(x) | |
elif k is None: | |
k = S.Zero | |
if k.is_zero: | |
if x.is_zero: | |
return S.Zero | |
if x is S.Exp1: | |
return S.One | |
w = Wild('w') | |
# W(x*log(x)) = log(x) for x >= 1/e | |
# e.g., W(-1/e) = -1, W(2*log(2)) = log(2) | |
result = x.match(w*log(w)) | |
if result is not None and Ge(result[w]*S.Exp1, S.One) is S.true: | |
return log(result[w]) | |
if x == -log(2)/2: | |
return -log(2) | |
# W(x**(x+1)*log(x)) = x*log(x) for x > 0 | |
# e.g., W(81*log(3)) = 3*log(3) | |
result = x.match(w**(w+1)*log(w)) | |
if result is not None and result[w].is_positive is True: | |
return result[w]*log(result[w]) | |
# W(e**(1/n)/n) = 1/n | |
# e.g., W(sqrt(e)/2) = 1/2 | |
result = x.match(S.Exp1**(1/w)/w) | |
if result is not None: | |
return 1 / result[w] | |
if x == -pi/2: | |
return I*pi/2 | |
if x == exp(1 + S.Exp1): | |
return S.Exp1 | |
if x is S.Infinity: | |
return S.Infinity | |
if fuzzy_not(k.is_zero): | |
if x.is_zero: | |
return S.NegativeInfinity | |
if k is S.NegativeOne: | |
if x == -pi/2: | |
return -I*pi/2 | |
elif x == -1/S.Exp1: | |
return S.NegativeOne | |
elif x == -2*exp(-2): | |
return -Integer(2) | |
def fdiff(self, argindex=1): | |
""" | |
Return the first derivative of this function. | |
""" | |
x = self.args[0] | |
if len(self.args) == 1: | |
if argindex == 1: | |
return LambertW(x)/(x*(1 + LambertW(x))) | |
else: | |
k = self.args[1] | |
if argindex == 1: | |
return LambertW(x, k)/(x*(1 + LambertW(x, k))) | |
raise ArgumentIndexError(self, argindex) | |
def _eval_is_extended_real(self): | |
x = self.args[0] | |
if len(self.args) == 1: | |
k = S.Zero | |
else: | |
k = self.args[1] | |
if k.is_zero: | |
if (x + 1/S.Exp1).is_positive: | |
return True | |
elif (x + 1/S.Exp1).is_nonpositive: | |
return False | |
elif (k + 1).is_zero: | |
if x.is_negative and (x + 1/S.Exp1).is_positive: | |
return True | |
elif x.is_nonpositive or (x + 1/S.Exp1).is_nonnegative: | |
return False | |
elif fuzzy_not(k.is_zero) and fuzzy_not((k + 1).is_zero): | |
if x.is_extended_real: | |
return False | |
def _eval_is_finite(self): | |
return self.args[0].is_finite | |
def _eval_is_algebraic(self): | |
s = self.func(*self.args) | |
if s.func == self.func: | |
if fuzzy_not(self.args[0].is_zero) and self.args[0].is_algebraic: | |
return False | |
else: | |
return s.is_algebraic | |
def _eval_as_leading_term(self, x, logx=None, cdir=0): | |
if len(self.args) == 1: | |
arg = self.args[0] | |
arg0 = arg.subs(x, 0).cancel() | |
if not arg0.is_zero: | |
return self.func(arg0) | |
return arg.as_leading_term(x) | |
def _eval_nseries(self, x, n, logx, cdir=0): | |
if len(self.args) == 1: | |
from sympy.functions.elementary.integers import ceiling | |
from sympy.series.order import Order | |
arg = self.args[0].nseries(x, n=n, logx=logx) | |
lt = arg.as_leading_term(x, logx=logx) | |
lte = 1 | |
if lt.is_Pow: | |
lte = lt.exp | |
if ceiling(n/lte) >= 1: | |
s = Add(*[(-S.One)**(k - 1)*Integer(k)**(k - 2)/ | |
factorial(k - 1)*arg**k for k in range(1, ceiling(n/lte))]) | |
s = expand_multinomial(s) | |
else: | |
s = S.Zero | |
return s + Order(x**n, x) | |
return super()._eval_nseries(x, n, logx) | |
def _eval_is_zero(self): | |
x = self.args[0] | |
if len(self.args) == 1: | |
return x.is_zero | |
else: | |
return fuzzy_and([x.is_zero, self.args[1].is_zero]) | |
def _log_atan_table(): | |
return { | |
# first quadrant only | |
sqrt(3): pi / 3, | |
1: pi / 4, | |
sqrt(5 - 2 * sqrt(5)): pi / 5, | |
sqrt(2) * sqrt(5 - sqrt(5)) / (1 + sqrt(5)): pi / 5, | |
sqrt(5 + 2 * sqrt(5)): pi * Rational(2, 5), | |
sqrt(2) * sqrt(sqrt(5) + 5) / (-1 + sqrt(5)): pi * Rational(2, 5), | |
sqrt(3) / 3: pi / 6, | |
sqrt(2) - 1: pi / 8, | |
sqrt(2 - sqrt(2)) / sqrt(sqrt(2) + 2): pi / 8, | |
sqrt(2) + 1: pi * Rational(3, 8), | |
sqrt(sqrt(2) + 2) / sqrt(2 - sqrt(2)): pi * Rational(3, 8), | |
sqrt(1 - 2 * sqrt(5) / 5): pi / 10, | |
(-sqrt(2) + sqrt(10)) / (2 * sqrt(sqrt(5) + 5)): pi / 10, | |
sqrt(1 + 2 * sqrt(5) / 5): pi * Rational(3, 10), | |
(sqrt(2) + sqrt(10)) / (2 * sqrt(5 - sqrt(5))): pi * Rational(3, 10), | |
2 - sqrt(3): pi / 12, | |
(-1 + sqrt(3)) / (1 + sqrt(3)): pi / 12, | |
2 + sqrt(3): pi * Rational(5, 12), | |
(1 + sqrt(3)) / (-1 + sqrt(3)): pi * Rational(5, 12) | |
} | |