In this section it is demonstrated how to model 2D wave using the Wave2D class. In the documetation of this class you can find some back ground. We show here two ways of modelling a 2D wave field
The spectrum defined in polar mesh simply multiplies the 1D frequency distribution $S(\omega)$ with the directional distribution function $D(\theta)$. In theory, the diretional distribution may be a function of the frequency as well, however, that is not implemented and left out of consideration. The frequency distribution is created using the Wave1D class. The resulting object is passed as an parameter to the Wave2D class. It looks like this
First start with the import of our modules required to run this notebook example
import sys
import os
import logging
import numpy as np
import matplotlib.pyplot as plt
import numpy as np
from IPython.display import HTML
from hmc_utils.misc import (create_logger, Timer)
import hmc_marine.wave_fields as wf
import hmc_marine
logger = create_logger(console_log_format_clean=True, console_log_level=logging.INFO)
To model a 2D wave you first have to set up 1D wave to obtain the spectral distribution. We have seen this in the example_wave_modelling notebook already.
wave1D_1 = wf.Wave1D(n_kx_nodes=128, nx_points=251, Lx=500, wave_construction="DFTpolar",
Tp=8, spectrum_type="jonswap", Hs=3, spectral_version="hmc",wave_selection="All")
wave1D_1.make_report()
We took 128 nodes in the wave vector domain. Let's have a look at the spectrum to see if this is enough to resolve the peak in the Jonswap spectrum
wave1D_1.plot_spectrum()
plt.show()
As you can see we have about five to six nodes at the peak in the spectrum, so with 128 nodes we can just resolve the peak. Also, the length of the domain of 500 m at a mesh of 251 grid points has a spatial resolution of 2 m. The nyquest frequency is kn = pi/delta_x = pi/2 = 1.57. We took 128 wave vector nodes so the delta spacing in the k-domain is delta_k=0.0012 rad/m, which corresponds to a maximum wave length of 2 * pi/delta_k = 512 m. Since the lenght L of the domain was 500 m, we won't see any repititions of the wave height over the domain length. Let's check that:
wave1D_1.reset_time(nt_samples=500, delta_t=.1)
movie = wave1D_1.animate_wave()
HTML(movie.to_html5_video())
plt.gcf().clear()
Using this 1D wave as a startig point we can now construct the 2D wave. We set the wave direction to 215 degrees. Please note that you have to convert the direction to radians first. If the DFT_polar mode is used (which was set in the Wave1D object), the 2D wave spectrum is constructed from the 1D spectrum vs k obtained from the wave1D and a directional distirbution. This means that also the resolution settings of the wave1D were all maintained. Only for the number of points in the x- and y-direction we have to specify a new value again (which, to emphasis again, may be different from the number of nodes in k-space since we use DFT). Also the resolution in the theta-direction in the spectrum needs to be defined, which we put on 60.
with Timer(message="DFT polar") as t:
wave2D_1 = wf.Wave2D(wave1D=wave1D_1,
nx_points=128, ny_points=128,
Lx=500, Ly=500,
n_theta_nodes=60,
Theta_0=np.deg2rad(215),
Theta_s_spreading_factor=5,
)
wave2D_1.make_report()
We created a wave spectrum with its wave field in about 3 s. It takes long because we are using a DFT, which means that the calculation time for a wave is proportional to the total number of spatial point times the total number of wave vector nodes. The value is reported as DFT N x N when running make_report. We can see that it is over 12e6.
Let's plot the spectrum of our wave field
wave2D_1.plot_spectrum(plot_title="Jonswap Spectrum on Polar mesh", polar_projection=True,
use_contourf=True, shift_origin=False, r_axis_lim=(0, 0.175), r_label_position=315,
)
plt.show()
The spectral plot shows the peak of the jonswap spectrum at 225 degrees with its maximum at 0.06 rad/m, which corresponds with a wave length of 2pi/0.06 = 104.7 m. The angular frequency of this peak is sqrt(9.81 * 0.06) = 0.77 rad/s, which means that the peak period is about 2pi / 0.77 = 8 s. Indeed, this is the values for Tp we specified for the wave1D at the start of the script.
Let's have a look at the animated wave field.
wave1D_1.reset_time(nt_samples=20, delta_t=1)
movie = wave2D_1.animate_wave(plot_title="Jonswap Wave constructed with DFT on Polar Spectrum",
use_contourf=True, min_data_value=-2, max_data_value=2, interval=400)
HTML(movie.to_html5_video())
plt.gcf().clear()
We limitted ourself to 20 frames for the animation, otherwise it would take too long: a DFT with all 128 x 128 = 16384 nodes takes about 9 seconds per frame calculation. We can significantly reduce the number of wave vector nodes by taking the EqualEnergyBins settings. With the EqualEnergyBins the total energy of each bin will be the same, which will result in more narrow bin width around the peak of the spectrum. Also we set the flag use_subrange_energy_limits, which will clip all the spectral components as defined by the Subrange method (have a look at the examle_wave_field_modelling notebook for an example on a 1D wave). Now let's simulate the wave again
wave1D_2 = wf.Wave1D(n_kx_nodes=64, nx_points=251, Lx=500, wave_construction="DFTpolar", n_bins_equal_energy=32,
Tp=8, spectrum_type="jonswap", Hs=3, spectral_version="hmc",wave_selection="EqualEnergyBins",
use_subrange_energy_limits=True)
wave1D_2.make_report()
wave1D_2.plot_spectrum()
plt.show()
Compared to the full spectum of our first attempt were we used 128 wave vector nodes for the spectrum, with only 29 nodes we have a much better description of the peak in the spectrum due the higher density of nodes in this region. Using this spectral distribution we can now create a 2D spectrum:
with Timer(message="DFT polar EqualEnergyBins") as t:
wave2D_2 = wf.Wave2D(wave1D=wave1D_2,
Lx=500, Ly=500,
n_theta_nodes=60,
Theta_0=np.deg2rad(215),
Theta_s_spreading_factor=5,
)
wave2D_2.make_report()
wave2D_2.plot_spectrum(plot_title="Jonswap Spectrum on Polar mesh with Equal Energy", polar_projection=True,
use_contourf=True, shift_origin=False,r_axis_lim=(0, 0.175), r_label_position=315
)
plt.show()
The spectrum now only has the wave nodes around the peak. Also a selection in the circumferential direction was made. As a result, to total amount of wave numbers in the 2D spectrum is now n_k_r_nodes x n_theta_nodes = 29 x 27 = 783, compared to 7680 nodes in our first attempt. If we animate the frames we can compare the wave fields as well:
wave1D_2.reset_time(nt_samples=20, delta_t=1)
movie2 = wave2D_2.animate_wave(plot_title="Jonswap Wave constructed with DFT on Polar Spectrum with EqualEnergyBins",
use_contourf=True, min_data_value=-2, max_data_value=2, interval=400)
HTML(movie2.to_html5_video())
plt.gcf().clear()
The wave fields look very similar in its large scale structures, although of course the spectrum with the node selection is smoother and has a slightly lower Hs estimate from the wave height standard deviation. This is the price we pay for a speed up of simulation time of more than a factor 20.
Finally it is demonstrated how we achieve the same speed up factor as the EqualEnergyBins method without reducing the number of wave nodes by using an FFT spectrum in stead of a DFT. Let's again first define the one-dimensional wave:
wave1D_3 = wf.Wave1D(n_kx_nodes=128, nx_points=251, Lx=500, wave_construction="FFT",
Tp=8, spectrum_type="jonswap", Hs=3, spectral_version="hmc",wave_selection="All")
wave1D_3.make_report()
wave1D_3.plot_spectrum()
plt.show()
Since the 'FFT' wave_construction method was selected we now have created a spectrum symmetric around k=0. Based on this we can create the 2D wave using the 1D wave as an input. It it important to realise that this time the number of nodes in the x-domain is the same as in the k-domain. Also this time a Cartesian description of the 2D mesh is made instead of a polar description, which means that the number of nodes of the 2D wave is not based on the number of nodes of the 1D wave, but need to be redefined.Since for FFT the number of kx and ky nodes in wave vector domain are the same as the number of nx and ny nodes in spatial domain, we only define the mesh in spatial domain; the mesh in k-domain follows from that
with Timer(message="FFT mesh") as t:
wave2D_3 = wf.Wave2D(wave1D=wave1D_3,
nx_points=128, ny_points=128,
Lx=500, Ly=500,
Theta_0=np.deg2rad(215),
Theta_s_spreading_factor=5,
)
wave2D_3.make_report()
wave2D_3.plot_spectrum(plot_title="Jonswap Spectrum on Cartesian mesh using FFT", polar_projection=False,
use_contourf=True, shift_origin=True,r_axis_lim=(0, 0.175), r_label_position=315,
kx_min=-0.175, kx_max=0.175, ky_min=-0.175, ky_max=0.175
)
plt.show()
wave1D_3.reset_time(nt_samples=20, delta_t=1)
movie3 = wave2D_3.animate_wave(plot_title="Jonswap Wave constructed with FFT based on all nodes",
use_contourf=True, min_data_value=-2, max_data_value=2, interval=400)
HTML(movie3.to_html5_video())
plt.gcf().clear()
Let's see how the calculation time to propagate the wave with 1 time steps compare for the three different methods
wave1D_1.reset_time(nt_samples=10)
with Timer(message="Total time {}".format(wave2D_2.name)) as t:
while wave1D_1.time < wave1D_1.t_end:
with Timer(message="DFT 1 at t={:2d}. Hs = {:.2f} m".format(wave1D_1.time, 4 * wave2D_1.amplitude.std())) as t2:
wave2D_1.propagate_wave()
wave1D_2.reset_time(nt_samples=10)
with Timer(message="Total time {}".format(wave2D_2.name)) as t:
while wave1D_2.time < wave1D_2.t_end:
with Timer(message="DFT 2 at t={:2d}. Hs = {:.2f} m".format(wave1D_2.time, 4 * wave2D_2.amplitude.std())) as t2:
wave2D_2.propagate_wave()
wave1D_3.reset_time(nt_samples=10)
with Timer(message="Total time {}".format(wave2D_3.name)) as t:
while wave1D_3.time < wave1D_3.t_end:
with Timer(message="FFT at t={:2d}. Hs = {:.2f} m".format(wave1D_3.time, 4 * wave2D_3.amplitude.std())) as t2:
wave2D_3.propagate_wave()
The FFT is the big winner, which was to be expected: the NxM term for the DFT went from 125829120 for the full DFT (wave2D_1) to 3207168 for the DFT with wave node selection (wave2D_2) and finally to Nxlog(N) of 158991 for the FFT (wave2D_3). The calculation time get about a factor 20 times faster for each next wave simulation. While for the FFT we are not limiting the number of wave nodes. The conclusion is again: use FFT when simulating 2D wave fields