Commit 010a5609 authored by sbiri's avatar sbiri
Browse files

AirSeaFluxCode.py with various switch changes for gustiness, tolerance limits,...

AirSeaFluxCode.py with various switch changes for gustiness, tolerance limits, output to missing value for no convergence, humidity options.

flux_subs.py minor changes to improve computations
parent a19d038b
...@@ -6,20 +6,14 @@ from flux_subs import (kappa, CtoK, get_heights, cdn_calc, cd_calc, get_skin, ...@@ -6,20 +6,14 @@ from flux_subs import (kappa, CtoK, get_heights, cdn_calc, cd_calc, get_skin,
gc, qsat_sea, qsat_air, visc_air, psit_26, psiu_26) gc, qsat_sea, qsat_air, visc_air, psit_26, psiu_26)
def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, def AirSeaFluxCode(spd, T, SST, lat=None, hum=None, P=None,
gust, meth, qmeth, n): hin=18, hout=10, Rl=None, Rs=None, jcool=None,
gust=None, meth="S80", qmeth="Buck2", tol=None, n=10,
out=0):
""" Calculates momentum and heat fluxes using different parameterizations """ Calculates momentum and heat fluxes using different parameterizations
Parameters Parameters
---------- ----------
meth : str
"S80","S88","LP82","YT96","UA","LY04","C30","C35","C40","ERA5"
qmeth : str
is the saturation evaporation method to use amongst
"HylandWexler","Hardy","Preining","Wexler","GoffGratch","CIMO",
"MagnusTetens","Buck","Buck2","WMO","WMO2000","Sonntag","Bolton",
"IAPWS","MurphyKoop"]
default is Buck2
spd : float spd : float
relative wind speed in m/s (is assumed as magnitude difference relative wind speed in m/s (is assumed as magnitude difference
between wind and surface current vectors) between wind and surface current vectors)
...@@ -28,28 +22,52 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -28,28 +22,52 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
SST : float SST : float
sea surface temperature in K (will convert if < 200) sea surface temperature in K (will convert if < 200)
lat : float lat : float
latitude (deg) latitude (deg), default 45deg
RH : float hum : float
relative humidity in % humidity input switch 2x1 [x, values] default is relative humidity
x=0 : relative humidity in %
x=1 : specific humidity (g/kg)
x=2 : dew point temperature (K)
P : float P : float
air pressure air pressure (hPa), default 1013hPa
hin : float hin : float
sensor heights in m (array 3x1 or 3xn) default 10m ([10, 10, 10]) sensor heights in m (array 3x1 or 3xn), default 18m
hout : float hout : float
output height, default is 10m output height, default is 10m
zi : int
PBL height (m)
Rl : float Rl : float
downward longwave radiation (W/m^2) downward longwave radiation (W/m^2)
Rs : float Rs : float
downward shortwave radiation (W/m^2) downward shortwave radiation (W/m^2)
jcool : int jcool : int
0 if sst is true ocean skin temperature, else 1 0 switch cool skin adjustment off, else 1
default is 1
gust : int gust : int
1 to include the effect of gustiness, else 0 3x1 [x, beta, zi] x=1 to include the effect of gustiness, else 0
beta gustiness parameter, beta=1 for UA, beta=1.2 for COARE
zi PBL height (m) 600 for COARE, 1000 for UA and ERA5, 800 default
default for COARE [1, 1.2, 600]
default for UA, ERA5 [1, 1, 1000]
default else [1, 1.2, 800]
meth : str
"S80","S88","LP82","YT96","UA","LY04","C30","C35","C40","ERA5"
qmeth : str
is the saturation evaporation method to use amongst
"HylandWexler","Hardy","Preining","Wexler","GoffGratch","CIMO",
"MagnusTetens","Buck","Buck2","WMO","WMO2000","Sonntag","Bolton",
"IAPWS","MurphyKoop"]
default is Buck2
tol : float
4x1 or 7x1 [option, lim1-3 or lim1-6]
option : 'flux' to set tolerance limits for fluxes only lim1-3
option : 'ref' to set tolerance limits for height adjustment lim-1-3
option : 'all' to set tolerance limits for both fluxes and height
adjustment lim1-6 ['all', 0.01, 0.01, 5e-05, 0.01, 1, 1]
default is tol=['flux', 0.01, 1, 1]
n : int n : int
number of iterations number of iterations (defautl = 10)
out : int
set 0 to set points that have not converged to missing (default)
set 1 to keep points
Returns Returns
------- -------
res : array that contains res : array that contains
...@@ -77,9 +95,9 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -77,9 +95,9 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
22. surface roughness length (zo) 22. surface roughness length (zo)
23. heat roughness length (zot) 23. heat roughness length (zot)
24. moisture roughness length (zoq) 24. moisture roughness length (zoq)
25. velocity at reference height (urefs) 25. velocity at reference height (uref)
26. temperature at reference height (trefs) 26. temperature at reference height (tref)
27. specific humidity at reference height (qrefs) 27. specific humidity at reference height (qref)
28. number of iterations until convergence 28. number of iterations until convergence
ind : int ind : int
the indices in the matrix for the points that did not converge the indices in the matrix for the points that did not converge
...@@ -91,43 +109,52 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -91,43 +109,52 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
ref_ht, tlapse = 10, 0.0098 # reference height, lapse rate ref_ht, tlapse = 10, 0.0098 # reference height, lapse rate
h_in = get_heights(hin, len(spd)) # heights of input measurements/fields h_in = get_heights(hin, len(spd)) # heights of input measurements/fields
h_out = get_heights(hout, 1) # desired height of output variables h_out = get_heights(hout, 1) # desired height of output variables
if np.all(np.isnan(lat)): # set latitude to 45deg if empty
lat=45*np.ones(spd.shape)
g = gc(lat, None) # acceleration due to gravity
# if input values are nan break # if input values are nan break
if (np.all(np.isnan(spd)) or np.all(np.isnan(T)) or np.all(np.isnan(SST))): if (np.all(np.isnan(spd)) or np.all(np.isnan(T)) or np.all(np.isnan(SST))):
sys.exit("input wind, T or SST is empty") sys.exit("input wind, T or SST is empty")
logging.debug('all input is nan') logging.debug('all spd or T or SST input is nan')
if (np.all(np.isnan(RH)) and meth == "C35"): if (np.all(lat) == None): # set latitude to 45deg if empty
RH = np.ones(spd.shape)*80 # if empty set to default for COARE3.5 lat = 45*np.ones(spd.shape)
elif (np.all(np.isnan(RH))): elif ((np.all(lat) != None) and (np.size(lat) == 1)):
sys.exit("input RH is empty") lat = np.ones(spd.shape)*np.copy(lat)
logging.debug('input RH is empty') g = gc(lat, None) # acceleration due to gravity
if (np.all(np.isnan(P)) and (meth == "C30" or meth == "C40")): if ((np.all(P) == None) and (meth == "C30" or meth == "C40")):
P = np.ones(spd.shape)*1015 # if empty set to default for COARE3.0 P = np.ones(spd.shape)*1015 # if empty set to default for COARE3.0
elif ((np.all(np.isnan(P))) and meth == "C35"): elif ((np.all(P) == None) or np.all(np.isnan(P))):
P = np.ones(spd.shape)*1013 # if empty set to default for COARE3.5 P = np.ones(spd.shape)*1013
elif (np.all(np.isnan(P))): logging.debug('input P is empty and set to 1013hPa')
sys.exit("input P is empty") elif (((np.all(P) != None) or np.all(~np.isnan(P))) and np.size(P) == 1):
logging.debug('input P is empty') P = np.ones(spd.shape)*np.copy(P)
if (np.all(np.isnan(Rl)) and meth == "C30"): if ((np.all(Rl) == None or np.all(np.isnan(Rl))) and meth == "C30"):
Rl = np.ones(spd.shape)*150 # set to default for COARE3.0 Rl = np.ones(spd.shape)*150 # set to default for COARE3.0
elif ((np.all(np.isnan(Rl)) and meth == "C35") or elif (((np.all(Rl) == None or np.all(np.isnan(Rl))) and meth == "C35") or
(np.all(np.isnan(Rl)) and meth == "C40")): ((np.all(Rl) == None or np.all(np.isnan(Rl))) and meth == "C40")):
Rl = np.ones(spd.shape)*370 # set to default for COARE3.5 Rl = np.ones(spd.shape)*370 # set to default for COARE3.5
elif (np.all(np.isnan(Rl))): elif (np.all(Rl) == None or np.all(np.isnan(Rl))):
Rl = np.ones(spd.shape)*370 # set to default for COARE3.5 Rl = np.ones(spd.shape)*370 # set to default for COARE3.5
if (np.all(np.isnan(Rs)) and meth == "C30"): if ((np.all(Rs) == None or np.all(np.isnan(Rs))) and meth == "C30"):
Rs = np.ones(spd.shape)*370 # set to default for COARE3.0 Rs = np.ones(spd.shape)*370 # set to default for COARE3.0
elif ((np.all(np.isnan(Rs))) and (meth == "C35" or meth == "C40")): elif (np.all(Rs) == None or np.all(np.isnan(Rs))):
Rs = np.ones(spd.shape)*150 # set to default for COARE3.5 Rs = np.ones(spd.shape)*150 # set to default for COARE3.5
if ((np.all(np.isnan(zi))) and (meth == "C30" or meth == "C35" or if ((gust == None) and (meth == "C30" or meth == "C35" or meth == "C40")):
meth == "C40")): gust = [1, 1.2, 600]
zi = 600 # set to default for COARE3.5 elif ((gust == None) and (meth == "UA" or meth == "ERA5")):
elif ((np.all(np.isnan(zi))) and (meth == "ERA5" or meth == "UA")): gust = [1, 1, 1000]
zi = 1000 elif (gust == None):
elif (np.all(np.isnan(zi))): gust = [1, 1.2, 800]
zi = 800 if (tol == None):
tol = ['flux', 0.01, 1, 1]
if ((jcool == None) and (meth == "S80" or meth == "S88" or meth == "LP82"
or meth == "YT96")):
jcool = 0
elif ((jcool == None) and (meth == "UA" or meth == "LY04" or meth == "C30"
or meth == "C35" or meth == "C40"
or meth == "ERA5")):
jcool = 1
logging.info('method %s, inputs: h_in: %s | lat: %s | P: %s | Rl: %s |'
' Rs: %s | gust: %s | jcool: %s', meth, h_in[:, 1],
np.nanmedian(lat), np.nanmedian(P), np.nanmedian(Rl),
np.nanmedian(Rs), gust, jcool)
#### ####
th = np.where(T < 200, (np.copy(T)+CtoK) * th = np.where(T < 200, (np.copy(T)+CtoK) *
np.power(1000/P,287.1/1004.67), np.power(1000/P,287.1/1004.67),
...@@ -135,22 +162,32 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -135,22 +162,32 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
Ta = np.where(T < 200, np.copy(T)+CtoK+tlapse*h_in[1], Ta = np.where(T < 200, np.copy(T)+CtoK+tlapse*h_in[1],
np.copy(T)+tlapse*h_in[1]) # convert to Kelvin if needed np.copy(T)+tlapse*h_in[1]) # convert to Kelvin if needed
sst = np.where(SST < 200, np.copy(SST)+CtoK, np.copy(SST)) sst = np.where(SST < 200, np.copy(SST)+CtoK, np.copy(SST))
if qmeth: if (hum == None):
RH = np.ones(spd.shape)*80
qsea = qsat_sea(sst, P, qmeth)/1000 # surface water q (g/kg)
qair = qsat_air(T, P, RH, qmeth)/1000 # q of air (g/kg)
elif (hum[0] == 0):
RH = hum[1]
if (np.all(RH) < 1):
sys.exit("input relative humidity units should be \%")
qsea = qsat_sea(sst, P, qmeth)/1000 # surface water q (g/kg)
qair = qsat_air(T, P, RH, qmeth)/1000 # q of air (g/kg)
elif (hum[0] == 1):
qair = hum[1]
qsea = qsat_sea(sst, P, qmeth)/1000 # surface water q (g/kg) qsea = qsat_sea(sst, P, qmeth)/1000 # surface water q (g/kg)
qair = qsat_air(T, P, RH, qmeth)/1000 # q of air (g/kg) elif (hum[0] == 2):
logging.info('method %s and q method %s | qsea:%s, qair:%s', meth, Td = hum[1] # dew point temperature (K)
qmeth, np.nanmedian(qsea), np.nanmedian(qair)) Td = np.where(Td < 200, np.copy(Td)+CtoK, np.copy(Td))
else: T = np.where(T < 200, np.copy(T)+CtoK, np.copy(T))
qsea = qsat_sea(sst, P, "Buck2")/1000 # surface water q (g/kg) esd = 611.21*np.exp(17.502*((Td-273.16)/(Td-32.19)))
qair = qsat_air(T, P, RH, "Buck2")/1000 # q of air (g/kg) es = 611.21*np.exp(17.502*((T-273.16)/(T-32.19)))
logging.info('method %s | qsea:%s, qair:%s', meth, RH = 100*esd/es
np.nanmedian(qsea[~np.isnan(qsea)]), qair = qsat_air(T, P, RH, qmeth)/1000 # q of air (g/kg)
np.nanmedian(qair[~np.isnan(qair)])) qsea = qsat_sea(sst, P, qmeth)/1000 # surface water q (g/kg)
logging.info('method %s and q method %s | qsea:%s, qair:%s', meth, qmeth,
np.nanmedian(qsea), np.nanmedian(qair))
if (np.all(np.isnan(qsea)) or np.all(np.isnan(qair))): if (np.all(np.isnan(qsea)) or np.all(np.isnan(qair))):
print("qsea and qair cannot be nan") print("qsea and qair cannot be nan")
logging.info('method %s qsea and qair cannot be nan | sst:%s, Ta:%s,'
'P:%s, RH:%s', meth, np.nanmedian(sst), np.nanmedian(Ta),
np.nanmedian(P), np.nanmedian(RH))
# first guesses # first guesses
dt = Ta - sst dt = Ta - sst
dq = qair - qsea dq = qair - qsea
...@@ -178,12 +215,12 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -178,12 +215,12 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
Rnl = 0.97*(5.67e-8*np.power(sst-0.3*jcool+CtoK, 4)-Rl) Rnl = 0.97*(5.67e-8*np.power(sst-0.3*jcool+CtoK, 4)-Rl)
dter = np.ones(T.shape)*0.3 dter = np.ones(T.shape)*0.3
dqer = dter*0.622*lv*qsea/(287.1*np.power(sst, 2)) dqer = dter*0.622*lv*qsea/(287.1*np.power(sst, 2))
if (gust == 1 and meth == "UA"): if (gust[0] == 1 and meth == "UA"):
wind = np.where(dtv >= 0, np.where(spd > 0.1, spd, 0.1), wind = np.where(dtv >= 0, np.where(spd > 0.1, spd, 0.1),
np.sqrt(np.power(np.copy(spd), 2)+np.power(0.5, 2))) np.sqrt(np.power(np.copy(spd), 2)+np.power(0.5, 2)))
elif (gust == 1): elif (gust[0] == 1):
wind = np.sqrt(np.power(np.copy(spd), 2)+np.power(0.5, 2)) wind = np.sqrt(np.power(np.copy(spd), 2)+np.power(0.5, 2))
elif (gust == 0): elif (gust[0] == 0):
wind = np.copy(spd) wind = np.copy(spd)
if (meth == "UA"): if (meth == "UA"):
...@@ -193,55 +230,57 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -193,55 +230,57 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
usr=kappa*wind/np.log(h_in[0]/zo) usr=kappa*wind/np.log(h_in[0]/zo)
Rb = g*h_in[0]*dtv/(tv*wind**2) Rb = g*h_in[0]*dtv/(tv*wind**2)
zol = np.where(Rb >= 0, Rb*np.log(h_in[0]/zo) / zol = np.where(Rb >= 0, Rb*np.log(h_in[0]/zo) /
(1-5*np.where(Rb < 0.19, Rb, 0.19)), (1-5*np.where(Rb < 0.19, Rb, 0.19)),
Rb*np.log(h_in[0]/zo)) Rb*np.log(h_in[0]/zo))
monob = h_in[0]/zol monob = h_in[0]/zol
zo = 0.013*np.power(usr, 2)/g + 0.11*visc_air(Ta)/usr zo = 0.013*np.power(usr, 2)/g + 0.11*visc_air(Ta)/usr
zot = zo/np.exp(2.67*np.power(usr*zo/visc_air(Ta), 0.25)-2.57) zot = zo/np.exp(2.67*np.power(usr*zo/visc_air(Ta), 0.25)-2.57)
zoq = zot zoq = zot
logging.info('method %s | wind:%s, usr:%s, ' logging.info('method %s | wind:%s, usr:%s, '
'zo:%s, zot:%s, zoq:%s, Rb:%s, monob:%s', meth, 'zo:%s, zot:%s, zoq:%s, Rb:%s, monob:%s', meth,
np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo),
np.nanmedian(zot), np.nanmedian(zoq), np.nanmedian(Rb), np.nanmedian(zot), np.nanmedian(zoq), np.nanmedian(Rb),
np.nanmedian(monob)) np.nanmedian(monob))
elif (meth == "ERA5"): elif (meth == "ERA5"):
usr = np.sqrt(cd*np.power(wind, 2)) usr = np.sqrt(cd*np.power(wind, 2))
Rb = ((g*h_in[0]*((2*dt)/(Ta+sst-g*h_in[0]) + Rb = ((g*h_in[0]*((2*dt)/(Ta+sst-g*h_in[0]) +
0.61*dq))/np.power(wind, 2)) 0.61*dq))/np.power(wind, 2))
zo = 0.11*visc_air(Ta)/usr+0.018*np.power(usr, 2)/g zo = 0.11*visc_air(Ta)/usr+0.018*np.power(usr, 2)/g
zot = 0.40*visc_air(Ta)/usr zot = 0.40*visc_air(Ta)/usr
zoq = 0.62*visc_air(Ta)/usr
zol = (Rb*((np.log((h_in[0]+zo)/zo)-psim_calc((h_in[0]+zo) / zol = (Rb*((np.log((h_in[0]+zo)/zo)-psim_calc((h_in[0]+zo) /
monob, meth)+psim_calc(zo/monob, meth)) / monob, meth)+psim_calc(zo/monob, meth)) /
(np.log((h_in[0]+zo)/zot) - (np.log((h_in[0]+zo)/zot) -
psit_calc((h_in[0]+zo)/monob, meth) + psit_calc((h_in[0]+zo)/monob, meth) +
psit_calc(zot/monob, meth)))) psit_calc(zot/monob, meth))))
monob = h_in[0]/zol monob = h_in[0]/zol
logging.info('method %s | wind:%s, usr:%s, ' logging.info('method %s | wind:%s, usr:%s, '
'zo:%s, zot:%s, Rb:%s, monob:%s', meth, 'zo:%s, zot:%s, Rb:%s, monob:%s', meth,
np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo),
np.nanmedian(zot), np.nanmedian(Rb), np.nanmedian(monob)) np.nanmedian(zot), np.nanmedian(Rb), np.nanmedian(monob))
elif (meth == "C30" or meth == "C35" or meth == "C40"): elif (meth == "C30" or meth == "C35" or meth == "C40"):
usr = np.sqrt(cd*np.power(wind, 2)) usr = np.sqrt(cd*np.power(wind, 2))
a = 0.011*np.ones(T.shape) a = 0.011*np.ones(T.shape)
a = np.where(wind > 10, 0.011+(wind-10)/(18-10)*(0.018-0.011), a = np.where(wind > 10, 0.011+(wind-10)/(18-10)*(0.018-0.011),
np.where(wind > 18, 0.018, a)) np.where(wind > 18, 0.018, a))
zo = a*np.power(usr, 2)/g+0.11*visc_air(T)/usr zo = a*np.power(usr, 2)/g+0.11*visc_air(T)/usr
rr = zo*usr/visc_air(T) rr = zo*usr/visc_air(T)
zoq = np.minimum(5e-5/np.power(rr, 0.6), 1.15e-4) zoq = np.minimum(5e-5/np.power(rr, 0.6), 1.15e-4)
zot = zoq zot = np.copy(zoq)
Rb = g*h_in[0]*dtv/((T+CtoK)*np.power(wind, 2)) Rb = g*h_in[0]*dtv/((T+CtoK)*np.power(wind, 2))
zol = (Rb*((np.log((h_in[0]+zo)/zo)-psim_calc((h_in[0]+zo) / zol = (Rb*((np.log((h_in[0]+zo)/zo)-psim_calc((h_in[0]+zo) /
monob, meth)+psim_calc(zo/monob, meth)) / monob, meth)+psim_calc(zo/monob, meth)) /
(np.log((h_in[0]+zo)/zot) - (np.log((h_in[0]+zo)/zot) -
psit_calc((h_in[0]+zo)/monob, meth) + psit_calc((h_in[0]+zo)/monob, meth) +
psit_calc(zot/monob, meth)))) psit_calc(zot/monob, meth))))
monob = h_in[0]/zol monob = h_in[0]/zol
logging.info('method %s | wind:%s, usr:%s, ' logging.info('method %s | wind:%s, usr:%s, '
'zo:%s, zot:%s, Rb:%s, monob:%s', meth, 'zo:%s, zot:%s, Rb:%s, monob:%s', meth,
np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo),
np.nanmedian(zot), np.nanmedian(Rb), np.nanmedian(monob)) np.nanmedian(zot), np.nanmedian(Rb), np.nanmedian(monob))
else: else:
zo, zot = 0.0001*np.ones(spd.shape), 0.0001*np.ones(spd.shape) zo = 0.0001*np.ones(spd.shape)
zot, zoq = 0.0001*np.ones(spd.shape), 0.0001*np.ones(spd.shape)
usr = np.sqrt(cd*np.power(wind, 2)) usr = np.sqrt(cd*np.power(wind, 2))
logging.info('method %s | wind:%s, usr:%s, ' logging.info('method %s | wind:%s, usr:%s, '
'zo:%s, zot:%s, monob:%s', meth, 'zo:%s, zot:%s, monob:%s', meth,
...@@ -249,18 +288,24 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -249,18 +288,24 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
np.nanmedian(zot), np.nanmedian(monob)) np.nanmedian(zot), np.nanmedian(monob))
tsr = (dt+dter*jcool)*kappa/(np.log(hin[1]/zot) - tsr = (dt+dter*jcool)*kappa/(np.log(hin[1]/zot) -
psit_calc(h_in[1]/monob, meth)) psit_calc(h_in[1]/monob, meth))
qsr = (dq+dqer*jcool)*kappa/(np.log(hin[2]/zot) - qsr = (dq+dqer*jcool)*kappa/(np.log(hin[2]/zoq) -
psit_calc(h_in[2]/monob, meth)) psit_calc(h_in[2]/monob, meth))
# tolerance for u,t,q,usr,tsr,qsr
tol = np.array([0.01, 0.01, 5e-05, 0.005, 0.001, 5e-07])
it, ind = 0, np.where(spd > 0) it, ind = 0, np.where(spd > 0)
ii, itera = True, np.zeros(spd.shape)*np.nan ii, itera = True, np.zeros(spd.shape)*np.nan
tau = 0.05*np.ones(spd.shape)
sensible = 5*np.ones(spd.shape)
latent = 65*np.ones(spd.shape)
while np.any(ii): while np.any(ii):
it += 1 it += 1
if it > n: if it > n:
break break
old = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n), if (tol[0] == 'flux'):
np.copy(usr), np.copy(tsr), np.copy(qsr)]) old = np.array([np.copy(tau), np.copy(sensible), np.copy(latent)])
elif (tol[0] == 'ref'):
old = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n)])
elif (tol[0] == 'all'):
old = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n),
np.copy(tau), np.copy(sensible), np.copy(latent)])
cdn[ind] = cdn_calc(u10n[ind], Ta[ind], None, lat[ind], meth) cdn[ind] = cdn_calc(u10n[ind], Ta[ind], None, lat[ind], meth)
if (np.all(np.isnan(cdn))): if (np.all(np.isnan(cdn))):
break break
...@@ -270,11 +315,15 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -270,11 +315,15 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
cd[ind] = cd_calc(cdn[ind], h_in[0, ind], ref_ht, psim[ind]) cd[ind] = cd_calc(cdn[ind], h_in[0, ind], ref_ht, psim[ind])
ctn[ind], cqn[ind] = ctcqn_calc(h_in[1, ind]/monob[ind], cdn[ind], ctn[ind], cqn[ind] = ctcqn_calc(h_in[1, ind]/monob[ind], cdn[ind],
u10n[ind], zo[ind], Ta[ind], meth) u10n[ind], zo[ind], Ta[ind], meth)
zot[ind] = ref_ht/(np.exp(np.power(kappa, 2) /
(ctn[ind]*np.log(ref_ht/zo[ind]))))
zoq[ind] = ref_ht/(np.exp(np.power(kappa, 2) /
(cqn[ind]*np.log(ref_ht/zo[ind]))))
psit[ind] = psit_calc(h_in[1, ind]/monob[ind], meth) psit[ind] = psit_calc(h_in[1, ind]/monob[ind], meth)
psiq[ind] = psit_calc(h_in[2, ind]/monob[ind], meth) psiq[ind] = psit_calc(h_in[2, ind]/monob[ind], meth)
ct[ind], cq[ind] = ctcq_calc(cdn[ind], cd[ind], ctn[ind], cqn[ind], ct[ind], cq[ind] = ctcq_calc(cdn[ind], cd[ind], ctn[ind], cqn[ind],
h_in[1, ind], h_in[2, ind], ref_ht, h_in[1, ind], h_in[2, ind], ref_ht,
psit[ind], psiq[ind]) psit[ind], psiq[ind])
if (meth == "UA"): if (meth == "UA"):
usr[ind] = np.where(h_in[0, ind]/monob[ind] < -1.574, usr[ind] = np.where(h_in[0, ind]/monob[ind] < -1.574,
kappa*wind[ind] / kappa*wind[ind] /
...@@ -351,10 +400,10 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -351,10 +400,10 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
usr[ind] = (wind[ind]*kappa/(np.log(h_in[0, ind]/zo[ind]) - usr[ind] = (wind[ind]*kappa/(np.log(h_in[0, ind]/zo[ind]) -
psiu_26(h_in[0, ind]/monob[ind], meth))) psiu_26(h_in[0, ind]/monob[ind], meth)))
logging.info('method %s | dter = %s | Rnl = %s ' logging.info('method %s | dter = %s | Rnl = %s '
'| usr = %s | tsr = %s | qsr = %s', meth, '| usr = %s | tsr = %s | qsr = %s', meth,
np.nanmedian(dter), np.nanmedian(Rnl), np.nanmedian(dter), np.nanmedian(Rnl),
np.nanmedian(usr), np.nanmedian(tsr), np.nanmedian(usr), np.nanmedian(tsr),
np.nanmedian(qsr)) np.nanmedian(qsr))
qsr[ind] = ((dq[ind]+dqer[ind]*jcool)*(kappa/(np.log(hin[2, ind] / qsr[ind] = ((dq[ind]+dqer[ind]*jcool)*(kappa/(np.log(hin[2, ind] /
zoq[ind])-psit_26(hin[2, ind]/monob[ind])))) zoq[ind])-psit_26(hin[2, ind]/monob[ind]))))
tsr[ind] = ((dt[ind]+dter[ind]*jcool)*(kappa/(np.log(hin[1, ind] / tsr[ind] = ((dt[ind]+dter[ind]*jcool)*(kappa/(np.log(hin[1, ind] /
...@@ -364,19 +413,24 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -364,19 +413,24 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
psim_calc(h_in[0, ind]/monob[ind], meth))) psim_calc(h_in[0, ind]/monob[ind], meth)))
tsr[ind] = ct[ind]*wind[ind]*(dt[ind]+dter[ind]*jcool)/usr[ind] tsr[ind] = ct[ind]*wind[ind]*(dt[ind]+dter[ind]*jcool)/usr[ind]
qsr[ind] = cq[ind]*wind[ind]*(dq[ind]+dqer[ind]*jcool)/usr[ind] qsr[ind] = cq[ind]*wind[ind]*(dq[ind]+dqer[ind]*jcool)/usr[ind]
dter[ind], dqer[ind], tkt[ind] = get_skin(sst[ind], qsea[ind], if (jcool == 1):
rho[ind], Rl[ind], dter[ind], dqer[ind], tkt[ind] = get_skin(sst[ind], qsea[ind],
Rs[ind], Rnl[ind], rho[ind], Rl[ind],
cp[ind], lv[ind], Rs[ind], Rnl[ind],
np.copy(tkt[ind]), cp[ind], lv[ind],
usr[ind], tsr[ind], np.copy(tkt[ind]),
qsr[ind], lat[ind]) usr[ind], tsr[ind],
qsr[ind], lat[ind])
else:
dter[ind] = np.zeros(sst[ind].shape)
dqer[ind] = np.zeros(sst[ind].shape)
tkt[ind] = np.zeros(sst[ind].shape)
Rnl[ind] = 0.97*(5.67e-8*np.power(SST[ind] - Rnl[ind] = 0.97*(5.67e-8*np.power(SST[ind] -
dter[ind]*jcool+CtoK, 4)-Rl[ind]) dter[ind]*jcool+CtoK, 4)-Rl[ind])
t10n[ind] = (Ta[ind] + t10n[ind] = (Ta[ind] -
(tsr[ind]*(np.log(h_in[1, ind]/ref_ht)-psit[ind])/kappa)) tsr[ind]/kappa*(np.log(h_in[1, ind]/ref_ht)-psit[ind]))
q10n[ind] = (qair[ind] + q10n[ind] = (qair[ind] -
(qsr[ind]*(np.log(h_in[2, ind]/ref_ht)-psiq[ind])/kappa)) qsr[ind]/kappa*(np.log(h_in[2, ind]/ref_ht)-psiq[ind]))
tv10n[ind] = t10n[ind]*(1+0.61*q10n[ind]) tv10n[ind] = t10n[ind]*(1+0.61*q10n[ind])
if (meth == "UA"): if (meth == "UA"):
tsrv[ind] = tsr[ind]*(1.+0.61*qair[ind])+0.61*th[ind]*qsr[ind] tsrv[ind] = tsr[ind]*(1.+0.61*qair[ind])+0.61*th[ind]*qsr[ind]
...@@ -389,11 +443,12 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -389,11 +443,12 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
elif (meth == "ERA5"): elif (meth == "ERA5"):
tsrv[ind] = tsr[ind]+0.61*t10n[ind]*qsr[ind] tsrv[ind] = tsr[ind]+0.61*t10n[ind]*qsr[ind]
Rb[ind] = ((g[ind]*h_in[0, ind]*((2*dt[ind])/(Ta[ind] + Rb[ind] = ((g[ind]*h_in[0, ind]*((2*dt[ind])/(Ta[ind] +
sst[ind]-g[ind]*h_in[0, ind])+0.61*dq[ind])) / sst[ind]-g[ind]*h_in[0, ind])+0.61*dq[ind])) /
np.power(wind[ind], 2)) np.power(wind[ind], 2))
zo[ind] = (0.11*visc_air(Ta[ind])/usr[ind]+0.018 * zo[ind] = (0.11*visc_air(Ta[ind])/usr[ind]+0.018 *
np.power(usr[ind], 2)/g[ind]) np.power(usr[ind], 2)/g[ind])
zot[ind] = 0.40*visc_air(Ta[ind])/usr[ind] zot[ind] = 0.40*visc_air(Ta[ind])/usr[ind]
zoq[ind] = 0.62*visc_air(Ta[ind])/usr[ind]
zol[ind] = (Rb[ind]*((np.log((h_in[0, ind]+zo[ind])/zo[ind]) - zol[ind] = (Rb[ind]*((np.log((h_in[0, ind]+zo[ind])/zo[ind]) -
psim_calc((h_in[0, ind]+zo[ind])/monob[ind], meth) + psim_calc((h_in[0, ind]+zo[ind])/monob[ind], meth) +
psim_calc(zo[ind]/monob[ind], meth)) / psim_calc(zo[ind]/monob[ind], meth)) /
...@@ -407,49 +462,52 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -407,49 +462,52 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
psim[ind] = psim_calc(h_in[0, ind]/monob[ind], meth) psim[ind] = psim_calc(h_in[0, ind]/monob[ind], meth)
psit[ind] = psit_calc(h_in[1, ind]/monob[ind], meth) psit[ind] = psit_calc(h_in[1, ind]/monob[ind], meth)
psiq[ind] = psit_calc(h_in[2, ind]/monob[ind], meth) psiq[ind] = psit_calc(h_in[2, ind]/monob[ind], meth)
if (gust == 1 and meth == "UA"): if (gust[0] == 1 and meth == "UA"):
wind[ind] = np.where(dtv[ind] >= 0, np.where(spd[ind] > 0.1, wind[ind] = np.where(dtv[ind] >= 0, np.where(spd[ind] > 0.1,
spd[ind], 0.1), spd[ind], 0.1),
np.sqrt(np.power(np.copy(spd[ind]), 2) + np.sqrt(np.power(np.copy(spd[ind]), 2) +
np.power(get_gust(1, tv[ind], usr[ind], np.power(get_gust(gust[1], tv[ind], usr[ind],
tsrv[ind], zi, lat[ind]), 2))) tsrv[ind], gust[2], lat[ind]), 2)))
# Zeng et al. 1998 (20) # Zeng et al. 1998 (20)
elif (gust == 1 and (meth == "C30" or meth == "C35" or meth == "C40")): elif (gust[0] == 1 and (meth == "C30" or meth == "C35" or
meth == "C40")):
wind[ind] = np.sqrt(np.power(np.copy(spd[ind]), 2) + wind[ind] = np.sqrt(np.power(np.copy(spd[ind]), 2) +
np.power(get_gust(1.2, Ta[ind], usr[ind], np.power(get_gust(gust[1], Ta[ind], usr[ind],
tsrv[ind], zi, lat[ind]), 2)) tsrv[ind], gust[2], lat[ind]), 2))
elif (gust == 1): elif (gust[0] == 1):
wind[ind] = np.sqrt(np.power(np.copy(spd[ind]), 2) + wind[ind] = np.sqrt(np.power(np.copy(spd[ind]), 2) +
np.power(get_gust(1, Ta[ind], usr[ind], np.power(get_gust(gust[1], Ta[ind], usr[ind],
tsrv[ind], zi, lat[ind]), 2)) tsrv[ind], gust[2], lat[ind]), 2))
elif (gust == 0): elif (gust[0] == 0):
wind[ind] = np.copy(spd[ind]) wind[ind] = np.copy(spd[ind])
if (meth == "UA"): u10n[ind] = wind[ind]-usr[ind]/kappa*(np.log(h_in[0, ind]/10) -
u10n[ind] = (wind[ind]-(usr[ind]/kappa)*(np.log(h_in[0, ind]/10) - psim[ind])
psim[ind])) u10n[u10n < 0] = np.nan
u10n[u10n < 0] = np.nan
elif (meth == "C30" or meth == "C35" or meth == "C40"):
u10n[ind] = ((wind[ind]+usr[ind]/kappa*(np.log(10/h_in[0, ind]) -
psiu_26(10/monob[ind], meth) +
psiu_26(h_in[0, ind]/monob[ind], meth)) +
psiu_26(10/monob[ind], meth)*usr[ind]/kappa) /
(wind[ind]/spd[ind]))
u10n[u10n < 0] = np.nan
elif (meth == "ERA5"):
u10n[ind] = (spd[ind]-(usr[ind]/kappa)*(np.log(h_in[0, ind] /
ref_ht)-psim[ind]))
u10n[u10n < 0] = np.nan
else:
u10n[ind] = (wind[ind]-(usr[ind]/kappa)*(np.log(h_in[0, ind]/10) -
psim[ind]))
u10n[u10n < 0] = np.nan
itera[ind] = np.ones(1)*it itera[ind] = np.ones(1)*it
new = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n), sensible = -rho*cp*usr*tsr
np.copy(usr), np.copy(tsr), np.copy(qsr)]) latent = -rho*lv*usr*qsr
d = np.abs(new-old) if (gust[0] == 1):
ind = np.where((d[0, :] > tol[0])+(d[1, :] > tol[1]) + tau = rho*np.power(usr, 2)*(spd/wind)
(d[2, :] > tol[2])+(d[3, :] > tol[3]) + elif (gust[0] == 0):
(d[4, :] > tol[4])+(d[5, :] > tol[5])) tau = rho*np.power(usr, 2)
if (tol[0] == 'flux'):
new = np.array([np.copy(tau), np.copy(sensible), np.copy(latent)])
elif (tol[0] == 'ref'):
new = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n)])
elif (tol[0] == 'all'):
new = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n),
np.copy(tau), np.copy(sensible), np.copy(latent)])
d = np.fabs(new-old)
if (tol[0] == 'flux'):
ind = np.where((d[0, :] > tol[1])+(d[1, :] > tol[2]) +
(d[2, :] > tol[3]))
elif (tol[0] == 'ref'):
ind = np.where((d[0, :] > tol[1])+(d[1, :] > tol[2]) +
(d[2, :] > tol[3]))
elif (tol[0] == 'all'):
ind = np.where((d[0, :] > tol[1])+(d[1, :] > tol[2]) +
(d[2, :] > tol[3])+(d[3, :] > tol[4]) +
(d[4, :] > tol[5])+(d[5, :] > tol[6]))
if (ind[0].size == 0): if (ind[0].size == 0):
ii = False ii = False
else: else:
...@@ -460,22 +518,16 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -460,22 +518,16 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
# calculate output parameters # calculate output parameters
rho = (0.34838*P)/(tv10n) rho = (0.34838*P)/(tv10n)
t10n = t10n-(273.16+tlapse*ref_ht) t10n = t10n-(273.16+tlapse*ref_ht)
sensible = -rho*cp*usr*tsr # zo = ref_ht/np.exp(kappa/cdn**0.5)
latent = -rho*lv*usr*qsr # zot = ref_ht/(np.exp(kappa**2/(ctn*np.log(ref_ht/zo))))
if (gust == 1): # zoq = ref_ht/(np.exp(kappa**2/(cqn*np.log(ref_ht/zo))))
tau = rho*np.power(usr, 2)*(spd/wind) uref = (spd-usr/kappa*(np.log(h_in[0]/h_out[0])-psim +
elif (gust == 0): psim_calc(h_out[0]/monob, meth)))
tau = rho*np.power(usr, 2) tref = (Ta-tsr/kappa*(np.log(h_in[1]/h_out[1])-psit +
zo = ref_ht/np.exp(kappa/cdn**0.5) psit_calc(h_out[0]/monob, meth)))
zot = ref_ht/(np.exp(kappa**2/(ctn*np.log(ref_ht/zo)))) tref = tref-(273.16+tlapse*h_out[1])
zoq = ref_ht/(np.exp(kappa**2/(cqn*np.log(ref_ht/zo)))) qref = (qair-qsr/kappa*(np.log(h_in[2]/h_out[2]) -
urefs = (spd-(usr/kappa)*(np.log(h_in[0]/h_out[0])-psim + psit+psit_calc(h_out[2]/monob, meth)))
psim_calc(h_out[0]/monob, meth)))
trefs = (Ta-(tsr/kappa)*(np.log(h_in[1]/h_out[1])-psit +
psit_calc(h_out[0]/monob, meth)))
trefs = trefs-(273.16+tlapse*h_out[1])
qrefs = (qair-(qsr/kappa)*(np.log(h_in[2]/h_out[2]) -
psit+psit_calc(h_out[2]/monob, meth)))
res = np.zeros((28, len(spd))) res = np.zeros((28, len(spd)))
res[0][:] = tau res[0][:] = tau
res[1][:] = sensible res[1][:] = sensible
...@@ -501,8 +553,13 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, ...@@ -501,8 +553,13 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool,
res[21][:] = zo res[21][:] = zo
res[22][:] = zot res[22][:] = zot
res[23][:] = zoq res[23][:] = zoq
res[24][:] = urefs res[24][:] = uref
res[25][:] = trefs res[25][:] = tref
res[26][:] = qrefs res[26][:] = qref
res[27][:] = itera res[27][:] = itera
if (out == 0):
res[:, ind] = np.nan
# set missing values where data have non acceptable values
res = np.where(spd < 0, np.nan, res)
return res, ind return res, ind
...@@ -2,11 +2,39 @@ import numpy as np ...@@ -2,11 +2,39 @@ import numpy as np
from VaporPressure import VaporPressure from VaporPressure import VaporPressure
CtoK = 273.16 # 273.15 CtoK = 273.16 # 273.15
""" Conversion factor for (^\circ\,C) to (^\\circ\\,K) """ """ Conversion factor for $^\circ\,$C to K """
kappa = 0.4 # NOTE: 0.41 kappa = 0.4 # NOTE: 0.41
""" von Karman's constant """ """ von Karman's constant """
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def get_stabco(meth="S80"):
""" Gives the coefficients \\alpha, \\beta, \\gamma for stability functions
Parameters
----------
meth : str
Returns
-------
coeffs : float
"""
alpha, beta, gamma = 0, 0, 0
if (meth == "S80" or meth == "S88" or meth == "LY04" or
meth == "UA" or meth == "ERA5" or meth == "C30" or meth == "C35" or
meth == "C40"):
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:
print("unknown method stabco: "+meth)
coeffs = np.zeros(3)
coeffs[0] = alpha
coeffs[1] = beta
coeffs[2] = gamma
return coeffs
# ---------------------------------------------------------------------
def cdn_calc(u10n, Ta, Tp, lat, meth="S80"): def cdn_calc(u10n, Ta, Tp, lat, meth="S80"):
...@@ -20,6 +48,8 @@ def cdn_calc(u10n, Ta, Tp, lat, meth="S80"): ...@@ -20,6 +48,8 @@ def cdn_calc(u10n, Ta, Tp, lat, meth="S80"):
air temperature (K) air temperature (K)
Tp : float Tp : float
wave period wave period
lat : float
latitude
meth : str meth : str
Returns Returns
...@@ -63,6 +93,8 @@ def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"): ...@@ -63,6 +93,8 @@ def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"):
air temperature (K) air temperature (K)
Tp : float Tp : float
wave period wave period
lat : float
latitude
meth : str meth : str
Returns Returns
...@@ -92,9 +124,9 @@ def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"): ...@@ -92,9 +124,9 @@ def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"):
zo = a*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr zo = a*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr
elif (meth == "C35"): elif (meth == "C35"):
a = 0.011*np.ones(Ta.shape) a = 0.011*np.ones(Ta.shape)
# a = np.where(u10n > 19, 0.0017*19-0.0050, # a = np.where(u10n > 19, 0.0017*19-0.0050,
# np.where((u10n > 7) & (u10n <= 18), # np.where((u10n > 7) & (u10n <= 18),
# 0.0017*u10n-0.0050, a)) # 0.0017*u10n-0.0050, a))
a = np.where(u10n > 19, 0.0017*19-0.0050, 0.0017*u10n-0.0050) a = np.where(u10n > 19, 0.0017*19-0.0050, 0.0017*u10n-0.0050)
zo = 0.11*visc_air(Ta)/usr+a*np.power(usr, 2)/g zo = 0.11*visc_air(Ta)/usr+a*np.power(usr, 2)/g
elif (meth == "C40"): elif (meth == "C40"):
...@@ -102,13 +134,36 @@ def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"): ...@@ -102,13 +134,36 @@ def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"):
a = np.where(u10n > 22, 0.0016*22-0.0035, 0.0016*u10n-0.0035) a = np.where(u10n > 22, 0.0016*22-0.0035, 0.0016*u10n-0.0035)
zo = a*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr # surface roughness zo = a*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr # surface roughness
elif (meth == "ERA5"): elif (meth == "ERA5"):
# eq. (3.26) p.40 over sea IFS Documentation cy46r1 # eq. (3.26) p.38 over sea IFS Documentation cy46r1
zo = 0.018*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr zo = 0.018*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr
else: else:
print("unknown method for cdn_from_roughness "+meth) print("unknown method for cdn_from_roughness "+meth)
cdnn = (kappa/np.log(10/zo))**2 cdnn = (kappa/np.log(10/zo))**2
cdn = np.where(np.abs(cdnn-cdn) < tol, cdnn, np.nan) cdn = np.where(np.abs(cdnn-cdn) < tol, cdnn, np.nan)
return cdnn return cdn
# ---------------------------------------------------------------------
def cd_calc(cdn, height, ref_ht, psim):
""" Calculates drag coefficient at reference height
Parameters
----------
cdn : float
neutral drag coefficient
height : float
original sensor height (m)
ref_ht : float
reference height (m)
psim : float
momentum stability function
Returns
-------
cd : float
"""
cd = (cdn/np.power(1+(np.sqrt(cdn)*(np.log(height/ref_ht)-psim))/kappa, 2))
return cd
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
...@@ -120,7 +175,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"): ...@@ -120,7 +175,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"):
zol : float zol : float
height over MO length height over MO length
cdn : float cdn : float
neatral drag coefficient neutral drag coefficient
u10n : float u10n : float
neutral 10m wind speed (m/s) neutral 10m wind speed (m/s)
zo : float zo : float
...@@ -187,7 +242,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"): ...@@ -187,7 +242,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"):
cqn = kappa**2/np.log(10/zo)/np.log(10/zoq) cqn = kappa**2/np.log(10/zo)/np.log(10/zoq)
ctn = kappa**2/np.log(10/zo)/np.log(10/zot) ctn = kappa**2/np.log(10/zo)/np.log(10/zot)
elif (meth == "ERA5"): elif (meth == "ERA5"):
# eq. (3.26) p.40 over sea IFS Documentation cy46r1 # eq. (3.26) p.38 over sea IFS Documentation cy46r1
usr = np.sqrt(cdn*np.power(u10n, 2)) usr = np.sqrt(cdn*np.power(u10n, 2))
zot = 0.40*visc_air(Ta)/usr zot = 0.40*visc_air(Ta)/usr
zoq = 0.62*visc_air(Ta)/usr zoq = 0.62*visc_air(Ta)/usr
...@@ -199,30 +254,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"): ...@@ -199,30 +254,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def cd_calc(cdn, height, ref_ht, psim): def ctcq_calc(cdn, cd, ctn, cqn, ht, hq, ref_ht, psit, psiq):
""" Calculates drag coefficient at reference height
Parameters
----------
cdn : float
neutral drag coefficient
height : float
original sensor height (m)
ref_ht : float
reference height (m)
psim : float
momentum stability function
Returns
-------
cd : float
"""
cd = (cdn/np.power(1+(np.sqrt(cdn)*(np.log(height/ref_ht)-psim))/kappa, 2))
return cd
# ---------------------------------------------------------------------
def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq):
""" Calculates heat and moisture exchange coefficients at reference height """ Calculates heat and moisture exchange coefficients at reference height
Parameters Parameters
...@@ -249,12 +281,14 @@ def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq): ...@@ -249,12 +281,14 @@ def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq):
Returns Returns
------- -------
ct : float ct : float
heat exchange coefficient
cq : float cq : float
moisture exchange coefficient
""" """
ct = (ctn*np.sqrt(cd/cdn) / ct = (ctn*np.sqrt(cd/cdn) /
(1+ctn*((np.log(h_t/ref_ht)-psit)/(kappa*np.sqrt(cdn))))) (1+ctn*((np.log(ht/ref_ht)-psit)/(kappa*np.sqrt(cdn)))))
cq = (cqn*np.sqrt(cd/cdn) / cq = (cqn*np.sqrt(cd/cdn) /
(1+cqn*((np.log(h_q/ref_ht)-psiq)/(kappa*np.sqrt(cdn))))) (1+cqn*((np.log(hq/ref_ht)-psiq)/(kappa*np.sqrt(cdn)))))
return ct, cq return ct, cq
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
...@@ -272,16 +306,13 @@ def psim_calc(zol, meth="S80"): ...@@ -272,16 +306,13 @@ def psim_calc(zol, meth="S80"):
------- -------
psim : float psim : float
""" """
coeffs = get_stabco(meth)
alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
if (meth == "ERA5"): if (meth == "ERA5"):
psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma), psim = psim_era5(zol)
psim_stab_era5(zol, alpha, beta, gamma))
elif (meth == "C30" or meth == "C35" or meth == "C40"): elif (meth == "C30" or meth == "C35" or meth == "C40"):
psim = psiu_26(zol, meth) psim = psiu_26(zol, meth)
else: else:
psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma), psim = np.where(zol < 0, psim_conv(zol, meth),
psim_stab(zol, alpha, beta, gamma)) psim_stab(zol, meth))
return psim return psim
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
...@@ -294,55 +325,25 @@ def psit_calc(zol, meth="S80"): ...@@ -294,55 +325,25 @@ def psit_calc(zol, meth="S80"):
zol : float zol : float
height over MO length height over MO length
meth : str meth : str
parameterisation method
Returns Returns
------- -------
psit : float psit : float
""" """
coeffs = get_stabco(meth)
alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
if (meth == "ERA5"): if (meth == "ERA5"):
psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma), psit = np.where(zol < 0, psi_conv(zol, meth),
psi_stab_era5(zol, alpha, beta, gamma)) psi_era5(zol))
elif (meth == "C30" or meth == "C35" or meth == "C40"): elif (meth == "C30" or meth == "C35" or meth == "C40"):
psit = psit_26(zol) psit = psit_26(zol)
else: else:
psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma), psit = np.where(zol < 0, psi_conv(zol, meth),
psi_stab(zol, alpha, beta, gamma)) psi_stab(zol, meth))
return psit return psit
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def get_stabco(meth="S80"): def psi_era5(zol):
""" Gives the coefficients \\alpha, \\beta, \\gamma for stability functions
Parameters
----------
meth : str
Returns
-------
coeffs : float
"""
if (meth == "S80" or meth == "S88" or meth == "LY04" or
meth == "UA" or meth == "ERA5" or meth == "C30" or meth == "C35" or
meth == "C40"):
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:
print("unknown method stabco: "+meth)
coeffs = np.zeros(3)
coeffs[0] = alpha
coeffs[1] = beta
coeffs[2] = gamma
return coeffs
# ---------------------------------------------------------------------
def psi_stab_era5(zol, alpha, beta, gamma):
""" Calculates heat stability function for stable conditions """ Calculates heat stability function for stable conditions
for method ERA5 for method ERA5
...@@ -350,14 +351,12 @@ def psi_stab_era5(zol, alpha, beta, gamma): ...@@ -350,14 +351,12 @@ def psi_stab_era5(zol, alpha, beta, gamma):
---------- ----------
zol : float zol : float
height over MO length height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns Returns
------- -------
psit : float psit : float
""" """
# eq (3.22) p. 39 IFS Documentation cy46r1 # eq (3.22) p. 37 IFS Documentation cy46r1
a, b, c, d = 1, 2/3, 5, 0.35 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 psit = -b*(zol-c/d)*np.exp(-d*zol)-np.power(1+(2/3)*a*zol, 1.5)-(b*c)/d+1
return psit return psit
...@@ -377,94 +376,103 @@ def psit_26(zol): ...@@ -377,94 +376,103 @@ def psit_26(zol):
psi : float psi : float
""" """
b, d = 2/3, 0.35 b, d = 2/3, 0.35
dzol = np.where(d*zol > 50, 50, d*zol) # stable dzol = np.where(d*zol > 50, 50, d*zol)
psi = -((1+b*zol)**1.5+b*(zol-14.28)*np.exp(-dzol)+8.525) psi = np.where(zol > 0,-(np.power(1+b*zol, 1.5)+b*(zol-14.28) *
psik = 2*np.log((1+np.sqrt(1-15*zol))/2) np.exp(-dzol)+8.525), np.nan)
psic = (1.5*np.log((1+np.power(1-34.15*zol, 1/3) + psik = np.where(zol < 0, 2*np.log((1+np.sqrt(1-15*zol))/2), np.nan)
np.power(1-34.15*zol, 2/3))/3)-np.sqrt(3) * psic = np.where(zol < 0, 1.5*np.log((1+np.power(1-34.15*zol, 1/3) +
np.arctan(1+2*np.power(1-34.15*zol, 1/3))/np.sqrt(3) + np.power(1-34.15*zol, 2/3))/3)-np.sqrt(3) *
4*np.arctan(1)/np.sqrt(3)) np.arctan(1+2*np.power(1-34.15*zol, 1/3))/np.sqrt(3) +
4*np.arctan(1)/np.sqrt(3), np.nan)
f = np.power(zol, 2)/(1+np.power(zol, 2)) f = np.power(zol, 2)/(1+np.power(zol, 2))
psi = np.where(zol < 0, (1-f)*psik+f*psic, psi) psi = np.where(zol < 0, (1-f)*psik+f*psic, psi)
return psi return psi
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psi_conv(zol, alpha, beta, gamma): def psi_conv(zol, meth):
""" Calculates heat stability function for unstable conditions """ Calculates heat stability function for unstable conditions
Parameters Parameters
---------- ----------
zol : float zol : float
height over MO length height over MO length
alpha, beta, gamma : float meth : str
constants given by get_stabco parameterisation method
Returns Returns
------- -------
psit : float psit : float
""" """
coeffs = get_stabco(meth)
alpha, beta = coeffs[0], coeffs[1]
xtmp = np.power(1-alpha*zol, beta) xtmp = np.power(1-alpha*zol, beta)
psit = 2*np.log((1+np.power(xtmp, 2))*0.5) psit = 2*np.log((1+np.power(xtmp, 2))*0.5)
return psit return psit
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psi_stab(zol, alpha, beta, gamma): def psi_stab(zol, meth):
""" Calculates heat stability function for stable conditions """ Calculates heat stability function for stable conditions
Parameters Parameters
---------- ----------
zol : float zol : float
height over MO length height over MO length
alpha, beta, gamma : float meth : str
constants given by get_stabco parameterisation method
Returns Returns
------- -------
psit : float psit : float
""" """
coeffs = get_stabco(meth)
gamma = coeffs[2]
psit = -gamma*zol psit = -gamma*zol
return psit return psit
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psim_stab_era5(zol, alpha, beta, gamma): def psim_era5(zol):
""" Calculates momentum stability function for stable conditions """ Calculates momentum stability function for method ERA5
for method ERA5
Parameters Parameters
---------- ----------
zol : float zol : float
height over MO length height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns Returns
------- -------
psim : float psim : float
""" """
# eq (3.22) p. 39 IFS Documentation cy46r1 # eq (3.20, 3.22) p. 37 IFS Documentation cy46r1
coeffs = get_stabco("ERA5")
alpha, beta = coeffs[0], coeffs[1]
xtmp = np.power(1-alpha*zol, beta)
a, b, c, d = 1, 2/3, 5, 0.35 a, b, c, d = 1, 2/3, 5, 0.35
psim = -b*(zol-c/d)*np.exp(-d*zol)-a*zol-(b*c)/d 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 return psim
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psim_conv(zol, alpha, beta, gamma): def psim_conv(zol, meth):
""" Calculates momentum stability function for unstable conditions """ Calculates momentum stability function for unstable conditions
Parameters Parameters
---------- ----------
zol : float zol : float
height over MO length height over MO length
alpha, beta, gamma : float meth : str
constants given by get_stabco parameterisation method
Returns Returns
------- -------
psim : float psim : float
""" """
coeffs = get_stabco(meth)
alpha, beta = coeffs[0], coeffs[1]
xtmp = np.power(1-alpha*zol, beta) xtmp = np.power(1-alpha*zol, beta)
psim = (2*np.log((1+xtmp)*0.5)+np.log((1+np.power(xtmp, 2))*0.5) - psim = (2*np.log((1+xtmp)*0.5)+np.log((1+np.power(xtmp, 2))*0.5) -
2*np.arctan(xtmp)+np.pi/2) 2*np.arctan(xtmp)+np.pi/2)
...@@ -472,20 +480,22 @@ def psim_conv(zol, alpha, beta, gamma): ...@@ -472,20 +480,22 @@ def psim_conv(zol, alpha, beta, gamma):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psim_stab(zol, alpha, beta, gamma): def psim_stab(zol, meth):
""" Calculates momentum stability function for stable conditions """ Calculates momentum stability function for stable conditions
Parameters Parameters
---------- ----------
zol : float zol : float
height over MO length height over MO length
alpha, beta, gamma : float meth : str
constants given by get_stabco parameterisation method
Returns Returns
------- -------
psim : float psim : float
""" """
coeffs = get_stabco(meth)
gamma = coeffs[2]
psim = -gamma*zol psim = -gamma*zol
return psim return psim
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
...@@ -505,28 +515,31 @@ def psiu_26(zol, meth): ...@@ -505,28 +515,31 @@ def psiu_26(zol, meth):
""" """
if (meth == "C30"): if (meth == "C30"):
dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable
psi = -((1+zol)+0.6667*(zol-14.28)*np.exp(-dzol)+8.525) psi = np.where(zol > 0, -((1+zol)+0.6667*(zol-14.28)*np.exp(-dzol) +
k = np.where(zol < 0) # unstable 8.525), np.nan)
x = np.power(1-15*zol[k], 0.25) x = np.where(zol < 0, np.power(1-15*zol, 0.25), np.nan)
psik = (2*np.log((1+x)/2)+np.log((1+np.power(x, 2))/2)-2*np.arctan(x) + psik = np.where(zol < 0, 2*np.log((1+x)/2)+np.log((1+np.power(x, 2)) /
2*np.arctan(1)) 2)-2*np.arctan(x)+2*np.arctan(1), np.nan)
x = np.power(1-10.15*zol[k], 0.3333) x = np.where(zol < 0, np.power(1-10.15*zol, 0.3333), np.nan)
psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) * psic = np.where(zol < 0, 1.5*np.log((1+x+np.power(x, 2))/3) -
np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3)) np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) +
f = np.power(zol[k], 2)/(1+np.power(zol[k], 2)) 4*np.arctan(1)/np.sqrt(3), np.nan)
psi[k] = (1-f)*psik+f*psic f = np.power(zol, 2)/(1+np.power(zol, 2))
psi = np.where(zol < 0, (1-f)*psik+f*psic, psi)
elif (meth == "C35" or meth == "C40"): elif (meth == "C35" or meth == "C40"):
dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable
a, b, c, d = 0.7, 3/4, 5, 0.35 a, b, c, d = 0.7, 3/4, 5, 0.35
psi = -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d) psi = np.where(zol > 0, -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d),
k = np.where(zol < 0) # unstable np.nan)
x = np.power(1-15*zol[k], 0.25) x = np.where(zol < 0, np.power(1-15*zol, 0.25), np.nan)
psik = 2*np.log((1+x)/2)+np.log((1+x**2)/2)-2*np.arctan(x)+2*np.arctan(1) psik = np.where(zol < 0, 2*np.log((1+x)/2)+np.log((1+x**2)/2) -
x = np.power(1-10.15*zol[k], 0.3333) 2*np.arctan(x)+2*np.arctan(1), np.nan)
psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) * x = np.where(zol < 0, np.power(1-10.15*zol, 0.3333), np.nan)
np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3)) psic = np.where(zol < 0, 1.5*np.log((1+x+np.power(x, 2))/3) -
f = np.power(zol[k], 2)/(1+np.power(zol[k], 2)) np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) +
psi[k] = (1-f)*psik+f*psic 4*np.arctan(1)/np.sqrt(3), np.nan)
f = np.power(zol, 2)/(1+np.power(zol, 2))
psi = np.where(zol < 0, (1-f)*psik+f*psic, psi)
return psi return psi
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
...@@ -638,6 +651,8 @@ def get_heights(h, dim_len): ...@@ -638,6 +651,8 @@ def get_heights(h, dim_len):
---------- ----------
h : float h : float
input heights (m) input heights (m)
dim_len : int
length dimension
Returns Returns
------- -------
...@@ -646,17 +661,17 @@ def get_heights(h, dim_len): ...@@ -646,17 +661,17 @@ def get_heights(h, dim_len):
hh = np.zeros((3, dim_len)) hh = np.zeros((3, dim_len))
if (type(h) == float or type(h) == int): if (type(h) == float or type(h) == int):
hh[0, :], hh[1, :], hh[2, :] = h, h, h hh[0, :], hh[1, :], hh[2, :] = h, h, h
elif (len(h) == 2 and h.ndim == 1): elif (len(h) == 2 and np.ndim(h) == 1):
hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[1] hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[1]
elif (len(h) == 3 and h.ndim == 1): elif (len(h) == 3 and np.ndim(h) == 1):
hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[2] hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[2]
elif (len(h) == 1 and h.ndim == 2): elif (len(h) == 1 and np.ndim(h) == 2):
hh = np.zeros((3, h.shape[1])) hh = np.zeros((3, h.shape[1]))
hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[0, :], h[0, :] hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[0, :], h[0, :]
elif (len(h) == 2 and h.ndim == 2): elif (len(h) == 2 and np.ndim(h) == 2):
hh = np.zeros((3, h.shape[1])) hh = np.zeros((3, h.shape[1]))
hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[1, :], h[1, :] hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[1, :], h[1, :]
elif (len(h) == 3 and h.ndim == 2): elif (len(h) == 3 and np.ndim(h) == 2):
hh = np.zeros((3, h.shape[1])) hh = np.zeros((3, h.shape[1]))
hh = np.copy(h) hh = np.copy(h)
return hh return hh
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment