2D Wave field modelling

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

  1. Polar coordinates DFT Full: We define the Wave spectrum on a polar mesh (r, theta) and create the wave witth the "DFTpolar" wave_construction method
  2. Polar coordinates DFT Selection: We define the Wave spectrum on a polar mesh (r, theta) and create the wave witth the "DFTpolar" wave_construction method with a reduction of wave nodes by using an EqualEnergyBins algorithm
  3. Cartesian coordinates FFT Full: We define the Wave spectrum on a cartesian mesh (k_x, k_y)and then use the FFT or DFTcartesian wave_construction method
  4. Comparing Calculation times: Compare the calculation time for the three methods above

2D Wave using a polar mesh

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

In [1]:
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)
C:\Anaconda\lib\site-packages\statsmodels\compat\pandas.py:56: FutureWarning: The pandas.core.datetools module is deprecated and will be removed in a future version. Please use the pandas.tseries module instead.
  from pandas.core import datetools

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.

In [2]:
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()
Initialise jonswap 1D wave field
kx_max larger than nyquist belongin to dx=2.0: clipping kx to 1.5707963267948966
Name                                     : wave_jonswap_DFTpolar_All_1d
----------- Domain settings --------
Start x [m]                              : 0
End x [m]                                : 500
Length x [m]                             : 500
----------- Time settings --------
Start t [s]                              : 0
End t [s]                                : 1000000
Length t [s]                             : 1000000
Delta t [s]                              : 1.0
----------- Numerical resolutions --------
Number x - nodes                         : 251
Number kx - nodes                        : 128
Delta x [m]                              : 2.0
Delta kx min [rad/m]                     : 0.012368475014132851
Delta kx max [rad/m]                     : 0.012368475014133073
Delta kx first [rad/m]                   : 0.012368475014133044
k-min [rad/m]                            : 0.0
k-max [rad/m]                            : 1.5707963267948966
k-nyq [rad/m]                            : 1.5707963267948966
L_max [m]                                : 508.0
----------- Numerical methodds --------
Selection method                         : All
Construction method                      : DFTpolar

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

In [3]:
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:

In [4]:
wave1D_1.reset_time(nt_samples=500, delta_t=.1)
movie = wave1D_1.animate_wave()
HTML(movie.to_html5_video())
Out[4]:
In [5]:
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.

In [6]:
  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()
        
Initialise JonSwap 1D wave field
DFT polar            routine              :       9942 ms
Name                                     : wave_jonswap_DFTpolar_All_2d
----------- Domain settings --------
Start x [m]                              : 0
Start y [m]                              : 0
End x [m]                                : 500
End y [m]                                : 500
Length x [m]                             : 500
Length y [m]                             : 500
----------- Time settings --------
Start t [s]                              : 0
End t [s]                                : 50.0
Length t [s]                             : 50.0
Delta t [s]                              : 0.1
----------- Numerical resolutions --------
Number x - nodes                         : 128
Number y - nodes                         : 128
Total number of spatial points           : 16384
# Polar mesh specifications
Number k_r - nodes                       : 128
Number theta - nodes                     : 60
Number total nodes                       : 7680
----------- Numerical methods --------
Selection method                         : All
Construction method                      : DFTpolar
DFT N x N                                : 125829120
FFT N x log(N)                           : 158991.327688118

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

In [7]:
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()
<matplotlib.figure.Figure at 0xdcbe6d8>

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.

In [8]:
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())
Out[8]:
In [9]:
plt.gcf().clear()

2D Wave using a DFT on a cartesian mesh with node selection

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

