True inverse function for cosine in numpy? (NOT arccos)

Viewed 2087

Here is a weird one:

I have found myself needing a numpy function that is what I would call the true inverse of np.cos (or another trigonometric function, cosine is used here for definiteness). What I mean by ''true inverse'' is a function invcos, such that

np.cos(invcos(x)) = x

for any real float x. Two observations: invcos(x) exists (it is a complex float) and np.arccos(x) does not do the job, because it only works for -1 < x < 1.

My question is if there is an efficient numpy function for this operation or if it can built from existing ones easily?

My attempt was to use a combination of np.arccos and np.arccosh to build the function by hand. This is based on the observation that np.arccos can deal with x in [-1,1] and np.arccosh can deal with x outside [-1,1] if one multiplies by the complex unit. To see that this works:

cos_x = np.array([0.5, 1., 1.5])

x = np.arccos(cos_x)
cos_x_reconstucted = np.cos(x)
# [0.5 1.  nan]

x2 = 1j*np.arccosh(cos_x)
cos_x_reconstructed2 = np.cos(x2)
# [nan+nanj 1.-0.j 1.5-0.j]

So we could combine this to

def invcos(array):
    x1 = np.arccos(array)
    x2 = 1j*np.arccosh(array)
    print(x1)
    print(x2)
    x = np.empty_like(x1, dtype=np.complex128)
    x[~np.isnan(x1)] = x1[~np.isnan(x1)]
    x[~np.isnan(x2)] = x2[~np.isnan(x2)]
    return x

cos_x = np.array([0.5, 1., 1.5])
x = invcos(cos_x)
cos_x_reconstructed = np.cos(x)
# [0.5-0.j 1.-0.j 1.5-0.j]

This gives the correct results, but naturally raises RuntimeWarnings:

RuntimeWarning: invalid value encountered in arccos.

I guess since numpy even tells me that my algorithm is not efficient, it is probably not efficient. Is there a better way to do this?


For readers who are interested in why this strange function may be useful: The motivation comes from a physics background. In certain theories, one can have vector components that are 'off-shell', which means that the components might even be longer than the vector. The above function can be useful to nevertheless parametrize things in terms of angles.

1 Answers

My question is if there is an efficient numpy function for this operation or if it can built from existing ones easily?

Yes; it is... np.arccos.

From the documentation:

For real-valued input data types, arccos always returns real output. For each value that cannot be expressed as a real number or infinity, it yields nan and sets the invalid floating point error flag.

For complex-valued input, arccos is a complex analytic function that has branch cuts [-inf, -1] and [1, inf] and is continuous from above on the former and from below on the latter.

So all we need to do is ensure that the input is a complex number (even if its imaginary part is zero):

>>> import numpy as np
>>> np.arccos(2.0)
__main__:1: RuntimeWarning: invalid value encountered in arccos
nan
>>> np.arccos(2 + 0j)
-1.3169578969248166j

For an array, we need the appropriate dtype:

>>> np.arccos(np.ones((3,3)) * 2)
array([[nan, nan, nan],
       [nan, nan, nan],
       [nan, nan, nan]])
>>> np.arccos(np.ones((3,3), dtype=np.complex) * 2)
array([[0.-1.3169579j, 0.-1.3169579j, 0.-1.3169579j],
       [0.-1.3169579j, 0.-1.3169579j, 0.-1.3169579j],
       [0.-1.3169579j, 0.-1.3169579j, 0.-1.3169579j]])
Related