From cb1a94c1ecc1b263e8c379d394a79b247bf1b1d3 Mon Sep 17 00:00:00 2001 From: shfe <shfe@dtu.dk> Date: Wed, 27 Nov 2019 07:42:15 +0100 Subject: [PATCH] some functions for wave modeling and soil modeling --- wetb/soil/KlinkvortModel.py | 121 ++++++++ wetb/soil/LeblancModel.py | 189 +++++++++++ wetb/soil/__init__.py | 0 wetb/soil/hyperbolic_model.py | 48 +++ wetb/soil/py_model.py | 167 ++++++++++ wetb/wave/__init__.py | 0 wetb/wave/dispersion.py | 157 ++++++++++ wetb/wave/numba_misc.py | 569 ++++++++++++++++++++++++++++++++++ wetb/wave/spectrum.py | 531 +++++++++++++++++++++++++++++++ wetb/wave/zerocrossing.py | 281 +++++++++++++++++ 10 files changed, 2063 insertions(+) create mode 100644 wetb/soil/KlinkvortModel.py create mode 100644 wetb/soil/LeblancModel.py create mode 100644 wetb/soil/__init__.py create mode 100644 wetb/soil/hyperbolic_model.py create mode 100644 wetb/soil/py_model.py create mode 100644 wetb/wave/__init__.py create mode 100644 wetb/wave/dispersion.py create mode 100644 wetb/wave/numba_misc.py create mode 100644 wetb/wave/spectrum.py create mode 100644 wetb/wave/zerocrossing.py diff --git a/wetb/soil/KlinkvortModel.py b/wetb/soil/KlinkvortModel.py new file mode 100644 index 00000000..bf9b7f58 --- /dev/null +++ b/wetb/soil/KlinkvortModel.py @@ -0,0 +1,121 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Jun 18 13:34:12 2018 + +@author: shfe + +Note: +This script is used for Klinkvort model used for cyclic accumulation calculation +""" + +import numpy as np +import matplotlib.pyplot as plt +plt.close('all') + +class KlinkvortModel(object): + + + def __init__(self, xi_b, xi_c, N): + """ + Initialize the input parameters + """ + + self.xi_b = xi_b + self.xi_c = xi_c + self.N = N + + def alpha(self): + """ + Calculate the parameter alpha + """ + # Calculate the Tb, Tc + if not 0 <= self.xi_b <= 1: + raise ValueError('The maximum load ratio should between 0 to 1') + + if not -1 <= self.xi_c <= 1: + raise ValueError('The cyclic load ratio should between -1 to 1') + + if self.xi_b <= 0.022: + Tb = 0 + else: + Tb = 0.61*self.xi_b - 0.013 + + Tc = (self.xi_c + 0.63)*(self.xi_c - 1)*(self.xi_c - 1.64) + alpha = Tb * Tc + + return Tb, Tc, alpha + + def evoluation(self, y1): + """ + Evoluation model for cyclic accumulated rotation + """ + + _, _, alpha = self.alpha() + yn = y1 * self.N**alpha + + return yn + + def equivalentnum(self, y1, yn, alpha): + """ + Equivalent number for cylces + """ + num = (yn/y1)**(1/alpha) + + return num + +def superposition(xi_b, xi_c, N_cases, y1_cases, s0 = 0): + + + # Check the array equal number of items + if not (np.size(xi_b) == np.size(xi_c) and np.size(xi_b) == np.size(N_cases)): + raise ValueError('Size for input should be identical') + print(s0) + yn = np.zeros(np.size(xi_b)) + for i, (b, c, N, y1) in enumerate(zip(xi_b, xi_c, N_cases, y1_cases)): + + if i==0: + + em = KlinkvortModel(b, c, N) + _, _, alpha = em.alpha() + if s0 == 0: + yn[i] = em.evoluation(y1) + else: + N_eq = (s0/y1)**(1/alpha) + if N_eq < 1e8: + +# N_eq = (s0/y1)**(1/alpha) + yn[i] = y1*(N+N_eq)**alpha + else: + yn[i] = s0 + + + + else: + em = KlinkvortModel(b, c, N) + _, _, alpha = em.alpha() + N_eq = (yn[i-1]/y1)**(1/alpha) + if N_eq < 1e8: +# N_eq = (yn[i-1]/y1)**(1/alpha) + yn[i] = y1*(N_eq + N)**alpha + else: + yn[i] = yn[i-1] + + return yn + +def alpha_super(xi_b, xi_c, N_cases): + # Check the alpha value + if not (np.size(xi_b) == np.size(xi_c) and np.size(xi_b) == np.size(N_cases)): + raise ValueError('Size for input should be identical') + + alpha_list = np.zeros(np.size(xi_b)) + for i, (b, c, N) in enumerate(zip(xi_b, xi_c, N_cases)): + + em = KlinkvortModel(b, c, N) + _, _, alpha = em.alpha() + alpha_list[i] = alpha + + return alpha_list + + + + \ No newline at end of file diff --git a/wetb/soil/LeblancModel.py b/wetb/soil/LeblancModel.py new file mode 100644 index 00000000..a6dbb51c --- /dev/null +++ b/wetb/soil/LeblancModel.py @@ -0,0 +1,189 @@ +# -*- coding: utf-8 -*- +""" +Created on Sun Mar 25 16:15:25 2018 + +@author: shfe@dtu.dk + +The model proposed by Leblanc +""" +import matplotlib.pyplot as plt +import numpy as np +plt.close('all') + +class LeblancModel(object): + + + def __init__(self, xi_b, xi_c, N): + """ + Initialize the input parameters + """ + + self.xi_b = xi_b + self.xi_c = xi_c + self.N = N + self.alpha = 0.31 + + def coeff(self, rd = 0.38): + """ + Calculate the parameter alpha + """ + # Calculate the Tb, Tc + if not 0 <= self.xi_b <= 1: + raise ValueError('The maximum load ratio should between 0 to 1') + + if not -1 <= self.xi_c <= 1: + raise ValueError('The cyclic load ratio should between -1 to 1') + + if rd == 0.38: + if self.xi_b <= 0.051: + Tb = 0 + else: + Tb = 0.4238*self.xi_b - 0.0217 + + elif rd == 0.04: + if self.xi_b <= 0.1461: + Tb = 0 + else: + Tb = 0.3087*self.xi_b - 0.0451 + + else: + raise ValueError('The relative density value is not validated yet') + + + if -1 <= self.xi_c < -0.65: + Tc = 13.71*self.xi_c + 13.71 + + elif -0.65 <= self.xi_c < 0: + Tc = -5.54*self.xi_c + 1.2 + + else: + Tc = -1.2*self.xi_c + 1.2 + + return Tb, Tc + + def evoluation(self, y1): + """ + Evoluation model for cyclic accumulated rotation + """ + + Tb, Tc = self.coeff() + yn = y1 * Tb * Tc * self.N**0.31 + + return yn + + def equivalentnum(self, y1, yn): + """ + Equivalent number for cylces + """ + Tb, Tc = self.coeff() + num = (yn/(y1*Tb*Tc))**(1/self.alpha) + + return num + +def superposition(xi_b, xi_c, N_cases, y1_cases, s0 = 0, y0 = 0): + + + # Check the array equal number of items + if not (np.size(xi_b) == np.size(xi_c) and np.size(xi_b) == np.size(N_cases)): + raise ValueError('Size for input should be identical') +# print(s0) + dyn = np.zeros(np.size(xi_b)) + yn = np.zeros(np.size(xi_b)) + y_static = np.zeros(np.size(xi_b)) + for i, (b, c, N, y1) in enumerate(zip(xi_b, xi_c, N_cases, y1_cases)): + + if i==0: + + em = LeblancModel(b, c, N) + Tb, Tc = em.coeff() + if s0 == 0: + dyn[i] = em.evoluation(y1) + else: + N_eq = (s0/(y1*Tb*Tc))**(1/em.alpha) + if N_eq < 1e8: + dyn[i] = (y1*Tb*Tc)*(N+N_eq)**em.alpha + else: + dyn[i] = s0 + + y_static[i] = max(y1, y0) + + else: + em = LeblancModel(b, c, N) + Tb, Tc = em.coeff() + N_eq = (dyn[i-1]/(y1*Tb*Tc))**(1/em.alpha) + if N_eq < 1e8: + dyn[i] = (y1*Tb*Tc)*(N_eq + N)**em.alpha + else: + dyn[i] = dyn[i-1] + + y_static[i] = max(y_static[i-1], y1) + + yn[i] = dyn[i] + y_static[i] + + return dyn, y_static, yn + +def coeff_super(xi_b, xi_c, N_cases): + # Check the alpha value + if not (np.size(xi_b) == np.size(xi_c) and np.size(xi_b) == np.size(N_cases)): + raise ValueError('Size for input should be identical') + + Tb_list = np.zeros(np.size(xi_b)) + Tc_list = np.zeros(np.size(xi_c)) + for i, (b, c, N) in enumerate(zip(xi_b, xi_c, N_cases)): + + em = LeblancModel(b, c, N) + Tb, Tc = em.coeff() + Tb_list[i] = Tb + Tc_list[i] = Tc + + return Tb_list, Tc_list + + +def moment_ult(L, D, gamma): + """ + Ultimate static moment + + Parameters + ---------------- + L -- Pile length + D -- Pile diameter + gamma -- Submerged unit weight + + Output + ---------------- + m_ult -- Ultimate static moment + """ + + + m_ult = L**3*D*gamma*0.6 + + return m_ult + +def rot_moment(m, L, D, gamma, pa = 101, rd = 0.04): + """ + Ultimate static moment + + Parameters + ---------------- + L -- Pile length + D -- Pile diameter + gamma -- Submerged unit weight + m -- Moment + + Output + ---------------- + theta - Rotational angle in radians + """ + + if rd == 0.04: + m_ult = L**3*D*gamma*0.6/1e3 # unit: MN + theta_norm = 0.038 * (m/m_ult)**2.33 + theta = theta_norm*np.sqrt((L*gamma)/pa) + + else: + m_ult = L**3*D*gamma*1.2/1e3 # unit: MN + theta_norm = 0.042 * (m/m_ult)**1.92 + theta = theta_norm*np.sqrt((L*gamma)/pa) + + return theta + diff --git a/wetb/soil/__init__.py b/wetb/soil/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wetb/soil/hyperbolic_model.py b/wetb/soil/hyperbolic_model.py new file mode 100644 index 00000000..f08e8203 --- /dev/null +++ b/wetb/soil/hyperbolic_model.py @@ -0,0 +1,48 @@ +# -*- coding: utf-8 -*- +""" +Created on Sun Apr 22 17:06:06 2018 + +@author: shfe + +Hyperbolic model used for soil load displacement behaviour +""" + +import numpy as np + +def hyperbolic_load(y, pu, a, b): + """ + Static hyperbolic model developed by Lee + + Parameters + ---------------- + y -- displacement + pu -- ultimate load + a, b -- coefficients + + Output + ---------------- + p -- soil resistence pressure + """ + + p = y/(a + b*y) * pu + + return p + +def hyperbolic_disp(p, pu, a, b): + """ + Static hyperbolic model developed by Lee + + Parameters + ---------------- + p -- load + pu -- ultimate load + a, b -- coefficients + + Output + ---------------- + y -- displacement + """ + + y = p * a/(pu - p*b) + + return y \ No newline at end of file diff --git a/wetb/soil/py_model.py b/wetb/soil/py_model.py new file mode 100644 index 00000000..9d51b53c --- /dev/null +++ b/wetb/soil/py_model.py @@ -0,0 +1,167 @@ +# -*- coding: utf-8 -*- +""" +Created on Sun Mar 25 16:15:25 2018 + +@author: shfe@dtu.dk + +Static p-y soil model and cyclic degradation model +""" + +import numpy as np + +def py_static(y, pu, yc): + """ + Static p-y model developed by Matlock + + Parameters + ---------------- + y -- displacement + pu -- ultimate stress + yc -- ultimate displacement + + Output + ---------------- + p -- soil resistence pressure + """ + + if y/yc <= 8: + p = 0.5*pu*(y/yc)**(1/3) + + else: + p = pu + + return p + +def strain_static(p, pu, yc): + """ + Static strain using p-y model developed by Matlock + + Parameters + ---------------- + p -- stress + pu -- ultimate stress + yc -- ultimate displacement + + Output + ---------------- + y -- soil strain + """ + if p <= pu: + y = (p/(0.5*pu))**3*yc + + else: + raise ValueError("The applied stress is LARGER than ultimate strength") + + return y + +def load_unload_py(p, pu, yc): + """ + load/unload module using p-y model developed by Matlock + + Parameters + ---------------- + p -- stress + pu -- ultimate stress + yc -- ultimate displacement + + Output + ---------------- + y -- soil strain + y_cum -- cumulative soil displacement + """ + + y = strain_static(p, pu, yc) + + if p <= 0.5*pu: # elastic + y_cum = 0 + + else: + y_cum = y - p/(0.5*pu/yc) + + return y, y_cum + + + + +def degra_coeff(N, y1, b): + """ + Degradation factor for p-y model + + Parameters + ---------------- + N -- number of cycle + y1 -- displacement predicted by static p-y curve + b -- pile diameter + + Output + ---------------- + lambda -- Degradation factor + """ + # check + + lambda_N = np.abs(y1/(0.2*b)*np.log(N)) + + try: + lambda_N <= 1 + except: + print("soil must be degraded!!!") + + return lambda_N + +def py_cyclic(y, pu, yc, N, y1, b): + """ + Degradation p-y model with cyclic load + + Parameters + ---------------- + y -- displacement + pu -- ultimate stress + yc -- ultimate displacement + N -- number of cycle + y1 -- displacement predicted by static p-y curve + b -- pile diameter + + Output + ---------------- + p -- degraded soil resistence pressure + """ + + lambda_N = degra_coeff(N, y1, b) + p = py_static(y, pu, yc) * (1 - lambda_N) + + return p + +def py_parcel(load_parcel, cycle_parcel, pu, yc, b): + """ + Degradation p-y model after a parcel of cyclic load + + Parameters + ---------------- + load_parcel -- load parcel + cycle_parcel -- number of cycle of load parcel + yc -- ultimate displacement + b -- pile diameter + + Output + ---------------- + p -- degraded soil resistence pressure + """ + + pu_N = np.zeros(np.size(load_parcel)+1) + pu_N[0] = pu + lambda_N = np.zeros(np.size(load_parcel)) + y_N = np.zeros(np.size(load_parcel)) + + for i in np.arange(np.size(load_parcel)): + load_i = load_parcel[i] + cycle_i = cycle_parcel[i] + if np.isnan(load_i): + y_i = 0 + else: + y_i = strain_static(load_i, pu_N[i], yc) + y_N[i] = y_i + lambda_i = degra_coeff(cycle_i, y_i, b) + lambda_N[i] = lambda_i + pu_N[i+1] = pu_N[i] * (1-lambda_i) + + return y_N, lambda_N, pu_N diff --git a/wetb/wave/__init__.py b/wetb/wave/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/wetb/wave/dispersion.py b/wetb/wave/dispersion.py new file mode 100644 index 00000000..467f7063 --- /dev/null +++ b/wetb/wave/dispersion.py @@ -0,0 +1,157 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Nov 6 21:06:05 2017 + +@author: shfe + +Note: The module contains an assortment of tools for working with linear wave theory calculations. +""" +# py2/3 compatible: +from __future__ import absolute_import, division, print_function, unicode_literals +import numpy as np +import matplotlib.pyplot as plt +plt.close('all') + +g = 9.806 # gravitational acceleration in m^2/s + +def wave_number(g, omega, h): + """ + Computes the wave number for given frequency and water depth + (linear dispersion relationship) + :param omega: -- wave frequency + :param g: -- gravitational acceleration (defaults to 9.806 m/s^2) + :param h: -- the water depth + :returns k: the wave number + """ + + p = omega**2 * h / g + q = dispersion(p) + k = q * omega**2 / g + + return k + + +def frequency(g, k, h): + """ + Computes the frequency for a given wave number and water depth + (linear dispersion relationship) + :param k: the wave number + :param g: -- gravitational acceleration + :param h: -- the water depth + :returns omega: -- wave frequency + """ + + return np.sqrt(g * k * np.tanh(k * h)) + + +def dispersion(p, tol=1e-14, max_iter=100): + """ + The linear dispersion relation in non-dimensional form: + finds q, given p + q = gk/omega^2 non-d wave number + p = omega^2 h / g non-d water depth + Starts with the Fenton and McKee approximation, then iterates with Newton's + method until accurate to within tol. + :param p: non-dimensional water depth + :param tol=1e-14: acceptable tolerance + :param max_iter: maximum number of iterations to accept + (it SHOULD converge in well less than 100) + """ + if p <= 0.0: + raise ValueError("Non dimensional water depth d must be >= 0.0") + # First guess (from Fenton and McKee): + q = np.tanh(p ** 0.75) ** (-2.0 / 3.0) + + iter = 0 + f = q * np.tanh(q * p) - 1 + while abs(f) > tol: + qp = q * p + fp = qp / (np.cosh(qp) ** 2) + np.tanh(qp) + q = q - f / fp + f = q * np.tanh(q * p) - 1 + iter += 1 + if iter > max_iter: + raise RuntimeError("Maximum number of iterations reached in dispersion()") + return q + + +def max_u(a, omega, g, h, z=None): + """ + Compute the maximum Eulerian horizontal velocity at a given depth + :param a: -- wave amplitude (1/2 the height) + :param omega: -- wave frequency + :param g: -- gravitational acceleration (defaults to 9.806 m/s^2) + :param h: -- the water depth + :param z=None: -- the depth at which to compute the maximum velocity. + if None, then h is used (the bottom) + """ + + z = h if z is None else z + k = wave_number(g, omega, h) + u = a * omega * (np.cosh(k * (h + z)) / np.sinh(k * h)) + + return u + + +def amp_scale_at_depth(g, omega, h, z): + """ + Compute the scale factor of the orbital amplitude at the given depth + :param g: -- gravitational acceleration + :param omega: -- the wave frequency + :param h: -- the water depth + :param z: -- the depth at which to compute the scale factor + """ + + k = wave_number(g, omega, h) + + return np.cosh(k * (h + z)) / np.cosh(k * (h)) + + +def celerity(k, h, g=g): + """ + Compute the celerity (wave speed, phase speed) for a given wave number and depth + :param k: -- the wave number + :param h: -- the water depth + :param g=g: -- gravitational acceleration (defaults to 9.806 m/s^2) + """ + + C = np.sqrt(g / k * np.tanh(k * h)) + + return C + + +def group_speed(k, h, g=g): + """ + Compute the group speed for a given wave number and depth + :param k: -- the wave number + :param h: -- the water depth + :param g=g: -- gravitational acceleration (defaults to 9.806 m/s^2) + """ + + n = 1.0 / 2 * (1 + (2 * k * h / np.sinh(2 * k * h))) + Cg = n * celerity(k, h, g) + + return Cg + + +def shoaling_coeff(omega, g, h0, h2): + """ + Compute the shoaling coeff for two depths: ho and h2. + The shoaling coeff is the ratio of wave height (H2) at a particular + point of interest to the original or deep water wave height (H0). + Pass in h0 = None for deep water + :param omega: -- the wave frequency + :param g: -- gravitational acceleration + :param h0: -- the initial water depth + :param h2: -- the depth at which to compute the shoaling coeff + """ + + k2 = wave_number(g, omega, h2) + Cg2 = group_speed(k2, h2, g) + if h0 is not None: + k0 = wave_number(g, omega, h0) + Cg0 = group_speed(k0, h0, g) + Ks = np.sqrt(Cg0 / Cg2) + return Ks + else: # Deep water + return np.sqrt((g / (2 * omega)) / Cg2) diff --git a/wetb/wave/numba_misc.py b/wetb/wave/numba_misc.py new file mode 100644 index 00000000..0e6872dc --- /dev/null +++ b/wetb/wave/numba_misc.py @@ -0,0 +1,569 @@ +""" +Created on 6. okt. 2016 + +@author: pab +""" +from __future__ import absolute_import, division +from numba import jit, float64, int64, int32, int8, void +import numpy as np + + +@jit(int64(int64[:], int8[:])) +def _findcross(ind, y): + ix, dcross, start, v = 0, 0, 0, 0 + n = len(y) + if y[0] < v: + dcross = -1 # first is a up-crossing + elif y[0] > v: + dcross = 1 # first is a down-crossing + elif y[0] == v: + # Find out what type of crossing we have next time.. + for i in range(1, n): + start = i + if y[i] < v: + ind[ix] = i - 1 # first crossing is a down crossing + ix += 1 + dcross = -1 # The next crossing is a up-crossing + break + elif y[i] > v: + ind[ix] = i - 1 # first crossing is a up-crossing + ix += 1 + dcross = 1 # The next crossing is a down-crossing + break + + for i in range(start, n - 1): + if ((dcross == -1 and y[i] <= v and v < y[i + 1]) or + (dcross == 1 and v <= y[i] and y[i + 1] < v)): + + ind[ix] = i + ix += 1 + dcross = -dcross + return ix + + +def findcross(xn): + """Return indices to zero up and downcrossings of a vector + """ + ind = np.empty(len(xn), dtype=np.int64) + m = _findcross(ind, xn) + return ind[:m] + + +def _make_findrfc(cmp1, cmp2): + + @jit(int64(int64[:], float64[:], float64), nopython=True) + def local_findrfc(t, y, h): + # cmp1, cmp2 = (a_le_b, a_lt_b) if method==0 else (a_lt_b, a_le_b) + + n = len(y) + j, t0, z0 = 0, 0, 0 + y0 = y[t0] + # The rainflow filter + for ti in range(1, n): + fpi = y0 + h + fmi = y0 - h + yi = y[ti] + + if z0 == 0: + if cmp1(yi, fmi): + z1 = -1 + elif cmp1(fpi, yi): + z1 = +1 + else: + z1 = 0 + t1, y1 = (t0, y0) if z1 == 0 else (ti, yi) + else: + if (((z0 == +1) and cmp1(yi, fmi)) or + ((z0 == -1) and cmp2(yi, fpi))): + z1 = -1 + elif (((z0 == +1) and cmp2(fmi, yi)) or + ((z0 == -1) and cmp1(fpi, yi))): + z1 = +1 + else: + raise ValueError + # warnings.warn('Something wrong, i={}'.format(tim1)) + + # Update y1 + if z1 != z0: + t1, y1 = ti, yi + elif z1 == -1: + t1, y1 = (t0, y0) if y0 < yi else (ti, yi) + elif z1 == +1: + t1, y1 = (t0, y0) if y0 > yi else (ti, yi) + + # Update y if y0 is a turning point + if abs(z0 - z1) == 2: + j += 1 + t[j] = t0 + + # Update t0, y0, z0 + t0, y0, z0 = t1, y1, z1 + # end + + # Update y if last y0 is greater than (or equal) threshold + if cmp2(h, abs(y0 - y[t[j]])): + j += 1 + t[j] = t0 + return j + 1 + return local_findrfc + + +@jit(int32(float64, float64), nopython=True) +def a_le_b(a, b): + return a <= b + + +@jit(int32(float64, float64), nopython=True) +def a_lt_b(a, b): + return a < b + + +_findrfc_le = _make_findrfc(a_le_b, a_lt_b) +_findrfc_lt = _make_findrfc(a_lt_b, a_le_b) + + +@jit(int64(int64[:], float64[:], float64), nopython=True) +def _findrfc(ind, y, h): + n = len(y) + t_start = 0 + nc = n // 2 - 1 + ix = 0 + for i in range(nc): + Tmi = t_start + 2 * i + Tpl = t_start + 2 * i + 2 + xminus = y[2 * i] + xplus = y[2 * i + 2] + + if(i != 0): + j = i - 1 + while ((j >= 0) and (y[2 * j + 1] <= y[2 * i + 1])): + if (y[2 * j] < xminus): + xminus = y[2 * j] + Tmi = t_start + 2 * j + j -= 1 + if (xminus >= xplus): + if (y[2 * i + 1] - xminus >= h): + ind[ix] = Tmi + ix += 1 + ind[ix] = (t_start + 2 * i + 1) + ix += 1 + # goto L180 continue + else: + j = i + 1 + while (j < nc): + if (y[2 * j + 1] >= y[2 * i + 1]): + break # goto L170 + if((y[2 * j + 2] <= xplus)): + xplus = y[2 * j + 2] + Tpl = (t_start + 2 * j + 2) + j += 1 + else: + if ((y[2 * i + 1] - xminus) >= h): + ind[ix] = Tmi + ix += 1 + ind[ix] = (t_start + 2 * i + 1) + ix += 1 + # iy = i + continue + + # goto L180 + # L170: + if (xplus <= xminus): + if ((y[2 * i + 1] - xminus) >= h): + ind[ix] = Tmi + ix += 1 + ind[ix] = (t_start + 2 * i + 1) + ix += 1 + elif ((y[2 * i + 1] - xplus) >= h): + ind[ix] = (t_start + 2 * i + 1) + ix += 1 + ind[ix] = Tpl + ix += 1 + + # L180: + # iy=i + # /* for i */ + return ix + + +def findrfc(y, h, method=0): + n = len(y) + t = np.zeros(n, dtype=np.int64) + findrfc_ = [_findrfc_le, _findrfc_lt, _findrfc][method] + m = findrfc_(t, y, h) + return t[:m] + + +@jit(void(float64[:], float64[:], float64[:], float64[:], + float64[:], float64[:], float64, float64, + int32, int32, int32, int32), nopython=True) +def _finite_water_disufq(rvec, ivec, rA, iA, w, kw, h, g, nmin, nmax, m, n): + # kfact is set to 2 in order to exploit the symmetry. + # If you set kfact to 1, you must uncomment all statements + # including the expressions: rvec[iz2], rvec[iv2], ivec[iz2] and ivec[iv2]. + + kfact = 2.0 + for ix in range(nmin - 1, nmax): + # for (ix = nmin-1;ix<nmax;ix++) { + kw1 = kw[ix] + w1 = w[ix] + tmp1 = np.tanh(kw1 * h) + # Cg, wave group velocity + Cg = 0.5 * g * (tmp1 + kw1 * h * (1.0 - tmp1 * tmp1)) / w1 # OK + tmp1 = 0.5 * g * (kw1 / w1) * (kw1 / w1) + tmp2 = 0.5 * w1 * w1 / g + tmp3 = g * kw1 / (w1 * Cg) + tmp4 = kw1 / np.sinh(2.0 * kw1 * h) if kw1 * h < 300.0 else 0.0 + + # Difference frequency effects finite water depth + Edij = (tmp1 - tmp2 + tmp3) / (1.0 - g * h / (Cg * Cg)) - tmp4 # OK + + # Sum frequency effects finite water depth + Epij = (3.0 * (tmp1 - tmp2) / + (1.0 - tmp1 / kw1 * np.tanh(2.0 * kw1 * h)) + + 3.0 * tmp2 - tmp1) # OK + # printf("Edij = %f Epij = %f \n", Edij,Epij); + + ixi = ix * m + iz1 = 2 * ixi + # iz2 = n*m-ixi; + for i in range(m): + rrA = rA[ixi] * rA[ixi] + iiA = iA[ixi] * iA[ixi] + riA = rA[ixi] * iA[ixi] + + # Sum frequency effects along the diagonal + rvec[iz1] += kfact * (rrA - iiA) * Epij + ivec[iz1] += kfact * 2.0 * riA * Epij + # rvec[iz2] += kfact*(rrA-iiA)*Epij; + # ivec[iz2] -= kfact*2.0*riA*Epij; + # iz2++; + + # Difference frequency effects along the diagonal + # are only contributing to the mean + rvec[i] += 2.0 * (rrA + iiA) * Edij + ixi += 1 + iz1 += 1 + # } + for jy in range(ix + 1, nmax): + # w1 = w[ix]; + # kw1 = kw[ix]; + w2 = w[jy] + kw2 = kw[jy] + tmp1 = g * (kw1 / w1) * (kw2 / w2) + tmp2 = 0.5 / g * (w1 * w1 + w2 * w2 + w1 * w2) + tmp3 = 0.5 * g * \ + (w1 * kw2 * kw2 + w2 * kw1 * kw1) / (w1 * w2 * (w1 + w2)) + tmp4 = (1 - g * (kw1 + kw2) / (w1 + w2) / (w1 + w2) * + np.tanh((kw1 + kw2) * h)) + Epij = (tmp1 - tmp2 + tmp3) / tmp4 + tmp2 - 0.5 * tmp1 # OK */ + + tmp2 = 0.5 / g * (w1 * w1 + w2 * w2 - w1 * w2) # OK*/ + tmp3 = -0.5 * g * \ + (w1 * kw2 * kw2 - w2 * kw1 * kw1) / (w1 * w2 * (w1 - w2)) + tmp4 = (1.0 - g * (kw1 - kw2) / (w1 - w2) / + (w1 - w2) * np.tanh((kw1 - kw2) * h)) + Edij = (tmp1 - tmp2 + tmp3) / tmp4 + tmp2 - 0.5 * tmp1 # OK */ + # printf("Edij = %f Epij = %f \n", Edij,Epij); + + ixi = ix * m + jyi = jy * m + iz1 = ixi + jyi + iv1 = jyi - ixi + # iz2 = (n*m-iz1) + # iv2 = n*m-iv1 + for i in range(m): + # for (i=0;i<m;i++,ixi++,jyi++,iz1++,iv1++) { + rrA = rA[ixi] * rA[jyi] # rrA = rA[i][ix]*rA[i][jy]; + iiA = iA[ixi] * iA[jyi] # iiA = iA[i][ix]*iA[i][jy]; + riA = rA[ixi] * iA[jyi] # riA = rA[i][ix]*iA[i][jy]; + irA = iA[ixi] * rA[jyi] # irA = iA[i][ix]*rA[i][jy]; + + # Sum frequency effects */ + tmp1 = kfact * 2.0 * (rrA - iiA) * Epij + tmp2 = kfact * 2.0 * (riA + irA) * Epij + rvec[iz1] += tmp1 # rvec[i][jy+ix] += tmp1 + ivec[iz1] += tmp2 # ivec[i][jy+ix] += tmp2 + # rvec[iz2] += tmp1 # rvec[i][n*m-(jy+ix)] += tmp1 + # ivec[iz2] -= tmp2 # ivec[i][n*m-(jy+ix)] -= tmp2 + # iz2++ + + # Difference frequency effects */ + tmp1 = kfact * 2.0 * (rrA + iiA) * Edij + tmp2 = kfact * 2.0 * (riA - irA) * Edij + rvec[iv1] += tmp1 # rvec[i][jy-ix] += tmp1 + ivec[iv1] += tmp2 # ivec[i][jy-ix] -= tmp2 + + # rvec[iv2] += tmp1 + # ivec[iv2] -= tmp2 + # iv2 += 1 + ixi += 1 + jyi += 1 + iz1 += 1 + iv1 += 1 + + +@jit(void(float64[:], float64[:], float64[:], float64[:], + float64[:], float64[:], float64, float64, + int32, int32, int32, int32), nopython=True) +def _deep_water_disufq(rvec, ivec, rA, iA, w, kw, h, g, nmin, nmax, m, n): + # kfact is set to 2 in order to exploit the symmetry. + # If you set kfact to 1, you must uncomment all statements + # including the expressions: rvec[iz2], rvec[iv2], ivec[iz2] and ivec[iv2]. + + kfact = 2.0 + for ix in range(nmin - 1, nmax): + ixi = ix * m + iz1 = 2 * ixi + iz2 = n * m - ixi + kw1 = kw[ix] + Epij = kw1 + for _i in range(m): + rrA = rA[ixi] * rA[ixi] + iiA = iA[ixi] * iA[ixi] + riA = rA[ixi] * iA[ixi] + + # Sum frequency effects along the diagonal + tmp1 = kfact * (rrA - iiA) * Epij + tmp2 = kfact * 2.0 * riA * Epij + rvec[iz1] += tmp1 + ivec[iz1] += tmp2 + ixi += 1 + iz1 += 1 + # rvec[iz2] += tmp1 + # ivec[iz2] -= tmp2 + iz2 += 1 + + # Difference frequency effects are zero along the diagonal + # and are thus not contributing to the mean. + for jy in range(ix + 1, nmax): + kw2 = kw[jy] + Epij = 0.5 * (kw2 + kw1) + Edij = -0.5 * (kw2 - kw1) + # printf("Edij = %f Epij = %f \n", Edij,Epij); + + ixi = ix * m + jyi = jy * m + iz1 = ixi + jyi + iv1 = jyi - ixi + iz2 = (n * m - iz1) + iv2 = (n * m - iv1) + for _i in range(m): + rrA = rA[ixi] * rA[jyi] # rrA = rA[i][ix]*rA[i][jy] + iiA = iA[ixi] * iA[jyi] # iiA = iA[i][ix]*iA[i][jy] + riA = rA[ixi] * iA[jyi] # riA = rA[i][ix]*iA[i][jy] + irA = iA[ixi] * rA[jyi] # irA = iA[i][ix]*rA[i][jy] + + # Sum frequency effects + tmp1 = kfact * 2.0 * (rrA - iiA) * Epij + tmp2 = kfact * 2.0 * (riA + irA) * Epij + rvec[iz1] += tmp1 # rvec[i][ix+jy] += tmp1 + ivec[iz1] += tmp2 # ivec[i][ix+jy] += tmp2 + # rvec[iz2] += tmp1 # rvec[i][n*m-(ix+jy)] += tmp1 + # ivec[iz2] -= tmp2 # ivec[i][n*m-(ix+jy)] -= tmp2 + iz2 += 1 + + # Difference frequency effects */ + tmp1 = kfact * 2.0 * (rrA + iiA) * Edij + tmp2 = kfact * 2.0 * (riA - irA) * Edij + + rvec[iv1] += tmp1 # rvec[i][jy-ix] += tmp1 + ivec[iv1] += tmp2 # ivec[i][jy-ix] += tmp2 + + # rvec[iv2] += tmp1 # rvec[i][n*m-(jy-ix)] += tmp1 + # ivec[iv2] -= tmp2 # ivec[i][n*m-(jy-ix)] -= tmp2 + iv2 += 1 + ixi += 1 + jyi += 1 + iz1 += 1 + iv1 += 1 + + +def disufq(rA, iA, w, kw, h, g, nmin, nmax, m, n): + """ + DISUFQ Is an internal function to spec2nlsdat + + Parameters + ---------- + rA, iA = real and imaginary parts of the amplitudes (size m X n). + w = vector with angular frequencies (w>=0) + kw = vector with wavenumbers (kw>=0) + h = water depth (h >=0) + g = constant acceleration of gravity + nmin = minimum index where rA(:,nmin) and iA(:,nmin) is + greater than zero. + nmax = maximum index where rA(:,nmax) and iA(:,nmax) is + greater than zero. + m = size(rA,1),size(iA,1) + n = size(rA,2),size(iA,2), or size(rvec,2),size(ivec,2) + + returns + ------- + rvec, ivec = real and imaginary parts of the resultant (size m X n). + + DISUFQ returns the summation of difference frequency and sum + frequency effects in the vector vec = rvec +sqrt(-1)*ivec. + The 2'nd order contribution to the Stokes wave is then calculated by + a simple 1D Fourier transform, real(FFT(vec)). + + """ + rvec = np.zeros(n * m) + ivec = np.zeros(n * m) + + if h > 10000: # { /* deep water /Inifinite water depth */ + _deep_water_disufq(rvec, ivec, rA, iA, w, kw, h, g, nmin, nmax, m, n) + else: + _finite_water_disufq(rvec, ivec, rA, iA, w, kw, h, g, nmin, nmax, m, n) + return rvec, ivec + + +@jit(int32[:](float64[:], float64[:], float64[:, :])) +def _findrfc3_astm(array_ext, a, array_out): + """ + Rain flow without time analysis + + Return [ampl ampl_mean nr_of_cycle] + + By Adam Nieslony + Visit the MATLAB Central File Exchange for latest version + http://www.mathworks.com/matlabcentral/fileexchange/3026 + """ + n = len(array_ext) + po = 0 + # The original rainflow counting by Nieslony, unchanged + j = -1 + c_nr1 = 1 + for i in range(n): + j += 1 + a[j] = array_ext[i] + while j >= 2 and abs(a[j - 1] - a[j - 2]) <= abs(a[j] - a[j - 1]): + ampl = abs((a[j - 1] - a[j - 2]) / 2) + mean = (a[j - 1] + a[j - 2]) / 2 + if j == 2: + a[0] = a[1] + a[1] = a[2] + j = 1 + if (ampl > 0): + array_out[po, :] = (ampl, mean, 0.5) + po += 1 + else: + a[j - 2] = a[j] + j = j - 2 + if (ampl > 0): + array_out[po, :] = (ampl, mean, 1.0) + po += 1 + c_nr1 += 1 + + c_nr2 = 1 + for i in range(j): + ampl = abs(a[i] - a[i + 1]) / 2 + mean = (a[i] + a[i + 1]) / 2 + if (ampl > 0): + array_out[po, :] = (ampl, mean, 0.5) + po += 1 + c_nr2 += 1 + return c_nr1, c_nr2 + + +@jit(int32[:](float64[:], float64[:], float64[:], float64[:], float64[:, :])) +def _findrfc5_astm(array_ext, array_t, a, t, array_out): + """ + Rain flow with time analysis + + returns + [ampl ampl_mean nr_of_cycle cycle_begin_time cycle_period_time] + + By Adam Nieslony + Visit the MATLAB Central File Exchange for latest version + http://www.mathworks.com/matlabcentral/fileexchange/3026 + """ + n = len(array_ext) + po = 0 + # The original rainflow counting by Nieslony, unchanged + j = -1 + c_nr1 = 1 + for i in range(n): + j += 1 + a[j] = array_ext[i] + t[j] = array_t[i] + while (j >= 2) and (abs(a[j - 1] - a[j - 2]) <= abs(a[j] - a[j - 1])): + ampl = abs((a[j - 1] - a[j - 2]) / 2) + mean = (a[j - 1] + a[j - 2]) / 2 + period = (t[j - 1] - t[j - 2]) * 2 + atime = t[j - 2] + if j == 2: + a[0] = a[1] + a[1] = a[2] + t[0] = t[1] + t[1] = t[2] + j = 1 + if (ampl > 0): + array_out[po, :] = (ampl, mean, 0.5, atime, period) + po += 1 + else: + a[j - 2] = a[j] + t[j - 2] = t[j] + j = j - 2 + if (ampl > 0): + array_out[po, :] = (ampl, mean, 1.0, atime, period) + po += 1 + c_nr1 += 1 + + c_nr2 = 1 + for i in range(j): + # for (i=0; i<j; i++) { + ampl = abs(a[i] - a[i + 1]) / 2 + mean = (a[i] + a[i + 1]) / 2 + period = (t[i + 1] - t[i]) * 2 + atime = t[i] + if (ampl > 0): + array_out[po, :] = (ampl, mean, 0.5, atime, period) + po += 1 + c_nr2 += 1 + return c_nr1, c_nr2 + + +def findrfc_astm(tp, t=None): + """ + Return rainflow counted cycles + + Nieslony's Matlab implementation of the ASTM standard practice for rainflow + counting ported to a Python C module. + + Parameters + ---------- + tp : array-like + vector of turningpoints (NB! Only values, not sampled times) + t : array-like + vector of sampled times + + Returns + ------- + sig_rfc : array-like + array of shape (n,3) or (n, 5) with: + sig_rfc[:,0] Cycles amplitude + sig_rfc[:,1] Cycles mean value + sig_rfc[:,2] Cycle type, half (=0.5) or full (=1.0) + sig_rfc[:,3] cycle_begin_time (only if t is given) + sig_rfc[:,4] cycle_period_time (only if t is given) + """ + + y1 = np.atleast_1d(tp).ravel() + n = len(y1) + a = np.zeros(n) + if t is None: + sig_rfc = np.zeros((n, 3)) + cnr = _findrfc3_astm(y1, a, sig_rfc) + else: + t1 = np.atleast_1d(t).ravel() + sig_rfc = np.zeros((n, 5)) + t2 = np.zeros(n) + cnr = _findrfc5_astm(y1, t1, a, t2, sig_rfc) + # the sig_rfc was constructed too big in rainflow.rf3, so + # reduce the sig_rfc array as done originally by a matlab mex c function + # n = len(sig_rfc) + return sig_rfc[:n - cnr[0]] + + +if __name__ == '__main__': + pass diff --git a/wetb/wave/spectrum.py b/wetb/wave/spectrum.py new file mode 100644 index 00000000..128d2f9f --- /dev/null +++ b/wetb/wave/spectrum.py @@ -0,0 +1,531 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Mar 2 13:33:21 2018 + +@author: shfe +""" + +from __future__ import absolute_import, division + +import warnings + +from scipy.stats import moment +import scipy.integrate as integrate +import scipy.special as sp +import numpy as np +from numpy import (atleast_1d, minimum, maximum, ones_like, + exp, log, sqrt, where, pi, isfinite, cosh, zeros_like, flatnonzero) + +import matplotlib.pyplot as plt + + +def sech(x): + return 1.0 / cosh(x) + +class ModelSpectrum(object): + type = 'ModelSpectrum' + + def __init__(self, Hm0=7.0, Tp=11.0, **kwds): + self.Hm0 = Hm0 + self.Tp = Tp + + def chk_seastate(self): + """ Check if seastate is valid + """ + + if self.Hm0 < 0: + raise ValueError('Hm0 can not be negative!') + + if self.Tp <= 0: + raise ValueError('Tp must be positve!') + + if self.Hm0 == 0.0: + warnings.warn('Hm0 is zero!') + + self._chk_extra_param() + + def _chk_extra_param(self): + pass + +def jonswap_peakfact(Hm0, Tp): + """ Jonswap peakedness factor, gamma, given Hm0 and Tp + + Parameters + ---------- + Hm0 : significant wave height [m]. + Tp : peak period [s] + + Returns + ------- + gamma : Peakedness parameter of the JONSWAP spectrum + + Details + ------- + A standard value for GAMMA is 3.3. However, a more correct approach is + to relate GAMMA to Hm0 and Tp: + D = 0.036-0.0056*Tp/sqrt(Hm0) + gamma = exp(3.484*(1-0.1975*D*Tp**4/(Hm0**2))) + This parameterization is based on qualitative considerations of deep water + wave data from the North Sea, see Torsethaugen et. al. (1984) + Here GAMMA is limited to 1..7. + + NOTE: The size of GAMMA is the common shape of Hm0 and Tp. + + Examples + -------- + >>> import wafo.spectrum.models as wsm + >>> import pylab as plb + >>> Tp,Hs = plb.meshgrid(range(4,8),range(2,6)) + >>> gam = wsm.jonswap_peakfact(Hs,Tp) + + >>> Hm0 = plb.linspace(1,20) + >>> Tp = Hm0 + >>> [T,H] = plb.meshgrid(Tp,Hm0) + >>> gam = wsm.jonswap_peakfact(H,T) + >>> v = plb.arange(0,8) + + >>> Hm0 = plb.arange(1,11) + >>> Tp = plb.linspace(2,16) + >>> T,H = plb.meshgrid(Tp,Hm0) + >>> gam = wsm.jonswap_peakfact(H,T) + + h = plb.contourf(Tp,Hm0,gam,v);h=plb.colorbar() + h = plb.plot(Tp,gam.T) + h = plb.xlabel('Tp [s]') + h = plb.ylabel('Peakedness parameter') + + plb.close('all') + + See also + -------- + jonswap + """ + Hm0, Tp = atleast_1d(Hm0, Tp) + + x = Tp / sqrt(Hm0) + + gam = ones_like(x) + + k1 = flatnonzero(x <= 5.14285714285714) + if k1.size > 0: # limiting gamma to [1 7] + xk = x.take(k1) + D = 0.036 - 0.0056 * xk # approx 5.061*Hm0**2/Tp**4*(1-0.287*log(gam)) + # gamma + gam.put(k1, minimum(exp(3.484 * (1.0 - 0.1975 * D * xk ** 4.0)), 7.0)) + + return gam + + +def jonswap_seastate(u10, fetch=150000., method='lewis', g=9.81, + output='dict'): + """ + Return Jonswap seastate from windspeed and fetch + + Parameters + ---------- + U10 : real scalar + windspeed at 10 m above mean water surface [m/s] + fetch : real scalar + fetch [m] + method : 'hasselman73' seastate according to Hasselman et. al. 1973 + 'hasselman76' seastate according to Hasselman et. al. 1976 + 'lewis' seastate according to Lewis and Allos 1990 + g : real scalar + accelaration of gravity [m/s**2] + output : 'dict' or 'list' + + Returns + ------- + seastate: dict where + Hm0 : significant wave height [m] + Tp : peak period [s] + gamma : jonswap peak enhancement factor. + sigmaA, + sigmaB : jonswap spectral width parameters. + Ag : jonswap alpha, normalization factor. + + Example + -------- + >>> import wafo.spectrum.models as wsm + >>> fetch = 10000; u10 = 10 + >>> ss = wsm.jonswap_seastate(u10, fetch, output='dict') + >>> for key in sorted(ss.keys()): key, ss[key] + ('Ag', 0.016257903375341734) + ('Hm0', 0.51083679198275533) + ('Tp', 2.7727680999585265) + ('gamma', 2.4824142635861119) + ('sigmaA', 0.07531733139517202) + ('sigmaB', 0.09191208451225134) + >>> S = wsm.Jonswap(**ss) + >>> S.Hm0 + 0.51083679198275533 + + # Alternatively + >>> ss1 = wsm.jonswap_seastate(u10, fetch, output='list') + >>> S1 = wsm.Jonswap(*ss1) + >>> S1.Hm0 + 0.51083679198275533 + + See also + -------- + Jonswap + + + References + ---------- + Lewis, A. W. and Allos, R.N. (1990) + JONSWAP's parameters: sorting out the inconscistencies. + Ocean Engng, Vol 17, No 4, pp 409-415 + + Hasselmann et al. (1973) + Measurements of Wind-Wave Growth and Swell Decay during the Joint + North Sea Project (JONSWAP). + Ergansungsheft, Reihe A(8), Nr. 12, Deutschen Hydrografischen Zeitschrift. + + Hasselmann et al. (1976) + A parametric wave prediction model. + J. phys. oceanogr. Vol 6, pp 200-228 + + """ + + # The following formulas are from Lewis and Allos 1990: + zeta = g * fetch / (u10 ** 2) # dimensionless fetch, Table 1 + # zeta = min(zeta, 2.414655013429281e+004) + if method.startswith('h'): + if method[-1] == '3': # Hasselman et.al (1973) + A = 0.076 * zeta ** (-0.22) + # dimensionless peakfrequency, Table 1 + ny = 3.5 * zeta ** (-0.33) + # dimensionless surface variance, Table 1 + epsilon1 = 9.91e-8 * zeta ** 1.1 + else: # Hasselman et.al (1976) + A = 0.0662 * zeta ** (-0.2) + ny = 2.84 * zeta ** (-0.3) # dimensionless peakfrequency, Table 1 + # dimensionless surface variance, Eq.4 + epsilon1 = 1.6e-7 * zeta + + sa = 0.07 + sb = 0.09 + gam = 3.3 + else: + A = 0.074 * zeta ** (-0.22) # Eq. 10 + ny = 3.57 * zeta ** (-0.33) # dimensionless peakfrequency, Eq. 11 + # dimensionless surface variance, Eq.12 + epsilon1 = 3.512e-4 * A * ny ** (-4.) * zeta ** (-0.1) + sa = 0.05468 * ny ** (-0.32) # Eq. 13 + sb = 0.078314 * ny ** (-0.16) # Eq. 14 + gam = maximum(17.54 * zeta ** (-0.28384), 1) # Eq. 15 + + Tp = u10 / (ny * g) # Table 1 + Hm0 = 4 * sqrt(epsilon1) * u10 ** 2. / g # Table 1 + if output[0] == 'l': + return Hm0, Tp, gam, sa, sb, A + else: + return dict(Hm0=Hm0, Tp=Tp, gamma=gam, sigmaA=sa, sigmaB=sb, Ag=A) + + +def _gengamspec(wn, N=5, M=4): + """ Return Generalized gamma spectrum in dimensionless form + + Parameters + ---------- + wn : arraylike + normalized frequencies, w/wp. + N : scalar + defining the decay of the high frequency part. + M : scalar + defining the spectral width around the peak. + + Returns + ------- + S : arraylike + spectral values, same size as wn. + + The generalized gamma spectrum in non- + dimensional form is defined as: + + S = G0.*wn.**(-N).*exp(-B*wn.**(-M)) for wn > 0 + = 0 otherwise + where + B = N/M + C = (N-1)/M + G0 = B**C*M/gamma(C), Normalizing factor related to Bretschneider form + + Note that N = 5, M = 4 corresponds to a normalized + Bretschneider spectrum. + + Examples + -------- + >>> import wafo.spectrum.models as wsm + >>> import numpy as np + >>> wn = np.linspace(0,4,5) + >>> wsm._gengamspec(wn, N=6, M=2) + array([ 0. , 1.16765216, 0.17309961, 0.02305179, 0.00474686]) + + See also + -------- + Bretschneider + Jonswap, + Torsethaugen + + + References + ---------- + Torsethaugen, K. (2004) + "Simplified Double Peak Spectral Model for Ocean Waves" + In Proc. 14th ISOPE + """ + w = atleast_1d(wn) + S = zeros_like(w) + + k = flatnonzero(w > 0.0) + if k.size > 0: + B = N / M + C = (N - 1.0) / M + + # A = Normalizing factor related to Bretschneider form + # A = B**C*M/gamma(C) + # S[k] = A*wn[k]**(-N)*exp(-B*wn[k]**(-M)) + logwn = log(w.take(k)) + logA = (C * log(B) + log(M) - sp.gammaln(C)) + S.put(k, exp(logA - N * logwn - B * exp(-M * logwn))) + return S + +class Jonswap(ModelSpectrum): + + """ + Jonswap spectral density model + + Member variables + ---------------- + Hm0 : significant wave height (default 7 (m)) + Tp : peak period (default 11 (sec)) + gamma : peakedness factor determines the concentraton + of the spectrum on the peak frequency. + Usually in the range 1 <= gamma <= 7. + default depending on Hm0, Tp, see jonswap_peakedness) + sigmaA : spectral width parameter for w<wp (default 0.07) + sigmaB : spectral width parameter for w<wp (default 0.09) + Ag : normalization factor used when gamma>1: + N : scalar defining decay of high frequency part. (default 5) + M : scalar defining spectral width around the peak. (default 4) + method : String defining method used to estimate Ag when gamma>1 + 'integration': Ag = 1/gaussq(Gf*ggamspec(wn,N,M),0,wnc) (default) + 'parametric' : Ag = (1+f1(N,M)*log(gamma)**f2(N,M))/gamma + 'custom' : Ag = Ag + wnc : wc/wp normalized cut off frequency used when calculating Ag + by integration (default 6) + Parameters + ---------- + w : array-like + angular frequencies [rad/s] + + Description + ----------- + The JONSWAP spectrum is defined as + + S(w) = A * Gf * G0 * wn**(-N)*exp(-N/(M*wn**M)) + where + G0 = Normalizing factor related to Bretschneider form + A = Ag * (Hm0/4)**2 / wp (Normalization factor) + Gf = j**exp(-.5*((wn-1)/s)**2) (Peak enhancement factor) + wn = w/wp + wp = angular peak frequency + s = sigmaA for wn <= 1 + sigmaB for 1 < wn + j = gamma, (j=1, => Bretschneider spectrum) + + The JONSWAP spectrum is assumed to be especially suitable for the North + Sea, and does not represent a fully developed sea. It is a reasonable model + for wind generated sea when the seastate is in the so called JONSWAP range, + i.e., 3.6*sqrt(Hm0) < Tp < 5*sqrt(Hm0) + + The relation between the peak period and mean zero-upcrossing period + may be approximated by + Tz = Tp/(1.30301-0.01698*gamma+0.12102/gamma) + + Examples + --------- + >>> import pylab as plb + >>> import wafo.spectrum.models as wsm + >>> S = wsm.Jonswap(Hm0=7, Tp=11,gamma=1) + >>> S2 = wsm.Bretschneider(Hm0=7, Tp=11) + >>> w = plb.linspace(0,5) + >>> all(np.abs(S(w)-S2(w))<1.e-7) + True + + h = plb.plot(w,S(w)) + plb.close('all') + + See also + -------- + Bretschneider + Tmaspec + Torsethaugen + + References + ----------- + Torsethaugen et al. (1984) + Characteristica for extreme Sea States on the Norwegian continental shelf. + Report No. STF60 A84123. Norwegian Hydrodyn. Lab., Trondheim + + Hasselmann et al. (1973) + Measurements of Wind-Wave Growth and Swell Decay during the Joint + North Sea Project (JONSWAP). + Ergansungsheft, Reihe A(8), Nr. 12, Deutschen Hydrografischen Zeitschrift. + """ + + type = 'Jonswap' + + def __init__(self, Hm0=7.0, Tp=11.0, gamma=None, sigmaA=0.07, sigmaB=0.09, + Ag=None, N=5, M=4, method='integration', wnc=6.0, + chk_seastate=True): + super(Jonswap, self).__init__(Hm0, Tp) + self.N = N + self.M = M + self.sigmaA = sigmaA + self.sigmaB = sigmaB + self.gamma = gamma + self.Ag = Ag + self.method = method + self.wnc = wnc + + if self.gamma is None or not isfinite(self.gamma) or self.gamma < 1: + self.gamma = jonswap_peakfact(Hm0, Tp) + + self._pre_calculate_ag() + + if chk_seastate: + self.chk_seastate() + + def _chk_extra_param(self): + Tp = self.Tp + Hm0 = self.Hm0 + gam = self.gamma + outsideJonswapRange = Tp > 5 * sqrt(Hm0) or Tp < 3.6 * sqrt(Hm0) + if outsideJonswapRange: + txt0 = """ + Hm0=%g,Tp=%g is outside the JONSWAP range. + The validity of the spectral density is questionable. + """ % (Hm0, Tp) + warnings.warn(txt0) + + if gam < 1 or 7 < gam: + txt = """ + The peakedness factor, gamma, is possibly too large. + The validity of the spectral density is questionable. + """ + warnings.warn(txt) + + def _localspec(self, wn): + Gf = self.peak_e_factor(wn) + return Gf * _gengamspec(wn, self.N, self.M) + + def _check_parametric_ag(self, N, M, gammai): + parameters_ok = 3 <= N <= 50 or 2 <= M <= 9.5 and 1 <= gammai <= 20 + if not parameters_ok: + raise ValueError('Not knowing the normalization because N, ' + + 'M or peakedness parameter is out of bounds!') + if self.sigmaA != 0.07 or self.sigmaB != 0.09: + warnings.warn('Use integration to calculate Ag when ' + 'sigmaA!=0.07 or sigmaB!=0.09') + + def _parametric_ag(self): + """ + Original normalization + + NOTE: that Hm0**2/16 generally is not equal to intS(w)dw + with this definition of Ag if sa or sb are changed from the + default values + """ + self.method = 'parametric' + + N = self.N + M = self.M + gammai = self.gamma + f1NM = 4.1 * (N - 2 * M ** 0.28 + 5.3) ** (-1.45 * M ** 0.1 + 0.96) + f2NM = ((2.2 * M ** (-3.3) + 0.57) * N ** (-0.58 * M ** 0.37 + 0.53) - + 1.04 * M ** (-1.9) + 0.94) + self.Ag = (1 + f1NM * log(gammai) ** f2NM) / gammai + # if N == 5 && M == 4, + # options.Ag = (1+1.0*log(gammai).**1.16)/gammai + # options.Ag = (1-0.287*log(gammai)) + # options.normalizeMethod = 'Three' + # elseif N == 4 && M == 4, + # options.Ag = (1+1.1*log(gammai).**1.19)/gammai + + self._check_parametric_ag(N, M, gammai) + + def _custom_ag(self): + self.method = 'custom' + if self.Ag <= 0: + raise ValueError('Ag must be larger than 0!') + + def _integrate_ag(self): + # normalizing by integration + self.method = 'integration' + if self.wnc < 1.0: + raise ValueError('Normalized cutoff frequency, wnc, ' + + 'must be larger than one!') + area1, unused_err1 = integrate.quad(self._localspec, 0, 1) + area2, unused_err2 = integrate.quad(self._localspec, 1, self.wnc) + area = area1 + area2 + self.Ag = 1.0 / area + + def _pre_calculate_ag(self): + """ PRECALCULATEAG Precalculate normalization. + """ + if self.gamma == 1: + self.Ag = 1.0 + self.method = 'parametric' + elif self.Ag is not None: + self._custom_ag() + else: + norm_ag = dict(i=self._integrate_ag, + p=self._parametric_ag, + c=self._custom_ag)[self.method[0]] + norm_ag() + + def peak_e_factor(self, wn): + """ PEAKENHANCEMENTFACTOR + """ + w = maximum(atleast_1d(wn), 0.0) + sab = where(w > 1, self.sigmaB, self.sigmaA) + + wnm12 = 0.5 * ((w - 1.0) / sab) ** 2.0 + Gf = self.gamma ** (exp(-wnm12)) + return Gf + + def __call__(self, wi): + """ JONSWAP spectral density + """ + w = atleast_1d(wi) + if (self.Hm0 > 0.0): + + N = self.N + M = self.M + wp = 2 * pi / self.Tp + wn = w / wp + Ag = self.Ag + Hm0 = self.Hm0 + Gf = self.peak_e_factor(wn) + S = ((Hm0 / 4.0) ** 2 / wp * Ag) * Gf * _gengamspec(wn, N, M) + else: + S = zeros_like(w) + return S + +if __name__ == '__main__': + plt.close('all') + Hm0 = 0.8 + Tp = 4.0 + S = Jonswap(Hm0=Hm0, Tp=Tp, gamma = 3.3) + w = np.arange(0, 50, 0.005) + f = w / (2 * pi) + Sw = S(w) + Sf = Sw * 2 * pi + plt.figure(figsize = (6, 6)) + plt.plot(f, Sf, lw = 2) # change to Hz + plt.xlabel(r'$f$ $[Hz]$', fontsize = 20) + plt.ylabel(r'$S$', fontsize = 20) + plt.xlim([0, 1]) + plt.grid() + plt.tight_layout() \ No newline at end of file diff --git a/wetb/wave/zerocrossing.py b/wetb/wave/zerocrossing.py new file mode 100644 index 00000000..9eafd9ae --- /dev/null +++ b/wetb/wave/zerocrossing.py @@ -0,0 +1,281 @@ +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 19 16:19:17 2018 + +@author: shfe + +This script is used to plot the probability of exceedence of wave elevation for Linear and Nonlinear Wave +""" +import numpy as np +from numpy import atleast_1d, sign, mod, zeros +from wetb.wave import numba_misc +from wetb.signal.spectrum import psd +import warnings + +#try: +# from wafo import c_library as clib # @UnresolvedImport +#except ImportError: +# warnings.warn('c_library not found. Check its compilation.') +# clib = None + +def xor(a, b): + """ + Return True only when inputs differ. + """ + return a ^ b + +def _findcross(x, method='numba'): + '''Return indices to zero up and downcrossings of a vector + ''' + + return numba_misc.findcross(x) + +def findcross(x, v=0.0, kind=None, method='clib'): + ''' + Return indices to level v up and/or downcrossings of a vector + + Parameters + ---------- + x : array_like + vector with sampled values. + v : scalar, real + level v. + kind : string + defines type of wave or crossing returned. Possible options are + 'dw' : downcrossing wave + 'uw' : upcrossing wave + 'cw' : crest wave + 'tw' : trough wave + 'd' : downcrossings only + 'u' : upcrossings only + None : All crossings will be returned + + Returns + ------- + ind : array-like + indices to the crossings in the original sequence x. + + Example + ------- + >>> from matplotlib import pyplot as plt + >>> import wafo.misc as wm + >>> ones = np.ones + >>> np.allclose(findcross([0, 1, -1, 1], 0), [0, 1, 2]) + True + >>> v = 0.75 + >>> t = np.linspace(0,7*np.pi,250) + >>> x = np.sin(t) + >>> ind = wm.findcross(x,v) # all crossings + >>> np.allclose(ind, [ 9, 25, 80, 97, 151, 168, 223, 239]) + True + >>> ind2 = wm.findcross(x,v,'u') + >>> np.allclose(ind2, [ 9, 80, 151, 223]) + True + >>> ind3 = wm.findcross(x,v,'d') + >>> np.allclose(ind3, [ 25, 97, 168, 239]) + True + >>> ind4 = wm.findcross(x,v,'d', method='2') + >>> np.allclose(ind4, [ 25, 97, 168, 239]) + True + + t0 = plt.plot(t,x,'.',t[ind],x[ind],'r.', t, ones(t.shape)*v) + t0 = plt.plot(t[ind2],x[ind2],'o') + plt.close('all') + + See also + -------- + crossdef + wavedef + ''' + xn = np.int8(sign(atleast_1d(x).ravel() - v)) # @UndefinedVariable + ind = _findcross(xn, method) + if ind.size == 0: + warnings.warn('No level v = %0.5g crossings found in x' % v) + return ind + + if kind not in ('du', 'all', None): + if kind == 'd': # downcrossings only + t_0 = int(xn[ind[0] + 1] > 0) + ind = ind[t_0::2] + elif kind == 'u': # upcrossings only + t_0 = int(xn[ind[0] + 1] < 0) + ind = ind[t_0::2] + elif kind in ('dw', 'uw', 'tw', 'cw'): + # make sure that the first is a level v down-crossing + # if kind=='dw' or kind=='tw' + # or that the first is a level v up-crossing + # if kind=='uw' or kind=='cw' + + first_is_down_crossing = int(xn[ind[0]] > xn[ind[0] + 1]) + if xor(first_is_down_crossing, kind in ('dw', 'tw')): + ind = ind[1::] + + n_c = ind.size # number of level v crossings + # make sure the number of troughs and crests are according to the + # wavedef, i.e., make sure length(ind) is odd if dw or uw + # and even if tw or cw + is_odd = mod(n_c, 2) + if xor(is_odd, kind in ('dw', 'uw')): + ind = ind[:-1] + else: + raise ValueError('Unknown wave/crossing definition!' + ' ({})'.format(kind)) + return ind + +def findtc(x_in, v=None, kind=None): + """ + Return indices to troughs and crests of data. + + Parameters + ---------- + x : vector + surface elevation. + v : real scalar + reference level (default v = mean of x). + + kind : string + defines the type of wave. Possible options are + 'dw', 'uw', 'tw', 'cw' or None. + If None indices to all troughs and crests will be returned, + otherwise only the paired ones will be returned + according to the wavedefinition. + + Returns + -------- + tc_ind : vector of ints + indices to the trough and crest turningpoints of sequence x. + v_ind : vector of ints + indices to the level v crossings of the original + sequence x. (d,u) + + Example: + -------- + >>> import matplotlib.pyplot as plt + >>> import wafo.misc as wm + >>> t = np.linspace(0,30,500).reshape((-1,1)) + >>> x = np.hstack((t, np.cos(t))) + >>> x1 = x[0:200,:] + >>> itc, iv = wm.findtc(x1[:,1],0,'dw') + >>> tc = x1[itc,:] + >>> np.allclose(itc, [ 52, 105]) + True + >>> itc, iv = wm.findtc(x1[:,1],0,'uw') + >>> np.allclose(itc, [ 105, 157]) + True + + a = plt.plot(x1[:,0],x1[:,1],tc[:,0],tc[:,1],'ro') + plt.close('all') + + See also + -------- + findtp + findcross, + wavedef + """ + + x = atleast_1d(x_in) + if v is None: + v = x.mean() + + v_ind = findcross(x, v, kind) + n_c = v_ind.size + if n_c <= 2: + warnings.warn('There are no waves!') + return zeros(0, dtype=np.int), zeros(0, dtype=np.int) + + # determine the number of trough2crest (or crest2trough) cycles + is_even = mod(n_c + 1, 2) + n_tc = int((n_c - 1 - is_even) / 2) + + # allocate variables before the loop increases the speed + ind = zeros(n_c - 1, dtype=np.int) + + first_is_down_crossing = (x[v_ind[0]] > x[v_ind[0] + 1]) + if first_is_down_crossing: + f1, f2 = np.argmin, np.argmax + else: + f1, f2 = np.argmax, np.argmin + + for i in range(n_tc): + # trough or crest + j = 2 * i + ind[j] = f1(x[v_ind[j] + 1:v_ind[j + 1] + 1]) + # crest or trough + ind[j + 1] = f2(x[v_ind[j + 1] + 1:v_ind[j + 2] + 1]) + + if (2 * n_tc + 1 < n_c) and (kind in (None, 'tw', 'cw')): + # trough or crest + ind[n_c - 2] = f1(x[v_ind[n_c - 2] + 1:v_ind[n_c - 1] + 1]) + + return v_ind[:n_c - 1] + ind + 1, v_ind + +def trough_crest(time, eta, v=None, wavetype=None): + """ + Return trough and crest turning points + + Parameters + ----------- + v : scalar + reference level (default v = mean of x). + + wavetype : string + defines the type of wave. Possible options are + 'dw', 'uw', 'tw', 'cw' or None. + If None indices to all troughs and crests will be returned, + otherwise only the paired ones will be returned + according to the wavedefinition. + + Returns + -------- + tc : TurningPoints object + with trough and crest turningpoints + """ + ind = findtc(eta, v, wavetype)[0] + eta_tc = eta[ind] + + try: + t = time + except Exception: + t = ind + t_tc = t[ind] + + return t_tc, eta_tc + +def wave_parameters(time, eta): + + """ + Returns several wave parameters from data. + Parameters + ---------- + rate : scalar integer + interpolation rate. Interpolates with spline if greater than one. + Returns + ------- + parameters : dict + wave parameters such as + Ac, At : Crest and trough amplitude, respectively + Tc, Tt : crest time and trough time + Hu, Hd : zero-up- and down-crossing wave height, respectively. + Tu, Td : zero-up- and down-crossing wave period, respectively. + """ + tc_ind, z_ind = findtc(eta, v=None, kind='tw') + + tc_a = eta[tc_ind] + tc_t = time[tc_ind] + + Ac = tc_a[1::2] + At = -tc_a[0::2] + + Tc = tc_t[1::2] + Tt = tc_t[0::2] + + Hu = Ac + At[1:] + Hd = Ac + At[:-1] + + tz = time[z_ind] + tu = tz[1::2] +# td = tz[0::2] + Tu = np.diff(tu) + T_crest = Tc - tu[:-1] + return Ac, At, Tc, Tt, Hu, Hd, Tu, tu, T_crest + \ No newline at end of file -- GitLab