diff --git a/AirSeaFluxCode/src/AirSeaFluxCode.py b/AirSeaFluxCode/src/AirSeaFluxCode.py index 0c07ffe6e1cd54f9fac206ac167fb1a45a352008..a8609549806a6e730f707ca51cba8db44c3c87d8 100644 --- a/AirSeaFluxCode/src/AirSeaFluxCode.py +++ b/AirSeaFluxCode/src/AirSeaFluxCode.py @@ -216,6 +216,7 @@ class S88: self.tsrv, self.psim, self.psit, self.psiq = [ np.zeros(self.arr_shp)*self.msk for _ in range(4)] + # extreme values for first comparison dummy_array = lambda val : np.full(self.T.shape, val)*self.msk # you can use def instead of lambda @@ -259,6 +260,7 @@ class S88: self.ct[ind] = ctq_calc( self.cd10n[ind], self.cd[ind], self.ct10n[ind], self.h_in[1, ind], self.ref10, self.psit[ind]) + # humidity self.cq10n[ind], self.zoq[ind] = ctqn_calc( "cq", self.h_in[2, ind]/self.monob[ind], self.cd10n[ind], @@ -492,7 +494,7 @@ class S88: SST) == np.ndarray, "input type of spd, T and SST should be" " numpy.ndarray" if self.meth in ["S80", "S88", "LP82", "YT96", "UA", "NCAR"]: - assert SST_fl == "bulk", "input SST should be skin for method "+self.meth + assert SST_fl == "bulk", "input SST should be bulk for method "+self.meth if self.meth in ["C30", "C35", "ecmwf", "Beljaars"]: if cskin == 1: assert SST_fl == "bulk", "input SST should be bulk with cool skin correction switched on for method "+self.meth @@ -555,7 +557,6 @@ class S80(S88): class YT96(S88): - def __init__(self): self.meth = "YT96" # no limits to u range as we use eq. 21 for cdn diff --git a/AirSeaFluxCode/src/AirSeaFluxCode_dev.py b/AirSeaFluxCode/src/AirSeaFluxCode_dev.py index 1d07daaedda95cdec13b2ad36727ce9a13cdf99e..fdb712fffd9abb363ce9cddcc1a6f0dc2ba2663b 100644 --- a/AirSeaFluxCode/src/AirSeaFluxCode_dev.py +++ b/AirSeaFluxCode/src/AirSeaFluxCode_dev.py @@ -4,7 +4,7 @@ import pandas as pd import logging from hum_subs import (get_hum, gamma) from util_subs import * -from flux_subs import * +from flux_subs_dev import * from cs_wl_subs import * @@ -569,7 +569,7 @@ class S88: SST) == np.ndarray, "input type of spd, T and SST should be" " numpy.ndarray" if self.meth in ["S80", "S88", "LP82", "YT96", "UA", "NCAR"]: - assert SST_fl == "bulk", "input SST should be skin for method "+self.meth + assert SST_fl == "bulk", "input SST should be bulk for method "+self.meth if self.meth in ["C30", "C35", "ecmwf", "Beljaars"]: if cskin == 1: assert SST_fl == "bulk", "input SST should be bulk with cool skin correction switched on for method "+self.meth diff --git a/AirSeaFluxCode/src/flux_subs_dev.py b/AirSeaFluxCode/src/flux_subs_dev.py new file mode 100644 index 0000000000000000000000000000000000000000..c7dcf2ba7cd7e27bf376f33d2f660cf3d5892bf3 --- /dev/null +++ b/AirSeaFluxCode/src/flux_subs_dev.py @@ -0,0 +1,965 @@ +import numpy as np +from util_subs import (kappa, visc_air) + +# --------------------------------------------------------------------- + + +def cdn_calc(u10n, usr, Ta, grav, meth): + """ + Calculate neutral drag coefficient. + + Parameters + ---------- + u10n : float + neutral 10m wind speed [m/s] + usr : float + friction velocity [m/s] + Ta : float + air temperature [K] + grav : float + gravity [m/s^2] + meth : str + + Returns + ------- + cdn : float + zo : float + """ + cdn = np.zeros(Ta.shape)*np.nan + if meth == "S80": # eq. 14 Smith 1980 + cdn = np.maximum((0.61+0.063*u10n)*0.001, (0.61+0.063*6)*0.001) + elif meth == "LP82": + # Large & Pond 1981 u10n <11m/s & eq. 21 Large & Pond 1982 + cdn = np.where(u10n < 11, 1.2*0.001, (0.49+0.065*u10n)*0.001) + elif meth in ["S88", "UA", "ecmwf", "C30", "C35", "Beljaars"]: + cdn = cdn_from_roughness(u10n, usr, Ta, grav, meth) + elif meth == "YT96": + # convert usr in eq. 21 to cdn to expand for low wind speeds + cdn = np.power((0.10038+u10n*2.17e-3+np.power(u10n, 2)*2.78e-3 - + np.power(u10n, 3)*4.4e-5)/u10n, 2) + elif meth == "NCAR": # eq. 11 Large and Yeager 2009 + cdn = np.where(u10n > 0.5, (0.142+2.7/u10n+u10n/13.09 - + 3.14807e-10*np.power(u10n, 6))*1e-3, + (0.142+2.7/0.5+0.5/13.09 - + 3.14807e-10*np.power(0.5, 6))*1e-3) + cdn = np.where(u10n > 33, 2.34e-3, np.copy(cdn)) + cdn = np.maximum(np.copy(cdn), 0.1e-3) + else: + raise ValueError("Unknown method cdn: "+meth) + + zo = 10/np.exp(kappa/np.sqrt(cdn)) + return cdn, zo +# --------------------------------------------------------------------- + + +def cdn_from_roughness(u10n, usr, Ta, grav, meth): + """ + Calculate neutral drag coefficient from roughness length. + + Parameters + ---------- + u10n : float + neutral 10m wind speed [m/s] + usr : float + friction velocity [m/s] + Ta : float + air temperature [K] + grav : float [m/s] + gravity + meth : str + + Returns + ------- + cdn : float + """ + # cdn = (0.61+0.063*u10n)*0.001 + zo, zc, zs = np.zeros(Ta.shape), np.zeros(Ta.shape), np.zeros(Ta.shape) + for it in range(5): + if meth == "S88": + # Charnock roughness length (eq. 4 in Smith 88) + zc = 0.011*np.power(usr, 2)/grav + # smooth surface roughness length (eq. 6 in Smith 88) + zs = 0.11*visc_air(Ta)/usr + zo = zc + zs # eq. 7 & 8 in Smith 88 + elif meth == "UA": + # valid for 0<u<18m/s # Zeng et al. 1998 (24) + zo = 0.013*np.power(usr, 2)/grav+0.11*visc_air(Ta)/usr + elif meth == "C30": # eq. 25 Fairall et al. 1996a + a = 0.011*np.ones(Ta.shape) + a = np.where(u10n > 10, 0.011+(u10n-10)*(0.018-0.011)/(18-10), + np.where(u10n > 18, 0.018, a)) + zo = a*np.power(usr, 2)/grav+0.11*visc_air(Ta)/usr + elif meth == "C35": # eq.6-11 Edson et al. (2013) + zo = (0.11*visc_air(Ta)/usr + + np.minimum(0.0017*19-0.0050, 0.0017*u10n-0.0050) * + np.power(usr, 2)/grav) + elif meth in ["ecmwf", "Beljaars"]: + # eq. (3.26) p.38 over sea IFS Documentation cy46r1 + # zo = 0.018*np.power(usr, 2)/grav+0.11*visc_air(Ta)/usr + # temporary as in aerobulk + zo = np.minimum(np.abs(zo), 0.001) + else: + raise ValueError("Unknown method for cdn_from_roughness "+meth) + + cdn = np.power(kappa/np.log(10/zo), 2) + # temporary as in aerobulk + if meth == "ecmwf": + cdn = np.maximum(cdn, 0.1e-3) + return cdn +# --------------------------------------------------------------------- + + +def cd_calc(cdn, hin, hout, psim): + """ + Calculate drag coefficient at reference height. + + Parameters + ---------- + cdn : float + neutral drag coefficient + hin : float + wind speed height [m] + hout : float + reference height [m] + psim : float + momentum stability function + + Returns + ------- + cd : float + """ + cd = (cdn/np.power(1+(np.sqrt(cdn)*(np.log(hin/hout)-psim))/kappa, 2)) + return cd +# --------------------------------------------------------------------- + + +def ctqn_calc(corq, zol, cdn, usr, zo, Ta, meth): + """ + Calculate neutral heat and moisture exchange coefficients. + + Parameters + ---------- + corq : flag to select + "ct" or "cq" + zol : float + height over MO length + cdn : float + neutral drag coefficient + usr : float + friction velocity [m/s] + zo : float + surface roughness [m] + Ta : float + air temperature [K] + meth : str + + Returns + ------- + ctqn : float + neutral heat exchange coefficient + zotq : float + roughness length for t or q + """ + if meth in ["S80", "S88", "YT96"]: + cqn = np.ones(Ta.shape)*1.20*0.001 # from S88 + ctn = np.ones(Ta.shape)*1.00*0.001 + zot = 10/(np.exp(np.power(kappa, 2) / (ctn*np.log(10/zo)))) + zoq = 10/(np.exp(np.power(kappa, 2) / (cqn*np.log(10/zo)))) + elif meth == "LP82": + cqn = np.where((zol <= 0), 1.15*0.001, 1*0.001) + ctn = np.where((zol <= 0), 1.13*0.001, 0.66*0.001) + zot = 10/(np.exp(np.power(kappa, 2)/(ctn*np.log(10/zo)))) + zoq = 10/(np.exp(np.power(kappa, 2)/(cqn*np.log(10/zo)))) + elif meth == "NCAR": + # Eq. (9),(12), (13) Large & Yeager, 2009 + cqn = np.maximum(34.6*0.001*np.sqrt(cdn), 0.1e-3) + ctn = np.maximum(np.where(zol < 0, 32.7*1e-3*np.sqrt(cdn), + 18*1e-3*np.sqrt(cdn)), 0.1e-3) + zot = 10/(np.exp(np.power(kappa, 2)/(ctn*np.log(10/zo)))) + zoq = 10/(np.exp(np.power(kappa, 2)/(cqn*np.log(10/zo)))) + elif meth == "UA": + # Zeng et al. 1998 (25) + rr = usr*zo/visc_air(Ta) + zoq = zo/np.exp(2.67*np.power(rr, 1/4)-2.57) + zot = np.copy(zoq) + cqn = np.power(kappa, 2)/(np.log(10/zo)*np.log(10/zoq)) + ctn = np.power(kappa, 2)/(np.log(10/zo)*np.log(10/zoq)) + elif meth == "C30": + rr = zo*usr/visc_air(Ta) + zoq = np.minimum(5e-5/np.power(rr, 0.6), 1.15e-4) # moisture roughness + zot = np.copy(zoq) # temperature roughness + cqn = np.power(kappa, 2)/np.log(10/zo)/np.log(10/zoq) + ctn = np.power(kappa, 2)/np.log(10/zo)/np.log(10/zot) + elif meth == "C35": + rr = zo*usr/visc_air(Ta) + zoq = np.minimum(5.8e-5/np.power(rr, 0.72), 1.6e-4) # moisture rough. + zot = np.copy(zoq) # temperature roughness + cqn = np.power(kappa, 2)/np.log(10/zo)/np.log(10/zoq) + ctn = np.power(kappa, 2)/np.log(10/zo)/np.log(10/zot) + elif meth in ["ecmwf", "Beljaars"]: + # eq. (3.26) p.38 over sea IFS Documentation cy46r1 + # zot = 0.40*visc_air(Ta)/usr + # zoq = 0.62*visc_air(Ta)/usr + # temporary as in aerobulk next 2lines + # eq.3.26, Chap.3, p.34, IFS doc - Cy31r1 + zot = np.minimum(np.abs(zot), 0.001) + zoq = np.minimum(np.abs(zoq), 0.001) + # cqn = np.power(kappa, 2)/np.log(10/zo)/np.log(10/zoq) + # ctn = np.power(kappa, 2)/np.log(10/zo)/np.log(10/zot) + # temporary as in aerobulk + ctn = np.maximum(ctn, 0.1e-3) + cqn = np.maximum(cqn, 0.1e-3) + else: + raise ValueError("Unknown method ctqn: "+meth) + + if corq == "ct": + ctqn = ctn + zotq = zot + elif corq == "cq": + ctqn = cqn + zotq = zoq + else: + raise ValueError("Unknown flag - should be ct or cq: "+corq) + + return ctqn, zotq +# --------------------------------------------------------------------- + + +def ctq_calc(cdn, cd, ctqn, hin, hout, psitq): + """ + Calculate heat and moisture exchange coefficients at reference height. + + Parameters + ---------- + cdn : float + neutral drag coefficient + cd : float + drag coefficient at reference height + ctqn : float + neutral heat or moisture exchange coefficient + hin : float + original temperature or humidity sensor height [m] + hout : float + reference height [m] + psitq : float + heat or moisture stability function + + Returns + ------- + ctq : float + heat or moisture exchange coefficient + """ + ctq = (ctqn*np.sqrt(cd/cdn) / + (1+ctqn*((np.log(hin/hout)-psitq)/(kappa*np.sqrt(cdn))))) + + return ctq +# --------------------------------------------------------------------- + + +def get_stabco(meth): + r""" + Give the coefficients $\alpha$, $\beta$, $\gamma$ for stability functions. + + Parameters + ---------- + meth : str + + Returns + ------- + coeffs : float + """ + alpha, beta, gamma = 0, 0, 0 + if meth in ["S80", "S88", "NCAR", "UA", "ecmwf", "C30", "C35", "Beljaars"]: + alpha, beta, gamma = 16, 0.25, 5 # Smith 1980, from Dyer (1974) + elif meth == "LP82": + alpha, beta, gamma = 16, 0.25, 7 + elif meth == "YT96": + alpha, beta, gamma = 20, 0.25, 5 + else: + raise ValueError("Unknown method stabco: "+meth) + coeffs = np.zeros(3) + coeffs[0] = alpha + coeffs[1] = beta + coeffs[2] = gamma + return coeffs +# --------------------------------------------------------------------- + + +def psim_calc(zol, meth): + """ + Calculate momentum stability function. + + Parameters + ---------- + zol : float + height over MO length + meth : str + + Returns + ------- + psim : float + """ + if meth == "ecmwf": + psim = psim_ecmwf(zol) + elif meth in ["C30", "C35"]: + psim = psiu_26(zol, meth) + elif meth == "Beljaars": # Beljaars (1997) eq. 16, 17 + psim = np.where(zol < 0, psim_conv(zol, meth), psi_Bel(zol)) + else: + psim = np.where(zol < 0, psim_conv(zol, meth), + psim_stab(zol, meth)) + return psim +# --------------------------------------------------------------------- + + +def psit_calc(zol, meth): + """ + Calculate heat stability function. + + Parameters + ---------- + zol : float + height over MO length + meth : str + parameterisation method + + Returns + ------- + psit : float + """ + if meth == "ecmwf": + # psit = np.where(zol < 0, psi_conv(zol, meth), + # psi_ecmwf(zol)) + # temporary as in aerobulk + psit = psi_ecmwf(zol) + elif meth in ["C30", "C35"]: + psit = psit_26(zol) + elif meth == "Beljaars": # Beljaars (1997) eq. 16, 17 + psit = np.where(zol < 0, psi_conv(zol, meth), psi_Bel(zol)) + else: + psit = np.where(zol < 0, psi_conv(zol, meth), + psi_stab(zol, meth)) + return psit +# --------------------------------------------------------------------- + + +def psi_Bel(zol): + """ + Calculate momentum/heat stability function. + + Parameters + ---------- + zol : float + height over MO length + meth : str + parameterisation method + + Returns + ------- + psit : float + """ + a, b, c, d = 0.7, 0.75, 5, 0.35 + psi = -(a*zol+b*(zol-c/d)*np.exp(-d*zol)+b*c/d) + return psi +# --------------------------------------------------------------------- + + +def psi_ecmwf(zol): + """ + Calculate heat stability function for stable conditions. + + For method ecmwf + + Parameters + ---------- + zol : float + height over MO length + + Returns + ------- + psit : float + """ + # eq (3.22) p. 37 IFS Documentation cy46r1 + # a, b, c, d = 1, 2/3, 5, 0.35 + # psit = -b*(zol-c/d)*np.exp(-d*zol)-np.power(1+(2/3)*a*zol, 1.5)-(b*c)/d+1 + # temporary as in aerobulk + a, b, c, d = 1, 2/3, 5, 0.35 + zol = np.minimum(np.copy(zol), 5) # Very stable conditions (L>0 and big) + # Unstable eq (3.20) p. 37 IFS Documentation cy46r1 + psi_unst = 2*np.log((1+np.sqrt(np.abs(1-16*zol)))/2) + # Stable eq (3.22) p. 37 IFS Documentation cy46r1 + psi_stab = -b*(zol-c/d)*np.exp(-d*zol) - \ + np.power(np.abs(1+(2/3)*a*zol), 1.5)-b*c/d+1 + # Brodeau added np.abs() to avoid NaN values when unstable, + # which contaminates the unstable solution... + psit = np.where(zol > 0, psi_stab, psi_unst) + return psit +# --------------------------------------------------------------------- + + +def psit_26(zol): + """ + Compute temperature structure function as in C35. + + Parameters + ---------- + zol : float + height over MO length + + Returns + ------- + psi : float + """ + b, d = 2/3, 0.35 + dzol = np.minimum(d*zol, 50) + psi = -1*((1+b*zol)**1.5+b*(zol-14.28)*np.exp(-dzol)+8.525) + k = np.where(zol < 0) + x = np.sqrt(1-15*zol[k]) + psik = 2*np.log((1+x)/2) + x = np.power(1-34.15*zol[k], 1/3) + psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) * + np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3)) + f = np.power(zol[k], 2)/(1+np.power(zol[k], 2)) + psi[k] = (1-f)*psik+f*psic + return psi +# --------------------------------------------------------------------- + + +def psi_conv(zol, meth): + """ + Calculate heat stability function for unstable conditions. + + Parameters + ---------- + zol : float + height over MO length + meth : str + parameterisation method + + Returns + ------- + psit : float + """ + coeffs = get_stabco(meth) + alpha, beta = coeffs[0], coeffs[1] + xtmp = np.power(1-alpha*zol, beta) + psit = 2*np.log((1+np.power(xtmp, 2))*0.5) + return psit +# --------------------------------------------------------------------- + + +def psi_stab(zol, meth): + """ + Calculate heat stability function for stable conditions. + + Parameters + ---------- + zol : float + height over MO length + meth : str + parameterisation method + + Returns + ------- + psit : float + """ + coeffs = get_stabco(meth) + gamma = coeffs[2] + psit = -gamma*zol + return psit +# --------------------------------------------------------------------- + + +def psim_ecmwf(zol): + """ + Calculate momentum stability function for method ecmwf. + + Parameters + ---------- + zol : float + height over MO length + + Returns + ------- + psim : float + """ + # temporary as in aerobulk + zol = np.minimum(np.copy(zol), 5) # Very stable conditions (L>0 and big!) + coeffs = get_stabco("ecmwf") + alpha, beta = coeffs[0], coeffs[1] + xtmp = np.power(1-alpha*zol, beta) + psi_unst = np.pi/2-2 * \ + np.arctan(xtmp)+np.log((np.power(1+xtmp, 2)*(1+np.power(xtmp, 2)))/8) + # Stable eq.3.22 p. 37 IFS Documentation cy46r1 + a, b, c, d = 1, 2/3, 5, 0.35 + psi_stab = -b*(zol-c/d)*np.exp(-d*zol)-a*zol-(b*c)/d + psim = np.where(zol < 0, psi_unst, psi_stab) + # eq (3.20, 3.22) p. 37 IFS Documentation cy46r1 + # coeffs = get_stabco("ecmwf") + # alpha, beta = coeffs[0], coeffs[1] + # xtmp = np.power(1-alpha*zol, beta) + # a, b, c, d = 1, 2/3, 5, 0.35 + # psim = np.where(zol < 0, np.pi/2-2*np.arctan(xtmp) + + # np.log((np.power(1+xtmp, 2)*(1+np.power(xtmp, 2)))/8), + # -b*(zol-c/d)*np.exp(-d*zol)-a*zol-(b*c)/d) + return psim +# --------------------------------------------------------------------- + + +def psiu_26(zol, meth): + """ + Compute velocity structure function C35. + + Parameters + ---------- + zol : float + height over MO length + + Returns + ------- + psi : float + """ + if meth == "C30": + dzol = np.minimum(0.35*zol, 50) # stable + psi = -1*((1+zol)+0.6667*(zol-14.28)*np.exp(-dzol)+8.525) + k = np.where(zol < 0) # unstable + x = (1-15*zol[k])**0.25 + psik = (2*np.log((1+x)/2)+np.log((1+x*x)/2)-2*np.arctan(x) + + 2*np.arctan(1)) + x = (1-10.15*zol[k])**(1/3) + psic = (1.5*np.log((1+x+x*x)/3) - + np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) + + 4*np.arctan(1)/np.sqrt(3)) + f = zol[k]**2/(1+zol[k]**2) + psi[k] = (1-f)*psik+f*psic + elif meth == "C35": + dzol = np.minimum(50, 0.35*zol) # stable + a, b, c, d = 0.7, 3/4, 5, 0.35 + psi = -1*(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d) + k = np.where(zol < 0) # unstable + x = np.power(1-15*zol[k], 1/4) + psik = 2*np.log((1+x)/2)+np.log((1+x*x)/2) - \ + 2*np.arctan(x)+2*np.arctan(1) + x = np.power(1-10.15*zol[k], 1/3) + psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) * + np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3)) + f = np.power(zol[k], 2)/(1+np.power(zol[k], 2)) + psi[k] = (1-f)*psik+f*psic + + return psi +# ---------------------------------------------------------------------------- + + +def psim_conv(zol, meth): + """ + Calculate momentum stability function for unstable conditions. + + Parameters + ---------- + zol : float + height over MO length + meth : str + parameterisation method + + Returns + ------- + psim : float + """ + coeffs = get_stabco(meth) + alpha, beta = coeffs[0], coeffs[1] + xtmp = np.power(1-alpha*zol, beta) + psim = (2*np.log((1+xtmp)*0.5)+np.log((1+np.power(xtmp, 2))*0.5) - + 2*np.arctan(xtmp)+np.pi/2) + return psim +# --------------------------------------------------------------------- + + +def psim_stab(zol, meth): + """ + Calculate momentum stability function for stable conditions. + + Parameters + ---------- + zol : float + height over MO length + meth : str + parameterisation method + + Returns + ------- + psim : float + """ + coeffs = get_stabco(meth) + gamma = coeffs[2] + psim = -gamma*zol + return psim +# --------------------------------------------------------------------- + + +def get_gust_old(beta, Ta, usr, tsrv, zi, grav): + """ + Compute gustiness. + + Parameters + ---------- + beta : float + constant + Ta : float + air temperature [K] + usr : float + friction velocity [m/s] + tsrv : float + star virtual temperature of air [K] + zi : int + scale height of the boundary layer depth [m] + grav : float + gravity + + Returns + ------- + ug : float [m/s] + """ + if np.nanmax(Ta) < 200: # convert to K if in Celsius + Ta = Ta+273.16 + # minus sign to allow cube root + Bf = (-grav/Ta)*usr*tsrv + ug = np.ones(np.shape(Ta))*0.2 + ug = np.where(Bf > 0, beta*np.power(Bf*zi, 1/3), 0.2) + return ug +# --------------------------------------------------------------------- + + +def get_gust(beta, zi, ustb, Ta, usr, tsrv, grav): + """ + Compute gustiness. + + Parameters + ---------- + beta : float + constant + zi : int + scale height of the boundary layer depth [m] + ustb : float + gust wind in stable conditions [m/s] + Ta : float + air temperature [K] + usr : float + friction velocity [m/s] + tsrv : float + star virtual temperature of air [K] + + grav : float + gravity + + Returns + ------- + ug : float [m/s] + """ + if np.nanmax(Ta) < 200: # convert to K if in Celsius + Ta = Ta+273.16 + # minus sign to allow cube root + Bf = (-grav/Ta)*usr*tsrv + ug = np.ones(np.shape(Ta))*ustb + ug = np.where(Bf > 0, np.maximum(beta*np.power(Bf*zi, 1/3), ustb), ustb) + # ug = np.where(Bf > 0, beta*np.power(Bf*zi, 1/3), ustb) + return ug +# --------------------------------------------------------------------- + + +def apply_GF(gust, spd, wind, step): + """ + Apply gustiness factor according if gustiness ON. + + There are different ways to remove the effect of gustiness according to + the user's choice. + + Parameters + ---------- + gust : int + option on how to apply gustiness + 0: gustiness is switched OFF + 1: gustiness is switched ON following Fairall et al. + 2: gustiness is switched ON and GF is removed from TSFs u10n, uref + 3: gustiness is switched ON and GF=1 + 4: gustiness is switched ON following Zeng et al. 1998 or + Brodeau et al. 2006 + 5: gustiness is switched ON following C35 matlab code + spd : float + wind speed [ms^{-1}] + wind : float + wind speed including gust [ms^{-1}] + step : str + step during AirSeaFluxCode the GF is applied: "u", "TSF" + + Returns + ------- + GustFact : float + gustiness factor. + + """ + # 1. following C35 documentation, 2. use GF to TSF, u10n uzout, + # 3. GF=1, 4. UA, 5. C35 code 6. ecmwf aerobulk) + # ratio of gusty to horizontal wind; gustiness factor + if step in ["u"]: + GustFact = wind*0+1 + if gust[0] in [1, 2]: + GustFact = np.sqrt(wind/spd) + elif gust[0] == 5: + # as in C35 matlab code + GustFact = wind/spd + elif step == "TSF": + # remove effect of gustiness from TSFs + # here it is a 3xspd.shape array + GustFact = np.ones([3, spd.shape[0]], dtype=float) + # GustFact = np.empty([3, spd.shape[0]], dtype=float)*np.nan + GustFact[0, :] = wind/spd + GustFact[1:3, :] = wind*0+1 + # following Fairall et al. (2003) + if gust[0] == 2: + # usr is divided by (GustFact)^0.5 (here applied to sensible and + # latent as well as tau) + GustFact[1:3, :] = np.sqrt(wind/spd) + elif gust[0] == 3: + GustFact[0, :] = wind*0+1 + return GustFact +# --------------------------------------------------------------------- + + +def get_strs(hin, monob, wind, zo, zot, zoq, dt, dq, cd, ct, cq, meth): + """ + Calculate star wind speed, temperature and specific humidity. + + Parameters + ---------- + hin : float + sensor heights [m] + monob : float + M-O length [m] + wind : float + wind speed [m/s] + zo : float + momentum roughness length [m] + zot : float + temperature roughness length [m] + zoq : float + moisture roughness length [m] + dt : float + temperature difference [K] + dq : float + specific humidity difference [g/kg] + cd : float + drag coefficient + ct : float + temperature exchange coefficient + cq : float + moisture exchange coefficient + meth : str + bulk parameterisation method option: "S80", "S88", "LP82", "YT96", + "UA", "NCAR", "C30", "C35", "ecmwf", "Beljaars" + + Returns + ------- + usr : float + friction wind speed [m/s] + tsr : float + star temperature [K] + qsr : float + star specific humidity [g/kg] + + """ + if meth == "UA": + # Zeng et al. 1998 + # away from extremes UA follows e.g. S80 + usr = wind*np.sqrt(cd) + tsr = ct*wind*dt/usr + qsr = cq*wind*dq/usr + + # momentum + hol0 = hin[0]/np.copy(monob) + # very unstable (Zeng et al. 1998 eq 7) + usr = np.where( + hol0 <= -1.574, wind*kappa/(np.log(-1.574*monob/zo) - + psim_calc(-1.574, meth) + + psim_calc(zo/monob, meth) + + 1.14*(np.power(-hin[0]/monob, 1/3) - + np.power(1.574, 1/3))), usr) + # very stable (Zeng et al. 1998 eq 10) + usr = np.where( + hol0 > 1, wind*kappa/(np.log(monob/zo)+5-5*zo/monob + + 5*np.log(hin[0]/monob)+hin[0]/monob-1), usr) + + # temperature + hol1 = hin[1]/np.copy(monob) + # very unstable (Zeng et al. 1998 eq 11) + tsr = np.where( + hol1 < -0.465, kappa*dt/(np.log((-0.465*monob)/zot) - + psit_calc(-0.465, meth) + + 0.8*(np.power(0.465, -1/3) - + np.power(-hin[1]/monob, -1/3))), tsr) + # very stable (Zeng et al. 1998 eq 14) + tsr = np.where( + hol1 > 1, kappa*(dt)/(np.log(monob/zot)+5-5*zot/monob + + 5*np.log(hin[1]/monob)+hin[1]/monob-1), tsr) + + # humidity + hol2 = hin[2]/monob + # very unstable (Zeng et al. 1998 eq 11) + qsr = np.where( + hol2 < -0.465, kappa*dq/(np.log((-0.465*monob)/zoq) - + psit_calc(-0.465, meth) + + psit_calc(zoq/monob, meth) + + 0.8*(np.power(0.465, -1/3) - + np.power(-hin[2]/monob, -1/3))), qsr) + # very stable (Zeng et al. 1998 eq 14) + qsr = np.where(hol2 > 1, kappa*dq/(np.log(monob/zoq)+5-5*zoq/monob + + 5*np.log(hin[2]/monob) + + hin[2]/monob-1), qsr) + else: + usr = wind*np.sqrt(cd) + tsr = ct*wind*dt/usr + qsr = cq*wind*dq/usr + return usr, tsr, qsr +# --------------------------------------------------------------------- + + +def get_tsrv(tsr, qsr, Ta, qair): + """ + Calculate virtual star temperature. + + Parameters + ---------- + tsr : float + star temperature (K) + qsr : float + star specific humidity (g/kg) + Ta : float + air temperature (K) + qair : float + air specific humidity (g/kg) + + Returns + ------- + tsrv : float + virtual star temperature (K) + + """ + # as in aerobulk One_on_L in mod_phymbl.f90 + # tsrv = tsr+0.6077*Ta*qsr + tsrv = tsr*(1+0.6077*qair)+0.6077*Ta*qsr + return tsrv + +# --------------------------------------------------------------------- + + +def get_Rb(grav, usr, hin_u, hin_t, tv, dtv, wind, monob, meth): + """ + Calculate bulk Richardson number. + + Parameters + ---------- + grav : float + acceleration due to gravity (m/s2) + usr : float + friction wind speed (m/s) + hin_u : float + u sensor height (m) + hin_t : float + t sensor height (m) + tv : float + virtual temperature (K) + dtv : float + virtual temperature difference, air and sea (K) + wind : float + wind speed (m/s) + monob : float + Monin-Obukhov length from previous iteration step (m) + meth : str + bulk parameterisation method option: "S80", "S88", "LP82", "YT96", + "UA", "NCAR", "C30", "C35", "ecmwf", "Beljaars" + + Returns + ------- + Rb : float + Richardson number + + """ + # now input dtv + # tvs = sst*(1+0.6077*qsea) # virtual SST + # dtv = tv - tvs # virtual air - sea temp. diff + # adjust wind to t measurement height + uz = (wind-usr/kappa*(np.log(hin_u/hin_t)-psim_calc(hin_u/monob, meth) + + psim_calc(hin_t/monob, meth))) + Rb = grav*dtv*hin_t/(tv*uz*uz) + return Rb + +# --------------------------------------------------------------------- + + +def get_LRb(Rb, hin_t, monob, zo, zot, meth): + """ + Calculate Monin-Obukhov length following ecmwf (IFS Documentation cy46r1). + + default for methods ecmwf and Beljaars + + Parameters + ---------- + Rb : float + Richardson number + hin_t : float + t sensor height (m) + monob : float + Monin-Obukhov length from previous iteration step (m) + zo : float + surface roughness (m) + zot : float + temperature roughness length (m) + meth : str + bulk parameterisation method option: "S80", "S88", "LP82", "YT96", + "UA", "NCAR", "C30", "C35", "ecmwf", "Beljaars" + + Returns + ------- + monob : float + M-O length (m) + + """ + zol = Rb*(np.power( + np.log((hin_t+zo)/zo)-psim_calc((hin_t+zo)/monob, meth) + + psim_calc(zo/monob, meth), 2)/(np.log((hin_t+zo)/zot) - + psit_calc((hin_t+zo)/monob, meth) + + psit_calc(zot/monob, meth))) + monob = hin_t/zol + return monob + +# --------------------------------------------------------------------- + + +def get_Ltsrv(tsrv, grav, tv, usr): + """ + Calculate Monin-Obukhov length from tsrv. + + Parameters + ---------- + tsrv : float + virtual star temperature (K) + grav : float + acceleration due to gravity (m/s2) + tv : float + virtual temperature (K) + usr : float + friction wind speed (m/s) + + Returns + ------- + monob : float + M-O length (m) + + """ + tsrv = np.maximum(np.abs(tsrv), 1e-9)*np.sign(tsrv) + monob = (np.power(usr, 2)*tv)/(grav*kappa*tsrv) + # temporary as in aerobulk + # monob = np.maximum(np.power(usr, 2)*tv, 1e-9)/(grav*kappa*tsrv) + # monob = 1/np.minimum(np.abs(1/monob), 200)*np.sign(1/monob) + return monob + +# ---------------------------------------------------------------------