Source code for WaveSpectra

# -*- coding: utf-8 -*-
"""
This module contains functions for calculating intergated wave parameters and 
reconstructing 2-D wave spectra from spectral parameters.

:Dependencies [External]: numpy
:Dependencies [Internal]: 

"""
# ----------------------------------------------------------------------------
#   IMPORTS
# ----------------------------------------------------------------------------
# Standard Python Dependencies
# Non-Standard Python Dependencies
import numpy as np
from scipy.special import gamma as gammaFunc
# Local Module Dependencies
from waveval.Validation import circ_mod_pi
# Other Dependencies


# ----------------------------------------------------------------------------
#   GLOBAL VARIABLES
# ----------------------------------------------------------------------------
grav = 9.81  # Mean gravitional acceleration at Earths surface m / s^2

# ----------------------------------------------------------------------------
#   CLASS DEFINITIONS
# ----------------------------------------------------------------------------


# ----------------------------------------------------------------------------
#   FUNCTION DEFINITIONS
# ----------------------------------------------------------------------------

# ======== Theoretical 1-D Spectra Functions ============
[docs]def pierson_moskowitz(f,Tp,Hs): try: f = np.array(f) except: pass assert isinstance(f, np.ndarray), 'f must be of type np.ndarray' assert isinstance(Tp, (int,float)), 'Tp must be of type int or float' assert isinstance(Hs, (int,float)), 'Hs must be of type int or float' f.sort() B_PM = (5/4)*(1/Tp)**4 A_PM = B_PM*(Hs/2)**2 Sf = A_PM*f**(-5)*np.exp(-B_PM*f**(-4)) return Sf
[docs]def jonswap(f,Tp,Hs,gamma=None): try: f = np.array(f) except: pass assert isinstance(f, np.ndarray), 'f must be of type np.ndarray' assert isinstance(Tp, (int,float)), 'Tp must be of type int or float' assert isinstance(Hs, (int,float)), 'Hs must be of type int or float' assert isinstance(gamma, (int,float, type(None))), \ 'gamma must be of type int or float' f.sort() B_PM = (5/4)*(1/Tp)**4 A_PM = B_PM*(Hs/2)**2 S_f = A_PM*f**(-5)*np.exp(-B_PM*f**(-4)) if not gamma: TpsqrtHs = Tp/np.sqrt(Hs); if TpsqrtHs <= 3.6: gamma = 5; elif TpsqrtHs > 5: gamma = 1; else: gamma = np.exp(5.75 - 1.15*TpsqrtHs); # Cutoff frequencies for gamma function siga = 0.07 sigb = 0.09 fp = 1/Tp # peak frequency lind = np.where(f<=fp) hind = np.where(f>fp) Gf = np.zeros(f.shape) Gf[lind] = gamma**np.exp(-(f[lind]-fp)**2/(2*siga**2*fp**2)) Gf[hind] = gamma**np.exp(-(f[hind]-fp)**2/(2*sigb**2*fp**2)) C = 1- 0.287*np.log(gamma) Sf = C*S_f*Gf return Sf
[docs]def dsfunc1d(theta,thetap,dspr=None): ds1d = np.zeros(np.shape(theta)) dtheta = theta-thetap dt = circ_mod_pi(np.deg2rad(dtheta)) if dspr is None: indx = np.where(np.abs(dt) <= np.deg2rad(90.0)) ds1d[indx] = (2.0/np.pi)*np.square(np.cos(dt[indx])) else: s = (2.0/np.square(np.deg2rad(dspr)))-1.0 G1 = gammaFunc(s+1.0) G2 = gammaFunc(s+0.5) if G1 == np.Inf: G1 = 1.0 G2 = 1.0 A2 = G1/(2.0*np.sqrt(np.pi)*G2) ds1d[:] = A2*(np.cos(0.5*dt)**(2.0*s))[:] return ds1d
# ============== 2-D Spectra Functions ==================
[docs]def significant_wave_height(moments): """ Integrated Wave Parameter: Significant wave height :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :return: Hm0. """ Hm0 = 4.0 * np.sqrt(moments['m0']) return Hm0
[docs]def mean_wave_period(moments): """ Integrated Wave Parameter: Mean wave period :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :return: Tm01. """ Tm01 = (moments['m1']/moments['m0']) return Tm01
[docs]def zero_crossing_period(moments): """ Integrated Wave Parameter: Zero crossing period :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :return: Tm02. """ Tm02 = np.sqrt(moments['m0']/moments['m2']) return Tm02
[docs]def wave_energy_period(moments): """ Integrated Wave Parameter: Wave energy period :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :returns: Tm10. """ Tm10 = (moments['m0']/moments['m1']) return Tm10
[docs]def spectral_period(moments): """ Integrated Wave Parameter: Peak spectral period :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :returns: Tm02. """ Tdw2 = np.sqrt(moments['m-1']/moments['m1']) return Tdw2
[docs]def wave_steepness(moments): """ Integrated Wave Parameter: Wave steepness :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :returns: Tdw2. """ Hm0 = significant_wave_height(moments) Tm01 = mean_wave_period(moments) xi = (2.0 * np.pi / grav) * np.sqrt( Hm0 / np.square(Tm01) ) return xi
[docs]def spectral_width(moments): """ Integrated Wave Parameter: Spectral width :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :returns: eps. """ num = moments['m0']*moments['m4'] - np.square(moments['m2']) den = moments['m0']*moments['m4'] eps = np.sqrt(num / den) return eps
[docs]def spectral_narrowness(moments): """ Integrated Wave Parameter: Spectral narrowness :param moments: Spectral moments calculated from the wave energy spectrum using :func:`WaveVal.WaveSpectra.spectralMoment()`. :returns: nu. """ num = moments['m0']*moments['m2'] - np.square(moments['m1']) den = np.square(moments['m1']) nu = np.sqrt(num / den) return nu
[docs]def spectralMoment(f,df,S,moment): """ :param f: frequency bin centre values :param df: frequency bin widths :param S: spectral energy density in frquency bins :param moment: interger representing moment to be calculated :return: specMom """ if S.ndim > 1: specMom = np.sum(pow(f,moment)*S*df,1) else: specMom = np.sum(pow(f,moment)*S*df) return specMom
[docs]def getSpectralMoments(f,df,S): moments = {} moms = [-1,0,1,2,4] for mom in moms: momStr = 'm'+str(mom) moments[momStr] = spectralMoment(f,df,S,mom) return moments
[docs]def getSpectralParameters(moments): spec_params = {} spec_params['Hm0'] = significant_wave_height(moments) spec_params['Tm01'] = mean_wave_period(moments) spec_params['Tm02'] = zero_crossing_period(moments) spec_params['Tm10'] = wave_energy_period(moments) spec_params['Tdw2'] = spectral_period(moments) spec_params['wave_steepness'] = wave_steepness(moments) spec_params['spectral_width'] = spectral_width(moments) spec_params['spectral_narrowness'] = spectral_narrowness(moments) return spec_params
# ============== 2-D Spectra Functions ==================
[docs]def wavehgtvar(freq, df, dt, spec2D): """ Convert 2-D wave spectrum into wave height variance map in (f,theta) space. """ nfreq = spec2D.shape[0] ntheta = spec2D.shape[1] f_arry = np.outer(freq,np.ones(ntheta,)) df_arry = np.outer(df,np.ones(ntheta,)) dt_arry = np.outer(np.ones(nfreq,),np.radians(dt)) whv = spec2D*f_arry*df_arry*dt_arry return whv
[docs]def dsfuncnorm(freq, df, th1, sth1, ntheta): """ Normalised 2-D directional spreading function Based on description given on NOAA `NDBC <https://www.ndbc.noaa.gov/measdes.shtml>`_ web site. """ nfreq = len(freq) # number of frequency bins dth = 360.0 / ntheta # theta bin width # Convert direction convention from meteorological to mathematical theta1 = 270.0 - th1 dsFnc = np.empty((nfreq, ntheta), dtype=float) # Apply weighting define in Earle et al, 1999 to avoid -ve energy for ith in range(ntheta): theta = ith * dth dsFnc[:,ith] = np.exp(-0.5*np.square((np.radians(theta)-np.radians(theta1)+2.0*np.pi)/np.radians(sth1)))/(np.sqrt(2.0*np.pi)*np.radians(sth1)) dsFnc[:,ith] += np.exp(-0.5*np.square((np.radians(theta)-np.radians(theta1))/np.radians(sth1)))/(np.sqrt(2.0*np.pi)*np.radians(sth1)) dsFnc[:,ith] += np.exp(-0.5*np.square((np.radians(theta)-np.radians(theta1)-2.0*np.pi)/np.radians(sth1)))/(np.sqrt(2.0*np.pi)*np.radians(sth1)) # Normalise across theta for ifrq in range(nfreq): dsFnc[ifrq,:] = dsFnc[ifrq,:] / np.nansum(dsFnc[ifrq,:]) return dsFnc
[docs]def spec2dnorm(freq, sf, df, th1, sth1, ntheta): """ Generate normlaised 2-D directional spectrum from standard parameters retrieve from a wavebuoy. """ # construct 2-D directional spreading function dsFnc = dsfuncnorm(freq, df, th1, sth1, ntheta) # construct 2-D directional spectrum sp = np.outer(sf,np.ones(ntheta,)) sp2d = sp * dsFnc return sp2d, dsFnc
[docs]def dsfunc(freq, df, th1, th2, sth1, sth2, ntheta): """ Directional spreading function Based on description of `MEDS <https://forge.ifremer.fr/plugins/mediawiki/wiki/ww3/index.php/En:buoy_data:meds>`_ buoy data format given on the IFREMER Wiki pages. :param freq: :param df: :param th1: :param th2: :param sth1: :param sth2: :param ntheta: :return: dsFnc """ nfreq = len(freq) # number of frequency bins dth = 360.0 / ntheta # theta bin width W1 = 2.0/3.0 W2 = 1.0/6.0 if th2 is None: th2 = np.zeros(th1.shape, dtype=float) sth2 = np.zeros(sth1.shape, dtype = float) W1 = 1.0 W2 = 0.0 # Convert direction convention from meteorological to mathematical theta1 = 270.0 - th1 theta2 = 270.0 - th2 # Set theta2 to give minimum angular separation with theta1 th2mth1 = np.abs(theta2-theta1) theta2[th2mth1 > 180.] = theta2[th2mth1 > 180.] + 180. R1 = np.abs(1.0 - 0.5*(np.square(np.radians(sth1)))) R2 = np.abs(1.0 - 0.5*(np.square(np.radians(sth2)))) dsFnc = np.empty((nfreq, ntheta), dtype=float) # Apply weighting define in Earle et al, 1999 to avoid -ve energy for ith in range(ntheta): theta = ith * dth #dsFnc[:,ith] = ( 0.5 + R1*np.cos(np.radians(theta-theta1)) # + R2*np.cos(2.0*np.radians(theta-theta2)) ) / np.pi #dsFnc[:,ith] = (dsFnc[:,ith]+np.abs(dsFnc[:,ith]))/2.0 dsFnc[:,ith] = ( 0.5 + W1*R1*np.cos(np.radians(theta-theta1)) + W2*R2*np.cos(2.0*np.radians(theta-theta2)) ) / np.pi # Normalise across theta for ifrq in range(nfreq): dsFnc[ifrq,:] = dsFnc[ifrq,:] / np.nansum(dsFnc[ifrq,:]) return dsFnc
[docs]def spec2d(freq, sf, df, th1, th2, sth1, sth2, ntheta): """ Generate 2-D directional spectrum from standard parameters retrieve from a wavebuoy. """ # construct 2-D directional spreading function dsFnc = dsfunc(freq, df, th1, th2, sth1, sth2, ntheta) # construct 2-D directional spectrum sp = np.outer(sf,np.ones(ntheta,)) sp2d = sp * dsFnc return sp2d, dsFnc
[docs]def spec2dparam(freq,theta,thetap,Tp,Hs,gamma=None,dspr=None): ds = dsfunc1d(theta,thetap,dspr) dsFnc = np.outer(np.ones(len(freq),),ds) sf = jonswap(freq,Tp,Hs,gamma) sp2d = np.outer(sf,np.ones(len(theta),))*dsFnc return sp2d, dsFnc