In [10]:
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()
Initialise jonswap 1D wave field
kx_max larger than nyquist belongin to dx=2.0: clipping kx to 1.5707963267948966
Name                                     : wave_jonswap_DFTpolar_EqualEnergyBins_1d
----------- Domain settings --------
Start x [m]                              : 0
End x [m]                                : 500
Length x [m]                             : 500
----------- Time settings --------
Start t [s]                              : 0
End t [s]                                : 1000000
Length t [s]                             : 1000000
Delta t [s]                              : 1.0
----------- Numerical resolutions --------
Number x - nodes                         : 251
Number kx - nodes                        : 29
Delta x [m]                              : 2.0
Delta kx min [rad/m]                     : 0.001137212605196225
Delta kx max [rad/m]                     : 0.018088887024095307
Delta kx first [rad/m]                   : 0.024933275028490423
k-min [rad/m]                            : 0.049866550056980846
k-max [rad/m]                            : 0.15911623387244875
k-nyq [rad/m]                            : 1.5707963267948966
L_max [m]                                : 2558.9670281622657
----------- Numerical methodds --------
Selection method                         : EqualEnergyBins
Construction method                      : DFTpolar
C:\Anaconda\lib\site-packages\scipy\optimize\minpack.py:161: RuntimeWarning: The iteration is not making good progress, as measured by the 
  improvement from the last ten iterations.
  warnings.warn(msg, RuntimeWarning)
In [11]:
wave1D_2.plot_spectrum()
plt.show()
<matplotlib.figure.Figure at 0xde04518>

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:

In [12]:
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()
        
Initialise JonSwap 1D wave field
DFT polar EqualEnergyBins routine              :        263 ms
Name                                     : wave_jonswap_DFTpolar_EqualEnergyBins_2d
----------- Domain settings --------
Start x [m]                              : 0
Start y [m]                              : 0
End x [m]                                : 500
End y [m]                                : 500
Length x [m]                             : 500
Length y [m]                             : 500
----------- Time settings --------
Start t [s]                              : 0
End t [s]                                : 1000000
Length t [s]                             : 1000000
Delta t [s]                              : 1.0
----------- Numerical resolutions --------
Number x - nodes                         : 64
Number y - nodes                         : 64
Total number of spatial points           : 4096
# Polar mesh specifications
Number k_r - nodes                       : 29
Number theta - nodes                     : 27
Number total nodes                       : 783
----------- Numerical methods --------
Selection method                         : EqualEnergyBins
Construction method                      : DFTpolar
DFT N x N                                : 3207168
FFT N x log(N)                           : 34069.57021888243
In [13]:
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:

In [14]:
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())
Out[14]:
In [15]:
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.

2D wave simulation with using a FFT

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:

In [16]:
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()
Initialise jonswap 1D wave field
Name                                     : wave_jonswap_FFT_All_1d
----------- Domain settings --------
Start x [m]                              : 0
End x [m]                                : 500
Length x [m]                             : 500
----------- Time settings --------
Start t [s]                              : 0
End t [s]                                : 1000000
Length t [s]                             : 1000000
Delta t [s]                              : 1.0
----------- Numerical resolutions --------
Number x - nodes                         : 251
Number kx - nodes                        : 251
Delta x [m]                              : 2.0
Delta kx min [rad/m]                     : -3.129076348197005
Delta kx max [rad/m]                     : 0.012516305392788363
Delta kx first [rad/m]                   : 0.012516305392788021
k-min [rad/m]                            : -1.5645381740985025
k-max [rad/m]                            : 1.5645381740985025
k-nyq [rad/m]                            : 1.5707963267948966
L_max [m]                                : 502.0
----------- Numerical methodds --------
Selection method                         : All
Construction method                      : FFT
In [17]:
wave1D_3.plot_spectrum()
plt.show()
<matplotlib.figure.Figure at 0xe023630>

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

In [18]:
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()
        
