rylirk
rylirk

Reputation: 179

Is there a fast Way to return Sin and Cos of the same value in Python?

I need to return the sin and cos values of every element in a large array. At the moment I am doing:

a,b=np.sin(x),np.cos(x)

where x is some large array. I need to keep the sign information for each result, so:

a=np.sin(x)
b=(1-a**2)**0.5

is not an option. Is there any faster way to return both sin and cos at once?

Upvotes: 18

Views: 9523

Answers (7)

Yuval
Yuval

Reputation: 3443

Using Numba you could improve speed by ~20%, if that matters to you.

import numba
import numpy as np

x = np.random.uniform((1000,))

@numba.njit
def sincos_simple(x):
    return np.sin(x), np.cos(x)

@numba.njit
def sincos_prealloc(x):
    r = np.empty(x.shape + (2,))
    r[..., 0] = np.sin(x)
    r[..., 1] = np.cos(x)
    return r

# compile numba function (run once before timing)
sincos_simple(x)
sincos_prealloc(x)

%timeit np.sin(x), np.cos(x)
%timeit sincos_simple(x)
%timeit sincos_prealloc(x)

Results:

1.02 µs ± 16.2 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
927 ns ± 13.1 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)
819 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1,000,000 loops each)

Upvotes: 0

Nico Schlömer
Nico Schlömer

Reputation: 58861

I compared the suggested solution with perfplot and found that nothing beats calling sin and cos explicitly.

enter image description here

Code to reproduce the plot:

import perfplot
import numpy as np


def sin_cos(x):
    return np.sin(x), np.cos(x)


def exp_ix(x):
    eix = np.exp(1j * x)
    return eix.imag, eix.real


def cos_from_sin(x):
    sin = np.sin(x)
    abs_cos = np.sqrt(1 - sin**2)
    sgn_cos = np.sign(((x - np.pi / 2) % (2 * np.pi)) - np.pi)
    cos = abs_cos * sgn_cos
    return sin, cos


b = perfplot.bench(
    setup=lambda n: np.linspace(0.0, 2 * np.pi, n),
    kernels=[sin_cos, exp_ix, cos_from_sin],
    n_range=[2**k for k in range(20)],
    xlabel="n",
)
b.save("out.png")
b.show()

Upvotes: 8

Reinderien
Reinderien

Reputation: 15273

For completeness, another way to combine this down to a single cos() call is to prepare an angle array where the second half has a phase shift of pi/2.

Borrowing the profiling code from Nico Schlömer, we get:

import perfplot
import numpy as np


def sin_cos(x):
    return np.sin(x), np.cos(x)


def exp_ix(x):
    eix = np.exp(1j * x)
    return eix.imag, eix.real


def cos_shift(x):
    angles = x[np.newaxis, :] + np.array(((-np.pi/2,), (0,)))
    return tuple(np.cos(angles))


perfplot.save(
    "out.png",
    setup=lambda n: np.linspace(0.0, 2 * np.pi, n),
    kernels=[sin_cos, exp_ix, cos_shift],
    n_range=[2 ** k for k in range(1, 16)],
    xlabel="n",
)

perfplot

So it's slower than the separate sin/cos calls, but in some (narrow) contexts might be more convenient because - from the cos() onward - it only needs to deal with a single array.

Upvotes: 1

Friedrich
Friedrich

Reputation: 21

A pure numpy version via complex numbers, e = cosφ + i sinφ, inspired by the answer from das-g.

x = np.arange(2 * np.pi, step=0.01)

eix = np.exp(1j*x)
cosx, sinx = eix.real, eix.imag

This is faster than the nprect, but still slower than sin and cos calls:

In [6]: timeit c = nprect(1, x); cosx, sinx = cos(x), sin(x)
1000 loops, best of 3: 242 us per loop

In [7]: timeit eix = np.exp(1j*x); cosx, sinx = eix.real, eix.imag
10000 loops, best of 3: 49.1 us per loop

In [8]: timeit cosx, sinx = cos(x), sin(x)
10000 loops, best of 3: 32.7 us per loop

Upvotes: 1

das-g
das-g

Reputation: 9994

You can use complex numbers and the fact that e i · φ = cos(φ) + i · sin(φ).

import numpy as np
from cmath import rect
nprect = np.vectorize(rect)

x = np.arange(2 * np.pi, step=0.01)

c = nprect(1, x)
a, b = c.imag, c.real

I'm using here the trick from https://stackoverflow.com/a/27788291/674064 to make a version of cmath.rect() that'll accept and return NumPy arrays.

This doesn't gain any speedup on my machine, though:

c = nprect(1, x)
a, b = c.imag, c.real

takes about three times the time (160μs) that

a, b = np.sin(x), np.cos(x)

took in my measurement (50.4μs).

Upvotes: 4

rylirk
rylirk

Reputation: 179

def cosfromsin(x,sinx):
   cosx=absolute((1-sinx**2)**0.5)
   signx=sign(((x-pi/2)%(2*pi))-pi)
   return cosx*signx

a=sin(x)
b=cosfromsin(x,a)

I've just timed this and it is about 25% faster than using sin and cos.

Upvotes: -1

vathek
vathek

Reputation: 551

You could take advantage by the fact that tan(x) contains both sin(x) and cos(x) function. So you could use the tan(x) and retrieve cos(x) ans sin(x) using the common transformation function.

Upvotes: -1

Related Questions