AJMR-Python-Baird/EWR_Data/waves.py

1438 lines
43 KiB
Python

"""
Series of functions to manage wave information.
Author:
-------
Nearshore Modeling Group
Gabriel Garcia Medina
ggarcia@coas.oregonstate.edu
Log of edits:
-------------
April 2014 - Created module
Gabriel Garcia Medina (ggarcia@coas.oregonstate.edu)
External dependencies:
numpy
scipy
Internal dependencies:
gsignal
"""
from __future__ import division, print_function
# Import generic modules
import numpy as np
import scipy
import scipy.optimize
import scipy.signal
# Internal modules
import pynmd.data.signal as gsignal
import pynmd.data.angles as _gangles
# ===============================================================================
# Compute radian frequency from wave number
# ===============================================================================
def idispersion(k, h):
'''
Inverse linear dispersion relation
USAGE:
------
sigma = idispersion(k,h)
INPUT:
------
k : Wave number [m**-1]
h : water depth [m]
OUTPUT:
-------
sigma : radian frequency [Hz]
'''
# Get radian wave frequency
sigma = (9.81 * k * np.tanh(k * h)) ** 0.5
# Return stokes velocities
return sigma
# ===============================================================================
# Compute wave number
# ===============================================================================
def dispersion(period, h, u=None):
'''
Computes the linear dispersion relation.
USAGE:
------
k = dispersion(period,h,u)
Input:
------
Period : Wave period [s]
h : Water depth [m]
u : (Optional) Ambient current velocity [m/s]
RETURNS:
--------
k : Wave number (2*pi/wave_length) [m**-1]
'''
if u is None:
u = 0.0
# Initialize wave number
# k = np.zeros(len(period))
sigma = (2.0 * np.pi) / period
# kinit = (((sigma**2)*h/9.81)*
# ((1/np.tanh(((sigma**2)/9.81*h)**0.75))**(2.0/3.0)))
kinit = 0.0001
def f(k, sigma, h, u):
y = (9.81 * k * np.tanh(k * h)) ** 0.5 - sigma + u * k
return y
if h <= 0:
k = np.NAN
else:
try:
k = scipy.optimize.newton(f, kinit, fprime=None,
args=(sigma, h, u), maxiter=100)
except RuntimeError:
k = np.NAN
return k
# ===============================================================================
# Compute wave number using the Kirby and Dalrymple (1986) composite equation
# ===============================================================================
def dispersion_kd86(period, h, wh, u=None):
'''
Return the wave number from the composite dispersion relation by
Kirby and Dalrymple (1986).
USAGE:
------
k = dispersion_kd86(period,h,wh,u)
Input:
------
Period : Wave period [s]
h : Water depth [m]
wh : Wave height [m]
u : (Optional) Ambient current velocity [m/s]
RETURNS:
--------
k : Wave number (2*pi/wave_length) [m**-1]
CELERITY EQUATION:
------------------
(c-u)^2 = g / k * (1 + f1 * eps^2 * D) tanh( k * h + f2 * eps)
f1 = tanh(k*h)^5
f2 = (k*h/sinh(k*h))^4
eps = k*wh/2
D = (8 + cosh(4*k*h) - 2 * tanh(k*h)^2)/(8*sinh(k*h)^4)
REFERENCES:
-----------
Catalan, P., and M. C. Haller, 2008: Remote sensing of breaking wave phase
speeds with application to non-linear depth inversions. Coastal
Engineering, 55, 93 - 111.
Kirby, J. T., and R. A. Dalrymple, 1986: An approximate model for nonlinear
dispersion in monochromatic wave propagation models. Coastal
Engineering, 9, 545-561.
'''
# Depth averaged ambient velocity
if u is None:
u = 0
# Compute radian frequency
sigma = (2.0 * np.pi) / period
# Initialize wave number
# kinit = 0.0001 # Manually initialize
kinit = dispersion(period, h, u) # Initialize with Airy dispersion relation
# Kirby and Dalrymple (1986) dispersion function
def f(k, sigma, h, wh, u):
eps = k * wh / 2
f1 = np.tanh(k * h) ** 5
f2 = (k * h / np.sinh(k * h)) ** 4
D = ((8.0 + np.cosh(4.0 * k * h) - 2.0 * (np.tanh(k * h) ** 2)) /
(8.0 * (np.sinh(k * h) ** 4)))
y = ((9.81 * k * np.tanh(k * h + f2 * eps) * (1.0 + f1 * (eps ** 2) * D)) ** 0.5 +
u * k - sigma)
return y
if h <= 0:
k = np.NAN
else:
try:
k = scipy.optimize.newton(f, kinit, fprime=None,
args=(sigma, h, wh, u), maxiter=100)
except RuntimeError:
k = np.NAN
return k
# ===============================================================================
# Compute wave number using the Booij
# ===============================================================================
def dispersion_booij(period, h, wh, u=None):
'''
Return the wave number from the composite dispersion relation by
Booij (1981).
USAGE:
------
k = dispersion_booij(period,h,wh,u)
Input:
------
Period : Wave period [s]
h : Water depth [m]
wh : Wave height [m]
u : (Optional) Ambient current velocity [m/s]
RETURNS:
--------
k : Wave number (2*pi/wave_length) [m**-1]
CELERITY EQUATION:
------------------
(c-u)^2 = g / k * tanh(k*(h+wh/2))
REFERENCES:
-----------
Booij, N. 1981: Gravity waves on water with non-uniform depth and current.
Tech. Rep. No. 81-1. Dept. Civil Engineering, Delft University of
Technology.
Catalan, P., and M. C. Haller, 2008: Remote sensing of breaking wave phase
speeds with application to non-linear depth inversions. Coastal
Engineering, 55, 93 - 111.
'''
# Depth averaged ambient velocity
if u is None:
u = 0.0
# Compute radian frequency
sigma = (2.0 * np.pi) / period
# Initialize wave number
# kinit = 0.0001 # Manually initialize
kinit = dispersion(period, h + wh / 2, u) # Initialize with Airy dispersion relation
# Booij (1981) dispersion function
def f(k, sigma, h, wh, u):
y = (9.81 * k * np.tanh(k * (h + wh / 2.0))) ** 0.5 + u * k - sigma
return y
if h <= 0:
k = np.NAN
else:
try:
k = scipy.optimize.newton(f, kinit, fprime=None,
args=(sigma, h, wh, u), maxiter=100)
except RuntimeError:
k = np.NAN
return k
# ===============================================================================
# Compute dispersion relation from Nwogu's equations
# ===============================================================================
def dispersion_nwogu(period, h, alpha):
'''
Compute dispersion relation for the Boussinesq equations based on
Nwogu 1993.
PARAMETERS:
-----------
period : Wave period [s]
h : Water depth [m]
alpha : Non-dimensional wave steepness parameter (see notes)
RETURNS:
--------
k : Wave number [m**-1]
NOTES:
------
alpha = 0 for celerity at the surface
alpha = -1/3 solves the traditional depth averaged equations.
alpha = -0.39 gives similar results to linear wave theory (Nwogu 1993)
REFERENCES:
-----------
Nwogu, O., 1993: Alternative Form of Boussinesq Equations for Nearshore
Wave Propagation. Journal of Waterway, Port, Coastal, and Ocean
Engineering, 119, 618-638.
'''
# Compute randian frequency
sigma = (2.0 * np.pi) / period
# Initialize wave number
# kinit = dispersion(period,h) # Linear wave theory
kinit = sigma ** 2 / 9.81 # Deep water equivalent
# kinit = 0.05 # Hard code parameter
# Define the dispersion relation function
def bd(k, sigma, h, alpha):
# y = 9.81 * (k**2) * h * (1.0 - 1.0/3.0 * (k*h)**2) - sigma**2
y = (9.81 * (k ** 2) * h * (1.0 - (alpha + 1.0 / 3.0) * ((k * h) ** 2)) /
(1.0 - alpha * ((k * h) ** 2)) - sigma ** 2)
return y
# Use Newton-Rhapson method to find the wave number
if h <= 0:
k = np.NAN
else:
try:
k = scipy.optimize.newton(bd, kinit, fprime=None,
args=(sigma, h, alpha), maxiter=1000)
except RuntimeError:
k = np.NAN
return k
# ===============================================================================
# Linear wave approximations
# ===============================================================================
def shallow_water_depth(period):
'''
h_shallow = shallow_water_depth(period)
Find where the waves with the given period enter shallow water according to
linear wave theory
PARAMETERS:
-----------
period : wave period [s]
RETURNS:
--------
h_shallow : Water depth where shallow water approximation is valid [m]
'''
# Initialize shallow water depth
hinit = 2.0
# Define shallow water limit
def swd(h, period):
y = dispersion(period, h) * h - np.pi / 10.0
return y
# Find zeros of the swd function
try:
h = scipy.optimize.newton(swd, hinit, fprime=None,
args=(period,), maxiter=100)
except RuntimeError:
h = np.NAN
return h
def deep_water_period(depth):
'''
t_deep = deep_water_depth(depth)
Find the period of waves entering intermediate water depth at a give depth
according to linear wave theory (i.e. kh = pi)
PARAMETERS:
-----------
depth : water depth [m]
RETURNS:
--------
t_deep : waves of this period and shorter do not feel the bottom [s]
'''
return 2.0 * np.pi / idispersion(np.pi / depth, depth)
# ===============================================================================
# Wave length
# ===============================================================================
def wave_length(period, h, verbose=True):
'''
Compute wave length using linear wave theory
Parameters
----------
period : wave period [s]
h : water depth [m]
Results
-------
wl_int : real wave length [m]
Screen output
-------------
wl_deep : deep water wave length [m]
wl_sha : shallow water wave length [m]
'''
wl_deep = 9.81 * period ** 2 / 2.0 / np.pi
wl_sha = period * np.sqrt(9.81 * h)
k = dispersion(period, h)
wl_int = 9.81 / 2.0 / np.pi * period ** 2 * np.tanh(k * h)
if verbose:
print(' ')
print('---------------------------------------------------------')
print('Wave Length deep water approx = ' + np.str(wl_deep) + ' m')
print('Wave Length shallow water approx = ' + np.str(wl_sha) + ' m')
print('Wave Length linear wave theory = ' + np.str(wl_int) + ' m')
print('---------------------------------------------------------')
print(' ')
return wl_int
# ===============================================================================
# Compute wave number using the Kirby and Dalrymple (1986) composite equation
# ===============================================================================
def celerity(period, h, u=None):
'''
Find celerity and group velocity from linear wave theory
USAGE:
------
c,n,cg = celerity(period,h,u)
Input:
------
period : Wave period [s]
h : Water depth [m]
u : (Optional) Ambient current velocity [m/s]
RETURNS:
--------
c : celerity [m/s]
n : cg = c*n
cg : group velocity [m/s]
'''
# Radian frequency
sigma = 2.0 * np.pi / period
# Find wave number
k = dispersion(period, h, u)
# Celerity
c = sigma / k
# Group velocity
n = 0.5 + k * h / np.sinh(2 * k * h)
cg = c * n
return c, n, cg
# ===============================================================================
# Compute stokes velocities from linear wave theory
# ===============================================================================
def uvstokes(Hwave, Dwave, Lwave, WDepth, VertDisc):
'''
Compute stokes velocities
Parameters
----------
Hwave : Wave height [m]
Dwave : Wave direction
Lwave : Wave length [m]
WDepth : Water depth [m]
VertDisc : Number of vertical layers
Returns
-------
ustokes, vstokes
'''
k = 2 * np.pi / Lwave # Compute wave number
deno = np.sinh(2.0 * k * WDepth) # sinh(2kh)
sigma = idispersion(k, WDepth) # Radian frequency
# Vertical discretization
z = np.linspace(0, WDepth, VertDisc)
nume = np.cosh(2.0 * k * (WDepth - z))
# Get zonal and meridional components
kx = k * np.cos((270 - Dwave) * np.pi / 180)
ky = k * np.sin((270 - Dwave) * np.pi / 180)
# Stokes drift
ustokes = (Hwave ** 2) / 4.0 * 1027.0 * 9.81 / sigma * kx * nume / deno
vstokes = (Hwave ** 2) / 4.0 * 1027.0 * 9.81 / sigma * ky * nume / deno
# Return stokes velocities
return ustokes, vstokes
# ===============================================================================
# TMA Spectrum
# ===============================================================================
def tma(freq_peak, gamma, h, Hmo, freq_min=0.01, freq_max=1.0, freq_int=0.001,
zeroth=True):
'''
Function to generate TMA spectrum
Parameters
----------
peak_freq : Peak frequency [Hz]
gamma : Peak enhancement factor (3.3 is a good guess)
h : water depth [m]
Hmo : Significant wave height [m]
Optional Parameters
-------------------
freq_min : Minimum frequency to compute the spectrum [Hz]
freq_max : Maximum frequency to compute the spectrum [Hz]
freq_int : Frequency inteval [Hz]
zeroth : Prepend zeroth frequency (Default = True)
Default values are 0.01, 1.0, and 0.001 Hz, respectively.
Returns
-------
s_tma : Tma spectrum as a function of frequency.
freq : Frequency axis [Hz]
References
----------
Bouws, E., H. Gunther, W. Rosenthal, and C. L. Vincent, 1985: Similarity
of the wind wave spectrum in finite depth water 1. Spectral form.
Journal of Geophysical Research, 90 (C1), 975-986.
Hughes, S. 1985: The TMA shallow-water spectrum, description and
applications. Technical Report CERC-84-7.
Notes
-----
- The units of the spectrum still do not make sense to me, must be verified.
- No scaling applied, alpha = 1
- Zeroth frequency is added to the spectrum
'''
# For testing only
# freq_peak = 0.1
# gamma = 3.3
# freq_min = 0.01
# freq_max = 1.0
# freq_int = 0.001
# h = 10.0
# Hmo = 1.0
# Compute frequency vector
freq = np.arange(freq_min, freq_max + freq_int, freq_int)
# Constants for peak enhancement factor
delta = np.ones_like(freq) * 0.07
delta[freq > freq_peak] = 0.09
# Compute alpha parameter (equation 26,27) TMA report
# wlen = 2.0*np.pi/dispersion(freq_peak**-1,h)
# alpha = (2.0 * np.pi* Hmo / wlen)**2
alpha = 1.0
# TMA scaling factor
omh = 2.0 * np.pi * freq * (h / 9.81) ** 0.5
phi = 1.0 - 0.5 * (2.0 - omh) ** 2
phi[omh < 1.0] = (0.5 * omh ** 2)[omh < 1.0]
phi[omh > 2.0] = 1.0
# Generate spectrum
s_tma = (alpha * 9.81 ** 2 * (2.0 * np.pi) ** -4 * (freq ** -5) * phi *
np.exp(-1.25 * (freq_peak / freq) ** 4) *
gamma ** np.exp(-1.0 * ((freq - freq_peak) ** 2)
/ (2.0 * (delta ** 2) * freq_peak ** 2)))
# Add zeroth frequency
if zeroth:
s_tma = np.r_[np.array([0.0]), spec_tma]
freq = np.r_[np.array([0.0]), freq]
# End of function
return s_tma, freq
# ===============================================================================
# JONSWAP Spectrum
# ===============================================================================
def jonswap(freq_peak, Hmo, gamma=3.3, freq_min=0.01, freq_max=1.0,
freq_int=0.001, goda=False, zeroth=True):
'''
Function to generate JONSWAP spectrum
Parameters
----------
peak_freq : Peak frequency [Hz]
Hmo : Significant wave height [m]
Optional Parameters
-------------------
gamma : Peak enhancement factor (defaults to 3.3)
freq_min : Minimum frequency to compute the spectrum [Hz]
freq_max : Maximum frequency to compute the spectrum [Hz]
freq_int : Frequency inteval [Hz]
goda : Use Y. Goda's approximation to the Jonswap spectrum.
If false we force the scaling of the spectrum to give the
same wave height passed as argument.
zeroth : Prepend zeroth frequency (Default = True)
Returns
-------
spec_jonswap : JONSWAP spectrum as a function of frequency.
freq : Frequency vector [Hz]
References
----------
Many, but a good description can be found in:
Y. Goda, Random Seas and Design of Maritime Structures.
Notes
-----
- Default frequency values are 0.01, 1.0, and 0.001 Hz.
'''
# For code development only -----------------------------------------------
# freq_peak = 1.0/10.0
# gamma = 3.3
# freq_min = 0.01
# freq_max = 0.5
# freq_int = 0.001
# Hmo = 1.0
# -------------------------------------------------------------------------
# Create frequency vector
freq = np.arange(freq_min, freq_max + freq_int, freq_int)
# Constants for peak enhancement factor
sigma = np.ones_like(freq) * 0.07
sigma[freq > freq_peak] = 0.09
# Goda's formulation
if goda:
# Beta parameter
beta = (0.0624 / (0.230 + 0.0336 * gamma - 0.185 * ((1.9 + gamma) ** -1)) *
(1.094 - 0.01915 * np.log(gamma)))
# Generate spectrum
spec_jonswap = (beta * (Hmo ** 2) * (freq_peak ** 4) * (freq ** -5) *
np.exp(-1.25 * ((freq / freq_peak) ** -4)) *
gamma ** (np.exp(-1.0 * (freq / freq_peak - 1.0) ** 2 /
(2.0 * sigma ** 2))))
else:
# Generate spectrum
spec_jonswap = ((freq ** -5) *
np.exp(-1.25 * ((freq / freq_peak) ** -4)) *
gamma ** (np.exp(-1.0 * (freq / freq_peak - 1.0) ** 2 /
(2.0 * sigma ** 2))))
# Scale parameter to match wave height energy in deep water
# I am not sure this is the right way to proceed but I'll still do it.
alpha = 1.0 / 16.0 * Hmo ** 2 / np.trapz(spec_jonswap, freq)
spec_jonswap = spec_jonswap * alpha
# Add zeroth frequency and expand the spectrum
if zeroth:
spec_jonswap_old = np.r_[np.array([0.0]), spec_jonswap]
freq_old = np.r_[np.array([0.0]), freq]
freq = np.arange(0.0, freq_max + freq_int, freq_int)
spec_jonswap = np.interp(freq, freq_old, spec_jonswap_old)
# End of function
return spec_jonswap, freq
# ===============================================================================
# Add directional distribution to spectrum
# ===============================================================================
def directional_spreading(spec, peak_dir, m, dirs=None):
"""
This function computes a directionally spread spectrum from a frequency
spectrum passed to the function.
PARAMETERS:
-----------
spec : frequency spectrum (as vector)
peak_dir : Peak wave direction in Nautical convention [degrees]
m : Directional width [cos(theta)**2m]
dirs : (Optional) vector of directions. If not given, the spectrum
will be computed every 5 degrees.
RETURNS:
--------
dir_spec : Directional spectrum in the same units given by the input
spectrum by degrees.
dirs : Vector with directions
NOTES:
------
Nautical convention refers to the direction the waves (wind) are coming
(is blowing) from with respect to the true north measured clockwise.
To recover the significant wave height
4.004 * np.trapz(np.sum(dir_spec,axis=-1)*(dirs[2]-dirs[1]),freq)**0.5
"""
# If direction vector is not passed as argument the directional distribution
# will be computed every five degrees.
try:
if dirs == None:
dirs = np.arange(0, 360, 5)
except:
pass
# Change directions to radians for internal computations
peak_dir_r = np.pi / 180.0 * peak_dir
dirs_r = np.pi / 180.0 * dirs
# Compute directional spread
g = np.cos(0.5 * (dirs_r - peak_dir_r)) ** (2 * m)
# Normalize the directional spread so that no energy is added
# Scaling is done in direction space to account for the units
# In other workds dir_spec will be of [m2/Hz-deg]
dth = dirs[2] - dirs[1]
g /= (np.sum(g) * dth)
# Generate the directionally spread spectrum
dir_spec = np.zeros((spec.shape[0], dirs.shape[0]))
for aa in range(spec.shape[0]):
dir_spec[aa, :] = spec[aa] * g
# Return directional spectrum
return dir_spec, dirs
# ===============================================================================
# Compute bulk wave parameters from water surface elevation time series
# ===============================================================================
def eta_bulk_params(eta, ot, band_ave=False, window=False, IGBand=[0.005, 0.05]):
"""
Compute bulk wave parameters from water surface elevation time series.
Parameters:
-----------
eta : Water surface elevation time series at a point [m]
ot : Time vector [s]
band_ave : (Optional) Bin average stencil
Window : (Optional) Application of a hanning window (True or False)
IGBand : (Optional) Infragravity wave frequency band
defaults to 0.005-0.05 Hz
Output:
-------
Dictionary containing
freq : Spectral frequencies [Hz]
spec : Wave variance spectrum [m**2/Hz]
cl : 95% confidence levels on the spectral estimates
Hs : Significant wave height [m]
H1 : Mean wave height [m]
Tp : Peak wave period [s]
Tp_fit : Peak wave period computed from second order polynomial fit
near Tp[s]
Tm01 : First moment wave period [s]
Tm02 : Second moment wave period [s]
Te : Energy period [s]
Sw : Spectral width (m0*m2/m1/m1 - 1)**2
H1_t : Mean wave height computed in the time domain [m]
T1_t : Mean wave period computed in the time domain [s]
Tm01IG : First moment wave period over the infragravity frequency band
TpIG : Peak wave period over the infragravity frequency band [s]
skew_t : Wave skewness from time domain
Notes:
------
mn are the different spectral moments
"""
# Remove mean from the data
etaw = eta - eta.mean()
# Compute variance of original time series
var_ts = np.var(etaw)
# Compute in time domain
[wh, wp, zcind] = whwpts(ot, eta)
# Wave skewness
skew_t = np.mean(etaw ** 3) / (np.mean(etaw ** 2) ** 1.5)
# Data windowing
if window:
tmpwindow = scipy.signal.hanning(etaw.shape[0])
etaw *= tmpwindow
# Compute variance spectrum
freq, spec = gsignal.psdraw(etaw, np.mean(ot[1:] - ot[:-1]))
# If data has been windowed we must boost the variance of the spectrum
# to match the original time series
if window:
# Compute variance of from the psd
var_psd = np.sum(spec) * (freq[2] - freq[1])
# Adjust variance from the windowed time series
spec *= var_ts / var_psd
# Band averaging if requested
if band_ave:
[freq, spec] = gsignal.band_averaging(spec, freq, band_ave)
else:
band_ave = 1
# Compute confidence levels on the spectral parameters, to estimate noise
# threshold.
conf_lev = 0.95
alpha = 1.0 - conf_lev
cl_upper = scipy.stats.chi2.ppf(alpha / 2, band_ave * 2)
cl_lower = scipy.stats.chi2.ppf(1.0 - alpha / 2.0, band_ave * 2)
cl = np.array([spec - spec * band_ave * 2.0 / cl_lower,
spec * band_ave * 2.0 / cl_upper - spec])
# Compute bulk wave parameters
bwp = fspec_bulk_params(freq, spec, IGBand)
bwp['freq'] = freq
bwp['spec'] = spec
bwp['cl'] = cl
bwp['H1_t'] = np.nanmean(wh)
bwp['T1_t'] = np.nanmean(wp)
bwp['skew_t'] = skew_t
return bwp
# ===============================================================================
# Function to compute bulk wave parameters from frequency spectrum
# ===============================================================================
def fspec_bulk_params(freq, spec, IGBand=[0.005, 0.05], zeroth=True):
"""
Function to compute bulk wave parameters from frequency spectrum
Parameters:
-----------
freq : Vector of spectral frequencies [Hz]
spec : Frequency spectrum [m2/Hz]
IGBand : Infragravity wave frequency cutoff
defaults to 0.005-0.05 Hz
zeroth : If true will remove the first frequency from the analysis
Returns:
--------
Dictionary containing bulk wave parameters
Hs : Significant wave height [m]
H1 : Mean wave height [m]
Tp : Peak wave period [s]
Tp_fit : Peak wave period computed from second order polynomial fit
near Tp[s]
Tm01 : First moment wave period [s]
Tm02 : Second moment wave period [s]
Te : Energy period [s]
Sw : Spectral width (m0*m2/m1/m1 - 1)**2
Tm01IG : First moment wave period over the infragravity frequency band
TpIG : Peak wave period over the infragravity frequency band [s]
HsIG : Significant wave height in the infragravity frequency band [m]
Notes:
------
- mn are the different spectral moments
"""
# Remove zeroth frequencies ------------------------------------------------
if zeroth:
spec = spec[1:]
freq = freq[1:]
# Compute spectral moments
moment0 = np.trapz(spec, freq, axis=-1)
moment1 = np.trapz(spec * freq, freq, axis=-1)
moment2 = np.trapz(spec * (freq) ** 2, freq, axis=-1)
momentn1 = np.trapz(spec * (freq) ** -1, freq, axis=-1)
# Wave heights
Hs = 4.004 * (moment0) ** 0.5
H1 = Hs * 2.0 / 3.0
# Spectral width
Sw = (moment0 * moment2 / moment1 / moment1 - 1) ** 0.5
# Spectral periods -----------------------
# Energy period
Te = momentn1 / moment0
# Mean wave period
Tm01 = moment0 / moment1
# Second moment period
Tm02 = (moment0 / moment2) ** 0.5
# Peak wave period
freq_max_ind = np.argmax(spec)
Tp = freq[freq_max_ind] ** -1
# Peak wave period using a quadratic fit over the largest frequencies
if freq_max_ind == 0:
Tp_fit = np.nan
elif freq_max_ind == freq.shape[0] - 1:
Tp_fit = np.nan
else:
minfreq = freq_max_ind - 1
maxfreq = freq_max_ind + 2
tmp_fit = np.polyfit(freq[minfreq:maxfreq], spec[minfreq:maxfreq], 2)
Tp_fit = (-1.0 * tmp_fit[1] / (2.0 * tmp_fit[0])) ** -1
# Infragravity wave periods ------------------------------------------------
igInd = np.logical_and(freq >= IGBand[0], freq < IGBand[1])
moment0 = np.trapz(spec[igInd], freq[igInd], axis=-1)
moment1 = np.trapz(spec[igInd] * freq[igInd], freq[igInd], axis=-1)
Tm01IG = moment0 / moment1
HsIG = 4.004 * (moment0) ** 0.5
freq_max_ind = np.argmax(spec[igInd])
TpIG = freq[igInd][freq_max_ind] ** -1
# Exit function
return {'Hs': Hs, 'H1': H1, 'Tp': Tp, 'Tp_fit': Tp_fit, 'Tm01': Tm01, 'Tm02': Tm02,
'Te': Te, 'Sw': Sw, 'Tm01IG': Tm01IG, 'TpIG': TpIG, 'HsIG': HsIG}
# ===============================================================================
# Bulk parameters from a directional spectrum
# ===============================================================================
def spec_bulk_params(freq, dirs, spec, IGBand=[0.005, 0.05], zeroth=True):
"""
Function to compute bulk wave parameters from frequency-direction spectrum
Parameters:
-----------
freq : Vector of spectral frequencies [Hz]
dirs : Vector of directions (nautical convention) [Deg or Rad]
spec : Wave spectrum [m2/Hz/Deg or m2/Hz/Deg]
The shape must be [freq,dirs]
IGBand : Infragravity wave frequency cutoff
defaults to 0.005-0.05 Hz
zeroth : If true will remove the first frequency from the analysis
Returns:
--------
Dictionary containing bulk wave parameters
Hs : Significant wave height [m]
H1 : Mean wave height [m]
Tp : Peak wave period [s]
Tp_fit : Peak wave period computed from second order polynomial fit
near Tp[s]
Tm01 : First moment wave period [s]
Tm02 : Second moment wave period [s]
Te : Energy period [s]
Sw : Spectral width (m0*m2/m1/m1 - 1)**2
Tm01IG : First moment wave period over the infragravity frequency band
TpIG : Peak wave period over the infragravity frequency band [s]
HsIG : Significant wave height in the infragravity frequency band [m]
Dm : Mean wave direction, second moment (Kuik et al. 1988)
Dp : Peak wave direction (computed from directional spectrum)
Dp_fit : Peak wave direction (computed using second order
polynomial fit around the peak of the directional spectrum)
Notes:
------
- mn are the different spectral moments
- First frequency will be discarded from the analysis. It is assumed to be
the zeroth-frequency.
REFERENCES:
-----------
Kuik, A.J., G.P. Van Vledder, and L.H. Holthuijsen, 1988: A method for the
routine analysis of pitch-and-roll buoy wave data. Journal of Physical
Oceanography, 18(7), pp.1020-1034.
"""
# Get directional properties
dirSpec = np.trapz(spec, freq, axis=0)
dth = np.abs(dirs[2] - dirs[1])
# Find peak wave directon
Dp = dirs[np.argmax(dirSpec)]
dirDict = dict(Dp=Dp)
# Peak wave direction using a quadratic fit over the largest directions
dir_max_ind = np.argmax(dirSpec)
if dir_max_ind == 0:
tmp_fit = np.polyfit([dirs[0] - dth, dirs[0], dirs[1]],
[dirSpec[-1], dirSpec[0], dirSpec[1]], 2)
elif dir_max_ind == dirs.shape[0] - 1:
tmp_fit = np.polyfit([dirs[-2], dirs[-1], dirs[-1] + dth],
[dirSpec[-2], dirSpec[-1], dirSpec[0]], 2)
else:
minDirInd = dir_max_ind - 1
maxDirInd = dir_max_ind + 2
tmp_fit = np.polyfit(dirs[minDirInd:maxDirInd], dirSpec[minDirInd:maxDirInd], 2)
Dp_fit = -1.0 * tmp_fit[1] / (2.0 * tmp_fit[0])
dirDict.update(Dp_fit=_gangles.wrapto360(Dp_fit))
# Find mean wave direction (Kuik et al 1988)
a1 = np.trapz(dirSpec * np.cos((270.0 - dirs) * np.pi / 180), dirs)
b1 = np.trapz(dirSpec * np.sin((270.0 - dirs) * np.pi / 180), dirs)
# Mean wave direction in nautical coordinates
Dm = _gangles.wrapto360(270.0 - np.arctan2(b1, a1) * 180.0 / np.pi)
dirDict.update(Dm=Dm)
# Get the parameter from the frequency spectrum
# freqSpec = np.trapz(spec,dirs,axis=-1)
freqSpec = np.sum(spec, axis=-1) * dth
bp = fspec_bulk_params(freq, freqSpec, IGBand, zeroth=zeroth)
bp.update(dirDict)
return bp
# ===============================================================================
# Wave Height and Period From time series
# ===============================================================================
def whwpts(t, x, d='up'):
'''
Function to compute the wave height and wave period from a given time
series.
USAGE:
------
[wh,wp,zcind] = whwpts(t,x,d)
PARAMETERS:
-----------
t : Time vector
x : Water surface elevation time series
d : Accepts 'up' or 'down' for zero upcrossing or downcrossing,
respectively.
RETURNS:
--------
wh : Wave height time series
wp : Wave period time series
zcind : zero-crossing index
METHODS:
--------
1. Find zero crossings
2. Find time difference between zero crossing to get wp
3. Find the amplitude of the time series between each zero crossing to find
wh.
NOTES:
------
- x and t must have the same length
'''
# Find the zero crossings
zcross = gsignal.zero_crossing(x, d=d)
# Find the wave period
wp = t[zcross[1:]] - t[zcross[:-1]]
# Find wave height
wh = np.zeros_like(wp)
for aa in range(wh.shape[0]):
wh[aa] = (np.max(x[zcross[aa]:zcross[aa + 1]]) -
np.min(x[zcross[aa]:zcross[aa + 1]]))
return wh, wp, zcross
# ===============================================================================
# Iribarrren number
# ===============================================================================
def iribarren(m, H, wl, verbose=False):
"""
Function to compute the Iribarren number (aka surf similarity parameter) for
deep water conditions
USAGE:
------
ssp = iribarren(m,H,wl,verbose)
PARAMETERS:
-----------
m : Beach slope
H : Wave height [m]
wl : Wave length [m]
verbose : Display result in the terminal (Optional defaults to False)
RETURNS:
--------
ssp : surf similarity parameter
FORMULA:
--------
ssp = tan(m)/(H/wl)**0.5
NOTES:
------
- ssp > 3.3: Reflective beach (surging or collapsing breakers)
- 0.5 < ssp < 3.3 : Intermediate beach (plunging breaker)
- ssp < 0.5 : Dissipative beach (spilling breaker)
"""
# Compute iribarren number
ssp = np.tan(m) / (H / wl) ** 0.5
# Display message
if verbose:
if ssp >= 3.3:
print('ssp = ' + np.str(ssp) + ': Reflective conditions')
elif ssp < 0.5:
print('ssp = ' + np.str(ssp) + ': Dissipative conditions')
else:
print('ssp = ' + np.str(ssp) + ': Intermediate conditions')
return ssp
# ===============================================================================
# Battjes Parameter
# ===============================================================================
def battjes04(m, h, igFreq, verbose=False):
'''
Function to compute the normalized bed slope proposed by Battjes et al 2004
USAGE:
------
nbd = battjes04(m,h,igFreq,verbose)
PARAMETERS:
-----------
m : Beach slope
h : Characteristic water depth in shoaling region [m]
igFreq : infragravity wave frequency [1/s]
verbose : Display result in the terminal (Optional defaults to False)
RETURNS:
--------
nbd : Normalized bed slope
FORMULA:
--------
nbd = m/(2 * pi * igFreq) * (g/h)**0.5
NOTES:
------
The next values are given if the breaking depth is substituted for h
- nbd < 0.3: Mild slope regime, were breaking generation is not as important
- nbd > 0.3: Steep slope regime
References:
-----------
Battjes , J. A., H. J. Bakkenes, T. T. Janssen, and A. R. van Dongeren,
2004: Shoaling of subharmonic gravity waves. Journal of Geophysical
Research, 109, C02009, doi:10.1029/2003JC001863.
'''
# Compute iribarren number
nbd = m / (2 * np.pi * igFreq) * (9.81 / h) ** 0.5
# Display message
if verbose:
if nbd < 0.3:
print('nbd = ' + np.str(nbd) + ': Mild slope')
else:
print('nbd = ' + np.str(nbd) + ': Steep slope')
return nbd
# ===============================================================================
# Baldock Parameter
# ===============================================================================
def baldock12(m, h, igFreq, H, wl, verbose=False):
'''
Function to compute the surfbeat similarity parameter by Baldock 2012
USAGE:
------
sbs = baldock12(m,h,igFreq,H,wl,verbose)
PARAMETERS:
-----------
m : Beach slope
h : Characteristic water depth in shoaling region [m]
igFreq : infragravity wave frequency [1/s]
H : Shortwave offshore wave height [m]
wl : Offshore wave length [m]
verbose : Display result in the terminal (Optional defaults to False)
RETURNS:
--------
sbs : Surfbeat similarity parameter
FORMULA:
--------
sbs = m/(2 * pi * igFreq) * (g/h)**0.5 * (H/wl)**0.5
NOTES:
------
Really no clear guidance is given, however the smallest the number the least
effective breakpoint generation is.
References:
-----------
Baldock, T. E., 2012: Dissipation of incident forced long waves in the
surf zone - Implications for the concept of "bound" wave realease at
short wave breaking. Coastal Engineering, 276-285.
'''
# Compute iribarren number
sbs = m / (2 * np.pi * igFreq) * (9.81 / h) ** 0.5 * (H / wl) ** 0.5
# Display message
if verbose:
print('sbs = ' + np.str(sbs))
return sbs
# ===============================================================================
# Reverse shoal waves
# ===============================================================================
def deep_water_equivalent(H, h, period):
"""
Code to find the equivalent deep water linear wave parameters based on
intermediate to shallow water conditions
PARAMETERS:
-----------
H : Wave height [m]
h : Water depth [m]
period : Wave period [s]
RETURNS:
--------
Deep water equivalents
H0 : Wave height [m]
k0 : Wave number [m-1]
NOTES:
------
1. Will not work if waves have broken already
"""
# Find the wave celerity
c1, n1, cg1 = celerity(period, h) # Intermediate water
cg0 = (9.81 * period) / (4 * np.pi) # Deep water
# Deep water wave length
H0 = H * ((cg1 / cg0) ** 0.5)
# Deep water wave number
k0 = ((2.0 * np.pi / period) ** 2) / 9.81
return H0, k0
def shoal(H1, h1, period, h0):
"""
Code to shoal waves based on linear wave theory
PARAMETERS:
-----------
H1 : Wave height [m]
h1 : Water depth [m]
period : Wave period [s]
h0 : Water depth to find equivalent conditions [m]
RETURNS:
--------
Equivalent parameters at h0
H0 : Wave height [m]
k0 : Wave number [m-1]
NOTES:
------
1. Will not work if waves have broken already
"""
# Find the wave celerity
c1, n1, cg1 = celerity(period, h1) # Current conditions
c0, n0, cg0 = celerity(period, h0) # Desired conditions
# Deep water wave length
H0 = H1 * ((cg1 / cg0) ** 0.5)
# Deep water wave number
k0 = dispersion(period, h0)
return H0, k0
# ===============================================================================
# Compute IEC Bulk Parameters from Spectrum
# ===============================================================================
def iec_params(freq, dirs, spec, dpt, rho=1025.0):
"""
Function to compute IEC recommended wave energy parameters from
frequency-direction spectrum
Parameters:
-----------
freq : Vector of spectral frequencies [Hz]
dirs : Vector of directions (nautical convention) [Deg]
spec : Wave spectrum [m2/Hz/Deg]
The shape must be [time,npts,freq,dirs]
dpt : Water depth of shape [npts]
rho : (Optional) Density of water
Returns:
--------
Dictionary containing bulk wave parameters
Hs : Significant wave height [m]
OWP : Omnidirectional wave power [W/m]
Jth : Maximum directionally resolved wave power [W/m]
Th : Direction of maximum directionally resolved wave power [deg]
d : Directionality coefficient (Jth/OWP)
Te : Energy period [s]
Sw : Spectral width (m0*m-2/m-1/m-1 - 1)**2
Notes:
------
- mn are the different spectral moments
- Directional grid is assumed to be regular
need to implement a trapz for when this is not true.
- Pass everything as arrays:
>>> dpt = np.array([100.0]) # If you only have one point
REFERENCES:
-----------
Marine energy - Wave, tidal and other water current converters -
Part 101: Wave energy resoure assessment and characterization.
IEC TS 62600-101
"""
# Figure out the size of the array and adjust if necessary
_, npts, nfreq, ndirs = np.shape(spec)
# Directions must be sorted (if they exist)
if ndirs > 1:
sortInd = np.argsort(dirs)
spec = spec[..., sortInd]
dirs = dirs[sortInd]
# Compute the group velocity for each point
cg = np.zeros((npts, nfreq)) * np.NAN
for aa in range(cg.shape[0]):
for bb in range(cg.shape[1]):
_, _, cg[aa, bb] = celerity(1 / freq[bb], dpt[aa])
# Non directional parameters -----------------------------------------------
if ndirs > 1:
# freqSpec = np.trapz(spec,dirs*np.pi/180,axis=-1)
dth = dirs[1] - dirs[0]
freqSpec = np.sum(spec, axis=-1) * dth
else:
freqSpec = spec[..., 0]
# Compute omnidirectional wave power
moment0 = np.trapz(freqSpec, freq, axis=-1)
momentn1 = np.trapz(freqSpec * (freq) ** -1, freq, axis=-1)
momentn2 = np.trapz(freqSpec * (freq) ** -2, freq, axis=-1)
# Significant wave height
bp = {}
bp['Hs'] = 4.004 * (moment0 ** 0.5)
# Energy Period
bp['Te'] = momentn1 / moment0
# Spectral width
bp['Sw'] = (moment0 * momentn2 / momentn1 / momentn1 - 1.0) ** 0.5
# Omnidirectional wave power (loop extravaganza here)
owp = np.zeros_like(bp['Hs']) * np.NAN
# Time loop
for aa in range(spec.shape[0]):
# Point loop
for bb in range(spec.shape[1]):
# Omnidirectional wave power
owp[aa, bb] = np.trapz(freqSpec[aa, bb, :] * cg[bb, :], freq)
# Get directional properties -----------------------------------------------
if ndirs > 1:
# Omidirectional wave power through a plane
cg = np.repeat(np.expand_dims(cg, axis=-1), ndirs, axis=-1)
jth = np.zeros_like(owp)
th = np.zeros_like(owp) * np.NAN
# Time loop
for aa in range(spec.shape[0]):
# Point loop
for bb in range(spec.shape[1]):
# Directional wave power
tmpJth = 0.0
dirSpec = np.trapz(spec[aa, bb, ...] * cg[bb, ...], freq, axis=-2)
# Direction loop
for cc in range(ndirs):
fac = np.cos(np.pi / 180.0 * (dirs[cc] - dirs))
fac[fac < 0] = 0.0
tmpJth = np.sum(dirSpec * fac, axis=-1) * dth
if tmpJth > jth[aa, bb]:
jth[aa, bb] = tmpJth
th[aa, bb] = dirs[cc]
else:
th = np.zeros_like(owp) * np.NAN
jth = np.zeros_like(owp) * np.NAN
# Take care of units here
owp *= 9.81 * rho
jth *= 9.81 * rho
# Allocate in arrays
bp['Th'] = th
bp['OWP'] = owp
bp['Jth'] = jth
bp['d'] = jth / owp # Directionality coefficient
return bp