Initialise JonSwap 1D wave field
FFT mesh             routine              :       2060 ms
Name                                     : wave_jonswap_FFT_All_2d
----------- Domain settings --------
Start x [m]                              : 0
Start y [m]                              : 0
End x [m]                                : 500
End y [m]                                : 500
Length x [m]                             : 500
Length y [m]                             : 500
----------- Time settings --------
Start t [s]                              : 0
End t [s]                                : 1000000
Length t [s]                             : 1000000
Delta t [s]                              : 1.0
----------- Numerical resolutions --------
Number x - nodes                         : 128
Number y - nodes                         : 128
Total number of spatial points           : 16384
# Cartesian mesh specifications
Number kx - nodes                        : 128
Number ky - nodes                        : 128
Delta x [m]                              : 3.937007874015748
Delta y [m]                              : 3.937007874015748
Delta kx min [rad/m]                     : -1.5834608721796803
Delta ky min [rad/m]                     : -1.5834608721796803
Delta kx max [rad/m]                     : 0.01246819584393466
Delta ky max [rad/m]                     : 0.01246819584393466
Delta kx first [rad/m]                   : 0.012468195843934491
Delta ky first [rad/m]                   : 0.012468195843934491
kx-min [rad/m]                           : -0.7979645340118074
ky-min [rad/m]                           : -0.7979645340118074
kx-max [rad/m]                           : 0.7854963381678729
ky-max [rad/m]                           : 0.7854963381678729
kx-nyq [rad/m]                           : 0.7979645340118074
ky-nyq [rad/m]                           : 0.7979645340118074
Lx_max [m]                               : 503.93700787401576
Ly_max [m]                               : 503.93700787401576
----------- Numerical methods --------
Selection method                         : All
Construction method                      : FFT
DFT N x N                                : 268435456
FFT N x log(N)                           : 158991.327688118
In [19]:
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()
In [20]:
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())
Out[20]:
In [21]:
plt.gcf().clear()

Comparing the calculation times

Let's see how the calculation time to propagate the wave with 1 time steps compare for the three different methods

In [22]:
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()
DFT 1 at t= 0. Hs = 3.06 m routine              :       8968 ms
DFT 1 at t= 1. Hs = 3.12 m routine              :       8881 ms
DFT 1 at t= 2. Hs = 3.13 m routine              :       9338 ms
DFT 1 at t= 3. Hs = 3.13 m routine              :       8706 ms
DFT 1 at t= 4. Hs = 3.11 m routine              :       9311 ms
DFT 1 at t= 5. Hs = 3.11 m routine              :       9356 ms
DFT 1 at t= 6. Hs = 3.13 m routine              :       8766 ms
DFT 1 at t= 7. Hs = 3.12 m routine              :       8831 ms
DFT 1 at t= 8. Hs = 3.10 m routine              :       9243 ms
DFT 1 at t= 9. Hs = 3.11 m routine              :       8461 ms
Total time wave_jonswap_DFTpolar_EqualEnergyBins_2d routine              :      89870 ms
In [23]:
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()
DFT 2 at t= 0. Hs = 2.83 m routine              :        245 ms
DFT 2 at t= 1. Hs = 2.90 m routine              :        233 ms
DFT 2 at t= 2. Hs = 2.92 m routine              :        233 ms
DFT 2 at t= 3. Hs = 2.92 m routine              :        233 ms
DFT 2 at t= 4. Hs = 2.92 m routine              :        235 ms
DFT 2 at t= 5. Hs = 2.92 m routine              :        223 ms
DFT 2 at t= 6. Hs = 2.93 m routine              :        230 ms
DFT 2 at t= 7. Hs = 2.93 m routine              :        228 ms
DFT 2 at t= 8. Hs = 2.92 m routine              :        223 ms
DFT 2 at t= 9. Hs = 2.92 m routine              :        230 ms
Total time wave_jonswap_DFTpolar_EqualEnergyBins_2d routine              :       2310 ms
In [24]:
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()
FFT at t= 0. Hs = 2.99 m routine              :          0 ms
FFT at t= 1. Hs = 2.99 m routine              :          0 ms
FFT at t= 2. Hs = 2.99 m routine              :          3 ms
FFT at t= 3. Hs = 2.99 m routine              :          0 ms
FFT at t= 4. Hs = 2.99 m routine              :          3 ms
FFT at t= 5. Hs = 2.99 m routine              :          5 ms
FFT at t= 6. Hs = 2.99 m routine              :          3 ms
FFT at t= 7. Hs = 2.99 m routine              :          8 ms
FFT at t= 8. Hs = 2.99 m routine              :         13 ms
FFT at t= 9. Hs = 2.99 m routine              :          0 ms
Total time wave_jonswap_FFT_All_2d routine              :         58 ms

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