Ram Rachum
Ram Rachum

Reputation: 88628

Numpy: Create sine wave with exponential decay

I'm a Numpy newbie.

I'd like to create an array with a million numbers, that has a sine wave with exponential decay on the amplitude.

In other words, I want the value of each cell n to be sin(n) * 2 ** (-n * factor).

What would be the most efficient way to do that?

Upvotes: 4

Views: 3109

Answers (2)

max9111
max9111

Reputation: 6482

You can use Numexpr

Numpy has efficient implementations for simple operations like sin(array), exp(array),.. The problem is that each expression (sin(n); -n * factor, 2 ** previous_Temp_array) is done on its own using temporary arrays for intermediate results. This leads to a quite high memory footprint and also has a negative impact on performance.

Code

import numpy as np
import numexpr as ne

def orig(n_max,factor):
    n=np.arange(n_max)
    return np.sin(n) * 2 ** (-n * factor)

#Rory Daulton's solution
def mod(n_max,factor):
    n=np.arange(n_max)
    newfactor = -np.log(2) * factor
    return np.sin(n) * np.exp(newfactor * n)

def mod_2(n_max,factor):
    n=np.arange(n_max)
    return ne.evaluate("sin(n) * 2**(-n * factor)")

#Rory Daulton's solution using Numexpr
def mod_3(n_max,factor):
    n=np.arange(n_max)
    newfactor = -np.log(2) * factor
    return ne.evaluate("sin(n) * exp(newfactor * n)")

Timings

%timeit res=orig(1e6,0.5)
81 ms ± 4.75 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit res=mod(1e6,0.5)
46.3 ms ± 5.29 ms per loop (mean ± std. dev. of 7 runs, 10 loops each)

%timeit res=mod_2(1e6,0.5)
16 ms ± 214 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit res=mod_3(1e6,0.5)
11.1 ms ± 48.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

Upvotes: 1

Rory Daulton
Rory Daulton

Reputation: 22544

Numpy has its own sin function, which is efficient at the task you want to do. The main cause of inefficiency would be in the exponentiation 2 ** (-n * factor).

However, numpy does have efficient exponentiation in its exp function. So we can transform the base to e to use exp by using

exp(-n * factor * log(2))

where log is another numpy function and uses base e. You could speed up your code even more by doing as much of the calculation as possible outside the loop that numpy sets up in its vectorization. In other words, you first set up the scalar

newfactor = -np.log(2) * factor

and set up your x array using x = np.linspace(0, 10, 1000000) or something similar. Then your y array is created with

y = np.sin(x) * np.exp(newfactor * x)

Now y is an array with all the calculated values corresponding to the values in the x array.

Numpy does the looping itself, very efficiently for current technology. My experiments show that doing the exponentiation this way takes less than 1/5th the time that np.power(2, -factor * x) or 2 ** (-x * factor) or np.exp2(-x * factor) take. Looking at the entire expression, for x an array of length one million, my code takes a total of 29.2 ms to execute. The code of @ComplicatedPhenomenon, which does look good, takes a total of 81.3 ms to execute, which is almost 3 times as long. (A hat-tip to @ComplicatedPhenomenon for pointing out an error in my code--I have corrected it and it seems to work well now.)

Upvotes: 3

Related Questions