Jakob
Jakob

Reputation: 20811

Reason for the difference between arg and atan2

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))

enter image description here

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)])

enter image description here

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

Answers (2)

Phillip
Phillip

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:

  • As long as you work with the symbolic version in sympy, everything you do is correct, both are equivalent
  • 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

Strikeskids
Strikeskids

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

Related Questions