Reputation: 20811
Recently, working with sympy I played and cos(x) being a complex function and discovered that the provided argument function arg
and the atan2
function yield different results. This can be easily shown by plotting the results.
import sympy as sp
c = sp.cos(x)
sp.plot(sp.atan2(sp.im(c),sp.re(c)))
sp.plot(sp.arg(c))
Although the difference is just the natural multiple of pi I would expect both functions to return the upper plot. This is also what WolframAlpha yields, see arg and atan2. Interesting enough if I numerically compute the argument (using the sympy function) I get the expected result:
import numpy as np
import matplotlib.pyplot as plt
xv = np.linspace(-10,10,200)
plt.plot(xv,[sp.arg(i) for i in np.cos(xv)])
Can someone please, point me to the difference, and maybe a resonable solution to get equal results?
I'm using sympy 0.7.5.
Upvotes: 4
Views: 1140
Reputation: 13668
The different plots come from an optimization within sympy. When you plot something, sympy internally uses numpy to evaluate your function. sympy's function vectorized_lambdify
translates arg(cos(x))
to its numpy equivalent, np.angle(np.cos(x))
, and then sympy evaluates that for an appropriate numpy array. So what sympy does is roughly equivalent to
import numpy as np
import matplotlib as plt
xv = np.array(np.linspace(-10,10,200), dtype=np.complex)
plt.plot(xv, np.angle(np.cos(xv)))
This reproduces the jumps from -pi to pi. See here for the important lines of code within sympy.
The other variant where you use atan is rewritten as
np.arctan2(-np.sin(np.real(x))*np.sinh(np.imag(x)),
np.cos(np.real(x))*np.cosh(np.imag(x)))
which is equivalent to numpy's definition of angle, and indeed plotting this also features the jumps. So the more important question, from my perspective, is: Why does a plot of sp.atan2(..)
look different?
I haven't been able to find a reason for that yet, but I believe that this might exceed the scope of your question anyway?! To answer your question:
If you want to have equal results, the simplest version probably would be to plot modulo 2pi. Alternatively, avoid the experimental_lambdify
call by plotting something like lambda val: (expr).subs(x, val)
directly from numpy, or entirely replace lambdify: Execute the following code before running your first example and both plots will look like the upper one:
import sympy.plotting.experimental_lambdify sympy.plotting.experimental_lambdify.experimental_lambdify = lambda x, exp, **kwargs: vectorize(lambda val: exp.subs(x[0], val))
(Note that this version of the snippet will only work for 1d plots.)
Upvotes: 3
Reputation: 4052
This could be an intended behavior. The argument is typically taken to be from -pi to pi because then the branch cut in the complex plane occurs at the negative real axis instead of the positive real axis
Upvotes: 0