I am trying to create a plot that looks like the picture.
Wave Particle Motions under Wave
This is not homework, i'm trying to do this for experience.
I have the following parameters:
Plot the water particle motions under Trough (Lowest point on wave elevation profile) at water depths
from 0 to 100 meters in increments of 10 m below mean water line.
The wave profile varying over space is ๐(๐ฅ) = ๐ดcos(๐x) at time = 0. Plot this wave profile first for one wave.
๐(๐ฅ) = ๐ด*cos(๐x) #at time = 0
Next compute vertical and horizontal particle displacements for different water depths of 0 to 100m
XDisp = -A * e**(k*z) * np.sin(-w*t)
YDisp = -A * e**(k*z) * np.cos(-w*t) # when x=0
You could use any x.
Motion magnitudes donโt change. Where z is depth below mean water level. All other parameters are as defined in earlier problems above.
Do not forget to shift the horizontally particle displacement to under trough and โzโ below water line for vertical particle displacement.
Here is my code, but im doing something wrong. I have the plot looking like the example but my circles are not right. I think it has to do with the x&y disp.
import numpy as np
import matplotlib.pyplot as plt
A = 1 # Wave amplitude in meters
T = 10 # Time Period in secs
n_w = 1 # Number of waves
wavelength = 156 # Wavelength in meters
# Wave Number
k = (2 * np.pi) / wavelength
# Wave angular frequency
w = (2 * np.pi) / T
def XDisp(z,t):
return -A * np.e**(k * z) * np.sin(-w * t)
def YDisp(z,t):
return -A * np.e**(k * z) * np.cos(-w * t)
def wave_elevation(x):
return A * np.cos(k * x)
t_list = np.array([0,0.25,0.5,0.75,1.0])*T
z = [0,-10,-20,-30,-40,-50,-60,-70,-80,-90,-100]
A_d = []
x_plot2 = []
for i in z:
A_d.append(A * np.e**(k * i))
x_plot2.append(wavelength/2)
x_plot = np.linspace(0,wavelength)
Y_plot = []
for i in x_plot:
Y_plot.append(wave_elevation(i))
plt.plot(x_plot,Y_plot,'.-r')
plt.scatter(x_plot2,z,s= A_d, facecolors = 'none',edgecolors = 'b',marker='o',linewidth=2)
plt.xlabel('X (m)')
plt.ylabel("\u03B7 & Water Depth")
plt.title('Wave Particle Motions Under Wave')
plt.legend()
plt.grid()
plt.show()
I am afraid with provided information, I don't follow science part of the question, but if you have problem in marker size you can put an array of sizes as third argument of plt.scatter. I think this code may help you, although I change your code a little bit to make it simpler
import numpy as np
import matplotlib.pyplot as plt
A = 1 # Wave amplitude in meters
T = 10 # Time Period in secs
n_w = 1 # Number of waves
wavelength = 156 # Wavelength in meters
k = (2 * np.pi) / wavelength # Wave Number
w = (2 * np.pi) / T # Wave angular frequency
def wave_elevation(x):
return A * np.cos(k * x)
A_d = [] # marker size
x2 = [] # for particle place on x axis which is wavelength/2
y2 = [] # for particle place on y axis
for i in range(0,100,10):
x2.append(wavelength/2)
y2.append(-i)
A_d.append(15 * np.exp(-k * i)) # here I change A to 15
x = []
y = []
for i in range(0,wavelength):
x.append(i)
y.append(wave_elevation(i))
plt.plot(x,y,'red')
plt.scatter(x2,y2,A_d)
plt.ylim(-100, 10)
plt.xlabel('X (m)')
plt.ylabel("\u03B7 & Water Depth")
plt.title('Wave Particle Motions Under Wave')
plt.grid()
plt.show()
Related
I'm working on my end of the degree thesis in which I have to measure the Sound Pressure Level of underwater recordings (wav files) at a particular frequency (2000Hz). So I came up with this code:
'''
def get_value(filename, f0, NFFT=8192, plot = False):
#Load audio
data, sampling_frequency = soundfile.read(filename)
# remove stereo
if len(data.shape)> 1:
data = data[:, 0]
# remove extra length
if len(data)>sampling_frequency:
data = data[0:sampling_frequency]
# remove DC
data = data - data.mean()
# power without filtering
total_power = 10*np.log10(np.mean(data**2))
# fft
NFFT = 4096 # number of samples in the FFT
window = np.array(1) #np.hamming(len(data))
fftdata = np.fft.fft(data / NFFT, n = NFFT)
SPL = 20 * np.log10(np.abs(fftdata)) # Sound Pressure Level [dB]
freq = np.linspace(0, sampling_frequency, NFFT) # frequency axis [Hz]
# take value at desired frequency
power_at_frequency = SPL[np.argmin(np.abs(freq-f0))]
print(power_at_frequency)
'''
However, I checked the value with audacity and is completely different.
Thanks beforehand.
If you are interested in only one frequency you don't have to compute the FFT you can simply use
totalEnergy = np.sum((data - np.mean(data)) ** 2)
freqEnergy = np.abs(np.sum(data * np.exp(2j * np.pi * np.arange(len(data)) * target_freq / sampling_freq)))
And if you are using FFT and the window size is not a multiple of the wave period the frequency will leak to other frequencies. To avoid this your
import numpy as np;
import matplotlib.pyplot as plt
sampling_frequency = 48000;
target_frequency = 2000.0;
ns = 1000000;
data = np.sin(2*np.pi * np.arange(ns) * target_frequency / sampling_frequency);
# power
print('a sine wave have power 0.5 ~', np.mean(data**2), 'that will be split in two ')
## Properly scaled frequency
plt.figure(figsize=(12, 5))
plt.subplot(121);
z = np.abs(np.fft.fft(data[:8192])**2) / 8192**2
print('tuned with 8192 samples', max(z), ' some power leaked in other frequencies')
plt.semilogy(np.fft.fftfreq(len(z)) * sampling_frequency, z)
plt.ylabel('power')
plt.title('some power leaked')
plt.subplot(122);
# 6000 samples = 1/8 second is multiple of 1/2000 second
z = np.abs(np.fft.fft(data[:6000])**2) / 6000**2
print('tuned with 6000 samples', max(z))
plt.semilogy(np.fft.fftfreq(len(z)) * sampling_frequency, z)
plt.xlabel('frequency')
plt.title('all power in exact two symmetric bins')
## FFT of size not multiple of 2000
print(np.sum(np.abs(np.fft.fft(data[:8192]))**2) / 8192)
I hope you can help! I need to show the trajectory of a particle that is under gravity of g = -9.81 m/s2 and time step of dt = 0.05 sec, where the position and velocity of the particle are:
x_1 = x_0 + v_x1 * dt
y_1 = y_0 + v_y1 * dt
v_x1 = v_x0
v_y1 = v_y0 + g * dt
This is what I should achieve:
This is what I've done so far:
import numpy as np
import matplotlib.pyplot as plt
plt.figure(1, figsize=(12,12))
ax = plt.subplot(111, aspect='equal')
ax.set_ylim(0,50)
ax.set_title('Boom --- Weeee! --- Ooof')
r = np.array([0.,0.,15.,30.])
g = -9.81
dt = 0.05
y = 0
x = 0
while y > 0:
plt.plot(x_1,y_2,':', ms=2)
x_1 = v_x1 * dt
y_1 = v_y1 * dt
v_x1 = v_x0
v_y1 = v_y0 + g * dt
This doesn't produce an image only the plt.figure stated in the beginning, I've tried to integrate the r vector into the loop but I can't figure out how.
Thank you.
Here's a modified version of your code that I believe gives you the result you desire (you may want to choose different initial velocity values):
import matplotlib.pyplot as plt
# Set up our plot surface
plt.figure(1, figsize=(12,12))
ax = plt.subplot()
# ax = plt.subplot(111, aspect='equal')
# ax.set_ylim(0,50)
ax.set_title('Boom --- Weeee! --- Ooof')
# Initial conditions
g = -9.81
dt = 0.05
y = 0
x = 0
v_x = 5
v_y = 5
# Create our lists that will store our data points
points_x = []
points_y = []
while True:
# Add the current position of our projectile to our data
points_x.append(x)
points_y.append(y)
# Move our projectile along
x += v_x * dt
y += v_y * dt
# If our projectile falls below the X axis (y < 0), end the simulation
if y < 0:
break
# Update our y velocity per gravity
v_y = v_y + g * dt
# Plot our data
ax.plot(points_x, points_y)
# Show the plot on the screen
plt.show()
I'm sorry if I could have made fewer changes. Here are the substantive ones I can think of:
You weren't using the r value you computed, so got rid of it, along with the import of numpy that was then no longer needed.
I took out calls you made explicitly size your plot. You're better off letting the plot library decide upon the bounds of the plot for you
I don't know if there's another way to do it, but I've always supplied data to the plotting library as arrays of points rather than by providing the points one at a time. So here, I collect up all of the x and y coordinates into two lists while running the simulation, and then add those arrays to the plot at the end to plot the data.
The x_0 vs x_1, etc., got confusing for me. I didn't see any reason to keep track of multiple position and velocity values, so I reduced the code down to using just one set of positions and velocities, x, y, v_x and v_y.
See the comments for more info
Result:
I conceptually understand Fourier transforms. I wrote a naive algorithm to compute the transform, decompose a wave and plot it's individual components. I know it's not 'fast', and it also doesn't reconstruct the right amplitude. It was just meant to code the math behind the machinery, and it gives me this nice output:
Questions
How do I do something similar with np.fft
How do I recover whatever winding frequencies numpy chose under the hood?
How do I recover the amplitude of component waves that I find using the transform?
I've tried a few things. However, when I use p = np.fft.fft(signal) on the same exact wave as the above, I get really wacky plots, like this one:
f1 = 3
f2 = 5
start = 0
stop = 1
sample_rate = 0.005
x = np.arange(start, stop, sample_rate)
y = np.cos(f1 * 2 * np.pi * x) + np.sin(f2 * 2 * np.pi *x)
p = np.fft.fft(y)
plt.plot(np.real(p))
Or if I try to use np.fft.freq() to get the right frequencies for the horizontal axis:
p = np.fft.fft(y)
f = np.fft.fftfreq(y.shape[-1], d=sampling_rate)
plt.plot(f, np.real(p))
And as a recent addition, my attempt to implement #wwii's suggestions resulted in an improvement, but the frequency powers are still off in the transform:
f1 = 3
f2 = 5
start = 0
stop = 4.5
sample_rate = 0.01
x = np.arange(start, stop, sample_rate)
y = np.cos(f1 * 2 * np.pi * x) + np.sin(f2 * 2 * np.pi *x)
p = np.fft.fft(y)
freqs= np.fft.fftfreq(y.shape[-1], d=sampling_rate)
q = np.abs(p)
q = q[freqs > 0]
f = freqs[freqs > 0]
peaks, _ = find_peaks(q)
peaks
plt.plot(f, q)
plt.plot(freqs[peaks], q[peaks], 'ro')
plt.show()
So again, my question is, how do I use np.fft.fft and np.fft.fftfreqs to get the same information as my naive method does? And secondly, how do I recover amplitude information from the fft (amplitude of the component waves that add up to the composite).
I've read the documentation, but it is far from helpful.
For context here is my my naive method:
def wind(timescale, data, w_freq):
"""
wrap time-series data around complex plain at given winding frequency
"""
return data * np.exp(2 * np.pi * w_freq * timescale * 1.j)
def transform(x, y, freqs):
"""
Returns center of mass of each winding frequency
"""
ft = []
for f in freqs:
mapped = wind(x, y, f)
re, im = np.real(mapped).mean(), np.imag(mapped).mean()
mag = np.sqrt(re ** 2 + im ** 2)
ft.append(mag)
return np.array(ft)
def get_waves(parts, time):
"""
Generate sine waves based on frequency parts.
"""
num_waves = len(parts)
steps = len(time)
waves = np.zeros((num_waves, steps))
for i in range(num_waves):
waves[i] = np.sin(parts[i] * 2 * np.pi * time)
return waves
def decompose(time, data, freqs, threshold=None):
"""
Decompose and return the individual components of a composite wave form.
Plot each component wave.
"""
powers = transform(time, data, freqs)
peaks, _ = find_peaks(powers, threshold=threshold)
plt.plot(freqs, powers, 'b.--', label='Center of Mass')
plt.plot(freqs[peaks], powers[peaks], 'ro', label='Peaks')
plt.xlabel('Frequency')
plt.legend(), plt.grid()
plt.show()
return get_waves(freqs[peaks], time)
And the signal set-up I used to generate the plots:
# sample data plot: sin with frequencey of 3 hz.
f1 = 3
f2 = 5
start = 0
stop = 1
sample_rate = 0.005
x = np.arange(start, stop, sample_rate)
y = np.cos(f1 * 2 * np.pi * x) + np.sin(f2 * 2 * np.pi *x)
plt.plot(x, y, '.')
plt.xlabel('time')
plt.ylabel('amplitude')
plt.show()
freqs = np.arange(0, 20, .5)
waves = decompose(x, y, freqs, threshold=0.12)
for w in waves:
plt.plot(x, w)
plt.show()
f1 = 3
f2 = 5
start = 0
stop = 1
sample_rate = 0.005
x = np.arange(start, stop, sample_rate)
y = np.cos(f1 * 2 * np.pi * x) + np.sin(f2 * 2 * np.pi *x)
p = np.fft.fft(y)
freqs = np.fft.fftfreq(y.shape[0],sample_rate)
The fft returns the complex values so you need the sqrt of the sum of the squares like you did for mag in transform.
>>> p[:2]
array([-1.42663659e-14+0.00000000e+00j, -1.77635684e-15+1.38777878e-17j])
q = np.absolute(p)
>>> q[:2]
array([1.77641105e-15, 2.70861628e-14])
fft and fftfreqs give you both sides of the transform reflected around zero hz. You can see the negative frequencies at the end.
>>> freqs[-10:]
array([-10., -9., -8., -7., -6., -5., -4., -3., -2., -1.])
You only care about the positive frequencies so you can filter for them and plot.
q = q[freqs > 0]
freqs = freqs[freqs > 0]
plt.bar(freqs,q)
plt.show()
plt.close()
If there is a dc component and you want to see it your filter would be freqs >= 0.
Your example has 200 data points so you get 100 (n/2) positive frequencies and the graph ranges from zero to one hundred Hz with peaks at three and five.
numpy.fft.rfft only computes the positive frequencies. Using numpy.fft.rfftfreq to get the frequencies.
For me, this was/is an awesome resource - The Scientist and Engineer's Guide to
Digital Signal Processing - it's on my desk at work.
Last month, I posted this question about how to concatenate sine waves WHEN you are generating them, but now I've faced a different situation where I will generate a sine and make it continue from the end of another sine I did not generate.
My solution was based on the second answer to my previous question, compute the hilbert transform, then, calculate the angle with numpy.angle and normalize it by adding 90, and generating the next sine from there. It works, but only when the unit of my frequency value is 0 or 5, otherwise, the waves doesn't match and I have no clue why.
from scipy.signal import hilbert
import numpy as np
from matplotlib import pyplot as plt
N = 1024
t = np.linspace(0, 1, N)
freq = 5.0
c = np.sin(2 * np.pi * freq * t + 0.0)
c2 = np.angle(hilbert(c), True) # in degrees
plt.subplot(2, 1, 1)
plt.grid()
plt.plot(c)
plt.subplot(2, 1, 2)
phase = c2[-1] + 90
c3 = np.sin(2.0 * np.pi * freq * t + np.deg2rad(phase))
plt.grid()
plt.plot(c3)
plt.show()
Frequency: 5.0
Frequency: 5.8
When the values at the beginning and the end of the time interval do not agree, boundary effects appear, distorting the Hilbert transform. (Recall that the Fourier transform reacts poorly to discontinuities.) This can be seen by plotting the end of c2: plt.plot(c2[-200:] + 90): notice the distortion toward the end, the curve is supposed to rise with constant slope.
You'll get better results by stepping back one period from the edge of the time window:
phase = c2[-1 - int(N//freq)] + 90
I tried with frequency 5.8: the beginning of second curve matches the end of the first.
It is not clear what your exact problem scope is. In the previous question, in a comment which spawned this followup question, you said:
If I don't have the generation equation ( say, I've got a chunk from mic ) what would be the approach?
Does this mean the data is not necessarily a sine wave? Is it noisy? Is it of varying magnitude? You mention DSP: are you doing the processing in real time, or can the analysis take as long as needed?
If it is a clean sine wave of known magnitude, it is relatively easy to extract the phase from the end of the signal, to allow a smooth continuation.
The phase is sinโปยน(y/mag). There are two inputs to sin(angle), which result in the value y/mag, one for where sin(angle) is increasing with increasing angle, and one for when it is decreasing. By looking at the previous point, we can determine which one we need.
def ending_phase(c, mag):
angle = math.asin(c[-1] / mag)
if c[-2] > c[-1]:
angle = np.pi - angle
return angle
From the phase of the last point, and the phase of the second last point, we can extrapolate the phase for the next point.
def next_phase(c, mag):
ph1 = ending_phase(c[:-1], mag)
ph2 = ending_phase(c, mag)
return 2 * ph2 - ph1
Passing the previous chunk to next_phase() computes the phase argument required to smoothly continue the chunk.
N = 1024
t = np.linspace(0, 1, N)
mag = 1.2
freq = 5.2
phase = 2.2
c1 = mag * np.sin(2 * np.pi * freq * t + phase)
plt.subplot(2,2,1)
plt.grid()
plt.plot(c1)
freq = 3.8
phase = next_phase(c1, mag)
c2 = mag * np.sin(2 * np.pi * freq * t + phase)
plt.subplot(2,2,2)
plt.grid()
plt.plot(c2)
c3 = np.concatenate((c1, c2))
plt.subplot(2,1,2)
plt.grid()
plt.plot(c3)
plt.show()
I know there have been several questions about using the Fast Fourier Transform (FFT) method in python, but unfortunately none of them could help me with my problem:
I want to use python to calculate the Fast Fourier Transform of a given two dimensional signal f, i.e. f(x,y). Pythons documentation helps a lot, solving a few issues, which the FFT brings with it, but i still end up with a slightly shifted frequency compared to the frequency i expect it to show. Here is my python code:
from scipy.fftpack import fft, fftfreq, fftshift
import matplotlib.pyplot as plt
import numpy as np
import math
fq = 3.0 # frequency of signal to be sampled
N = 100.0 # Number of sample points within interval, on which signal is considered
x = np.linspace(0, 2.0 * np.pi, N) # creating equally spaced vector from 0 to 2pi, with spacing 2pi/N
y = x
xx, yy = np.meshgrid(x, y) # create 2D meshgrid
fnc = np.sin(2 * np.pi * fq * xx) # create a signal, which is simply a sine function with frequency fq = 3.0, modulating the x(!) direction
ft = np.fft.fft2(fnc) # calculating the fft coefficients
dx = x[1] - x[0] # spacing in x (and also y) direction (real space)
sampleFrequency = 2.0 * np.pi / dx
nyquisitFrequency = sampleFrequency / 2.0
freq_x = np.fft.fftfreq(ft.shape[0], d = dx) # return the DFT sample frequencies
freq_y = np.fft.fftfreq(ft.shape[1], d = dx)
freq_x = np.fft.fftshift(freq_x) # order sample frequencies, such that 0-th frequency is at center of spectrum
freq_y = np.fft.fftshift(freq_y)
half = len(ft) / 2 + 1 # calculate half of spectrum length, in order to only show positive frequencies
plt.imshow(
2 * abs(ft[:half,:half]) / half,
aspect = 'auto',
extent = (0, freq_x.max(), 0, freq_y.max()),
origin = 'lower',
interpolation = 'nearest',
)
plt.grid()
plt.colorbar()
plt.show()
And what i get out of this when running it, is:
Now you see that the frequency in x direction is not exactly at fq = 3, but slightly shifted to the left. Why is this?
I would assume that is has to do with the fact, that FFT is an algorithm using symmetry arguments and
half = len(ft) / 2 + 1
is used to show the frequencies at the proper place. But I don't quite understand what the exact problem is and how to fix it.
Edit: I have also tried using a higher sampling frequency (N = 10000.0), which did not solve the issue, but instead shifted the frequency slightly too far to the right. So i am pretty sure that the problem is not the sampling frequency.
Note: I'm aware of the fact, that the leakage effect leads to unphysical amplitudes here, but in this post I am primarily interested in the correct frequencies.
I found a number of issues
you use 2 * np.pi twice, you should choose one of either linspace or the arg to sine as radians if you want a nice integer number of cycles
additionally np.linspace defaults to endpoint=True, giving you an extra point for 101 instead of 100
fq = 3.0 # frequency of signal to be sampled
N = 100 # Number of sample points within interval, on which signal is considered
x = np.linspace(0, 1, N, endpoint=False) # creating equally spaced vector from 0 to 2pi, with spacing 2pi/N
y = x
xx, yy = np.meshgrid(x, y) # create 2D meshgrid
fnc = np.sin(2 * np.pi * fq * xx) # create a signal, which is simply a sine function with frequency fq = 3.0, modulating the x(!) direction
you can check these issues:
len(x)
Out[228]: 100
plt.plot(fnc[0])
fixing the linspace endpoint now means you have an even number of fft bins so you drop the + 1 in the half calc
matshow() appears to have better defaults, your extent = (0, freq_x.max(), 0, freq_y.max()), in imshow appears to fubar the fft bin numbering
from scipy.fftpack import fft, fftfreq, fftshift
import matplotlib.pyplot as plt
import numpy as np
import math
fq = 3.0 # frequency of signal to be sampled
N = 100 # Number of sample points within interval, on which signal is considered
x = np.linspace(0, 1, N, endpoint=False) # creating equally spaced vector from 0 to 2pi, with spacing 2pi/N
y = x
xx, yy = np.meshgrid(x, y) # create 2D meshgrid
fnc = np.sin(2 * np.pi * fq * xx) # create a signal, which is simply a sine function with frequency fq = 3.0, modulating the x(!) direction
plt.plot(fnc[0])
ft = np.fft.fft2(fnc) # calculating the fft coefficients
#dx = x[1] - x[0] # spacing in x (and also y) direction (real space)
#sampleFrequency = 2.0 * np.pi / dx
#nyquisitFrequency = sampleFrequency / 2.0
#
#freq_x = np.fft.fftfreq(ft.shape[0], d=dx) # return the DFT sample frequencies
#freq_y = np.fft.fftfreq(ft.shape[1], d=dx)
#
#freq_x = np.fft.fftshift(freq_x) # order sample frequencies, such that 0-th frequency is at center of spectrum
#freq_y = np.fft.fftshift(freq_y)
half = len(ft) // 2 # calculate half of spectrum length, in order to only show positive frequencies
plt.matshow(
2 * abs(ft[:half, :half]) / half,
aspect='auto',
origin='lower'
)
plt.grid()
plt.colorbar()
plt.show()
zoomed the plot: