# Understanding sine wave generation in Python with linspace

I was trying to sample a 12.8 MHz sine wave (78.125 ns) signal at every 160us (micro seconds). Since 160us is multiple of base period 78.125ns(x2048) i expected to get a sample of fixed amplitude but instead what I am seeing is a another periodic sine wave. I don't understand why ?

I am doubting quantization error but shouldn't that generate uniform noise instead of a creating a periodic sine wave.

    import numpy as np
from matplotlib import pyplot as plt

fig2 = plt.figure()
ax2 = fig2.add_subplot(1, 1, 1)

capture_size1 = 2048
timestep1 = 160e-6
freq1 = 12.8e6
time1 = np.linspace(0, capture_size1 * timestep1, capture_size1)
w1 = np.sin(time1 * 2 * np.pi * freq1)
ax2.plot(time1, w1, '.')

plt.show()


Edit1 : 1. the 12.8 MHZ is intentionally under sampled

1. Adding the screenshot of the plot with capture_size1 = 2048, the sine wave has proper amplitude of [+1, -1]

Edit2: I tried to increase the precision by using Decimal and i see it is behaving as expected. I expect a straight line as the sampling point is an exact multiple of period.

from decimal import Decimal
from math import pi as mpi
from math import sin as msin
import numpy as np
from matplotlib import pyplot as plt

fig2 = plt.figure()
ax2 = fig2.add_subplot(1, 1, 1)

capture_size1 = 2048
timestep1 = 160e-6
freq1 = 12.8e6
time1 = np.linspace(0, capture_size1 * timestep1, capture_size1)
w1 = np.sin(time1 * 2 * np.pi * freq1)
ax2.plot(time1, w1, '.')

capture_size3 = Decimal(2048 * 16)
timestep3 = Decimal(160e-6)
freq3 = Decimal(12.8e6)
time3 = [Decimal(i) * timestep3 for i in range(capture_size1)]
w3 = [msin(Decimal(i) * timestep3 * Decimal(2) * Decimal(mpi) * freq3) for i in range(capture_size1)]
ax2.plot(time3, w3, '.')

plt.legend(["Actual", "Expected"])
plt.show()



Edit3: I further did some analysis thanks to the comment by @jithin. Looks like this is an issue with linspace. I tried to generate the time interval by just multiplication as shown in below code and removed the original plot which used linspace(this is crucial) so now i am able to see the values in 1e-9 range as others suggest. So is there indeed issue with linspace ?

from decimal import Decimal
from math import pi as mpi
from math import sin as msin
import numpy as np
from matplotlib import pyplot as plt

fig2 = plt.figure()
ax2 = fig2.add_subplot(1, 1, 1)

capture_size1 = 2048
# timestep1 = 160e-6
# freq1 = 12.8e6
# time1 = np.linspace(0, capture_size1 * timestep1, capture_size1)
# w1 = np.sin(time1 * 2 * np.pi * freq1)
# ax2.plot(time1, w1, '.')

capture_size2 = 2048
timestep2 = 160e-6
freq2 = 12.8e6
time2 = [i * timestep2 for i in range(capture_size2)]
w2 = [np.sin(i * timestep2 * 2 * np.pi * freq2) for i in range(capture_size2)]
ax2.plot(time2, w2, '.')

capture_size3 = Decimal(2048)
timestep3 = Decimal(160e-6)
freq3 = Decimal(12.8e6)
time3 = [Decimal(i) * timestep3 for i in range(capture_size1)]
w3 = [msin(Decimal(i) * timestep3 * Decimal(2) * Decimal(mpi) * freq3) for i in range(capture_size1)]
ax2.plot(time3, w3, '.')

plt.legend(["multiply", "Decimal"], fontsize='xx-large')
plt.show()


The image of the above python code is below

## 2 Answers

Change the following line :

time1 = np.linspace(0, capture_size1 * timestep1, capture_size1)

To the following:

time1 = np.linspace(0, capture_size1 * timestep1, capture_size1, endpoint=false)

You will see correct results. Your original time instances is not what you intend because Python will create 2048 equally spaced point between 0 and 2048*Ts. What you want is equally spaced 2048 points starting from 0 and 160usec apart.

You can also use the following line if you dont want to use 'endpoint=false' :

time1 = np.linspace(0, (capture_size1-1) * timestep1, capture_size1)

At your current sampling period of $$160\mu sec$$, it will mean that you are taking 1 sample of the sinusoidal every 2048 periods. There will be aliasing, but you are not bothered about that because you want to see fixed amplitude across discrete time. Basically, you want aliasing to happen.

• Another option is to use np.arange instead of np.linspace, since you directly specify the step size. May 5, 2020 at 17:25

In order to sample that signal without aliasing, you need a sampling rate of at least

$$F_s = 2 * 12.8 MHz = 25 600 000 Hz$$, which means that you need to sample every $$\frac {1}{F_s} = 0,0390625 \mu s$$.

This means that your current sampling rate is way too low, in fact by dividing $$\frac{160}{0,0390625} = 4096$$ shows how much you need to increase your sampling rate.

update: np.sin function just like any sin function can only produce a sine wave (unless the argument is 0 or close to it, in which case you can get a straight line possibly due to numerical round-off errors). Note that np.sin accepts an angle as an argument so freq1 will be treated as an angle.

• the under sampling was intentional
– arun
May 5, 2020 at 6:56
• @arun, can you show us what you expect and what you currently have, because I'm not sure what the problem is? May 5, 2020 at 6:58
• Edited the question with further analysis, i am expecting a straight line but what i see is a sine wave
– arun
May 5, 2020 at 7:15
• @arun, thanks, I understand it now. I will update my answer May 5, 2020 at 7:20
• @jithin time1 = 0:timestep1:capture_size1*timestep1; in MATLAB gives points which are spaced timeStep1 apart starting from 0. But, time1 = np.linspace(0, capture_size1 * timestep1, capture_size1) in Python gives equally spaced 2048 points between 0 and 2048*timestep1, which will not be timestep1 apart but a little more. That is why you are seeing all zeros in MATLAB but not in Python. The trick is to use linspace() function in Python with argument 'endpoint=false'. May 5, 2020 at 10:56