Commit 22be86dd authored by sbiri's avatar sbiri
Browse files

UA and ERA5 algorithms added

parent 9b617551
import numpy as np import numpy as np
import sys import sys
import logging import logging
from flux_subs import get_heights,cdn_calc, cd_calc, get_skin, psim_calc, \ from flux_subs import (kappa, CtoK, get_heights, cdn_calc, cd_calc, get_skin,
psit_calc, ctcq_calc,ctcqn_calc, get_gust, gc,q_calc,qsea_calc,qsat26sea, qsat26air, \ psim_calc, psit_calc, ctcq_calc, ctcqn_calc, get_gust,
visc_air, psit_26,psiu_26, psiu_40,cd_C35 gc, q_calc, qsea_calc, qsat26sea, qsat26air,
visc_air, psit_26, psiu_26, psiu_40, cd_C35,
charnock_C35)
def flux_calc_v3(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi = 600, \
Rl=None, Rs=None, jcool=1, method="Smith80",n=20): def flux_calc_v3_1(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi=600,
""" Rl=None, Rs=None, jcool=1, meth="S88", n=10):
inputs: """ Calculates momentum and heat fluxes using different parameterizations
flux method ("Smith80","Smith88","LP82","HEXOS","HEXOSwave","YT96","COARE3.0","COARE3.5","LY04")
spd : relative wind speed in m/s (is assumed as magnitude difference between wind Parameters
and surface current vectors) ----------
T : air temperature in K (will convert if < 200) meth : str
SST : sea surface temperature in K (will convert if < 200) "S80","S88","LP82","YT96","UA","HEXOS","HEXOSwave",
lat : latitude "LY04","C30","C35","ERA5"
rh : relative humidity in %, default 80% for C35 spd : float
P : air pressure, default 1013 for C35 relative wind speed in m/s (is assumed as magnitude difference
hin : sensor heights in m (array of 1->3 values: 1 -> u=t=q; 2 -> u,t=q; 3 -> u,t,q ) default 10m between wind and surface current vectors)
hout: default heights are 10m T : float
wcp : phase speed of dominant waves (m/s) called in COARE3.5 air temperature in K (will convert if < 200)
sigH: significant wave height (m) called in COARE3.5 SST : float
zi : PBL height (m) called in COARE3.5 sea surface temperature in K (will convert if < 200)
Rl : downward longwave radiation (W/m^2) lat : float
Rs : downward shortwave radiation (W/m^2) latitude (deg)
jcool: 0 if sst is true ocean skin temperature called in COARE3.5 RH : float
n : number of iterations, for COARE3.5 set to 10 relative humidity in %
outputs: P : float
res : which contains 1. tau 2. sensible heat 3. latent heat 4. Monin-Obhukov length (monob) air pressure
5. drag coefficient (cd) 6. neutral drag coefficient (cdn) 7. ct 8. ctn 9. cq 10. cqn hin : float
11.tsrv 12. tsr 13. qsr 14. usr 15. psim 16. psit 17. u10n 18. t10n 19. tv10n sensor heights in m (array of 1->3 values: 1 -> u=t=q; /
20. q10n 21. zo 22. zot 23. zoq 24. urefs 25.trefs 26. qrefs 27. iterations 2 -> u,t=q; 3 -> u,t,q ) default 10m
ind : the indices in the matrix for the points that did not converge after the maximum number of iterations hout : float
based on bform.f and flux_calc.R modified and ported to python by S. Biri output height, default is 10m
wcp : float
phase speed of dominant waves (m/s) called in C35
sigH : float
significant wave height (m) called in C35
zi : int
PBL height (m) called in C35
Rl : float
downward longwave radiation (W/m^2)
Rs : float
downward shortwave radiation (W/m^2)
jcool : bool
0 if sst is true ocean skin temperature called in C35
n : int
number of iterations, for C35 set to 10
out : str
format in which to save output ('output.nc' or None)
Returns
-------
res : array that contains
1. momentum flux (W/m^2)
2. sensible heat (W/m^2)
3. latent heat (W/m^2)
4. Monin-Obhukov length (mb)
5. drag coefficient (cd)
6. neutral drag coefficient (cdn)
7. heat exhange coefficient (ct)
8. neutral heat exhange coefficient (ctn)
9. moisture exhange coefficient (cq)
10. neutral moisture exhange coefficient (cqn)
11. star virtual temperature (tsrv)
12. star temperature (tsr)
13. star humidity (qsr)
14. star velocity (usr)
15. momentum stability function (psim)
16. heat stability funciton (psit)
17. 10m neutral velocity (u10n)
18. 10m neutral temperature (t10n)
19. 10m neutral virtual temperature (tv10n)
20. 10m neutral specific humidity (q10n)
21. surface roughness length (zo)
22. heat roughness length (zot)
23. moisture roughness length (zoq)
24. velocity at reference height (urefs)
25. temperature at reference height (trefs)
26. specific humidity at reference height (qrefs)
27. number of iterations until convergence
ind : int
the indices in the matrix for the points that did not converge
after the maximum number of iterations
The code is based on bform.f and flux_calc.R modified by S. Biri
""" """
logging.basicConfig(filename='flux_calc.log', level=logging.INFO) logging.basicConfig(filename='flux_calc.log',
kappa,CtoK = 0.4, 273.16 # von Karman's constant; conversion factor from degC to K format='%(asctime)s %(message)s',level=logging.INFO)
ref_ht, tlapse = 10, 0.0098 # reference height, lapse rate ref_ht, tlapse = 10, 0.0098 # reference height, lapse rate
hh_in = get_heights(hin) # heights of input measurements/fields hh_in = get_heights(hin) # heights of input measurements/fields
hh_out = get_heights(hout) # desired height of output variables,default is 10 hh_out = get_heights(hout) # desired height of output variables
g=gc(lat,None) # acceleration due to gravity if np.all(np.isnan(lat)): # set latitude to 45deg if empty
ctn, ct, cqn, cq= np.zeros(spd.shape)*np.nan,np.zeros(spd.shape)*np.nan,np.zeros(spd.shape)*np.nan,np.zeros(spd.shape)*np.nan lat=45*np.ones(np.shape(spd))
g = gc(lat, None) # acceleration due to gravity
ctn, ct, cqn, cq = (np.zeros(spd.shape)*np.nan, np.zeros(spd.shape)*np.nan,
np.zeros(spd.shape)*np.nan, np.zeros(spd.shape)*np.nan)
# 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 input is nan')
if (np.all(np.isnan(RH)) or np.all(np.isnan(P))): if (np.all(np.isnan(RH)) and meth == "C35"):
if (method=="COARE3.5"): RH = np.ones(np.shape(spd))*80 # if empty set to default for COARE3.5
RH=np.ones(np.shape(spd))*80 else:
P=np.ones(np.shape(spd))*1013 sys.exit("input RH is empty")
else: logging.debug('input RH is empty')
sys.exit("input RH or P is empty") if (np.all(np.isnan(P)) and meth == "C35"):
if (np.all(spd[~np.isnan(spd)]== 0) and method != "COARE3.0"): P = np.ones(np.shape(spd))*1013 # if empty set to default for COARE3.5
sys.exit("wind cannot be zero when method other than COARE3.0") else:
logging.debug('all velocity input is zero') sys.exit("input P is empty")
if (np.all(np.isnan(Rl)) and method=="COARE3.5"): logging.debug('input P is empty')
Rl=np.ones(np.shape(spd))*370 # set to default if (np.all(np.isnan(Rl)) and meth == "C35"):
if (np.all(np.isnan(Rs)) and method=="COARE3.5"): Rl = np.ones(np.shape(spd))*370 # set to default for COARE3.5
Rs=np.ones(np.shape(spd))*150 # set to default if (np.all(np.isnan(Rs)) and meth == "C35"):
if (np.all(np.isnan(zi)) and method=="COARE3.5"): Rs = np.ones(np.shape(spd))*150 # set to default for COARE3.5
zi=600 # set to default if (np.all(np.isnan(zi)) and meth == "C35"):
### additional parameters for COARE3.5 zi = 600 # set to default for COARE3.5
waveage,seastate=1,1 elif ((np.all(np.isnan(zi)) and meth == "ERA5") or
(np.all(np.isnan(zi)) and meth == "UA")):
zi = 1000
# additional parameters for C35
waveage, seastate = 1, 1
if np.isnan(wcp[0]): if np.isnan(wcp[0]):
wcp=np.ones(np.shape(spd))*np.nan wcp = np.ones(np.shape(spd))*np.nan
waveage=0 waveage = 0
if np.isnan(sigH[0]): if np.isnan(sigH[0]):
sigH=np.ones(np.shape(spd))*np.nan sigH = np.ones(np.shape(spd))*np.nan
seastate=0 seastate = 0
if waveage and seastate: if waveage and seastate:
print('Using seastate dependent parameterization') print('Using seastate dependent parameterization')
logging.info('Using seastate dependent parameterization') logging.info('Using seastate dependent parameterization')
...@@ -75,190 +135,298 @@ def flux_calc_v3(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi = 600, \ ...@@ -75,190 +135,298 @@ def flux_calc_v3(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi = 600, \
print('Using waveage dependent parameterization') print('Using waveage dependent parameterization')
logging.info('Using waveage dependent parameterization') logging.info('Using waveage dependent parameterization')
#### ####
Ta = np.where(np.nanmax(T)<200,np.copy(T)+CtoK+tlapse*hh_in[1],np.copy(T)+tlapse*hh_in[1]) # convert to Kelvin if needed Ta = np.where(np.nanmax(T) < 200, np.copy(T)+CtoK+tlapse*hh_in[1],
sst = np.where(np.nanmax(SST)<200,np.copy(SST)+CtoK,np.copy(SST)) np.copy(T)+tlapse*hh_in[1]) # convert to Kelvin if needed
if (method == "COARE3.5"): sst = np.where(np.nanmax(SST) < 200, np.copy(SST)+CtoK, np.copy(SST))
qsea = qsat26sea(sst,P)/1000 # surface water specific humidity (g/kg) if (meth == "C35"):
Q,_ = qsat26air(T,P,RH) # specific humidity of air (g/kg) qsea = qsat26sea(sst, P)/1000 # surface water specific humidity (g/kg)
qair=Q/1000; del Q Q, _ = qsat26air(T, P, RH) # specific humidity of air (g/kg)
logging.info('method %s | qsea:%s, qair:%s',method,np.ma.median(qsea),np.ma.median(qair)) qair = Q/1000
del Q
logging.info('method %s | qsea:%s, qair:%s', meth,
np.ma.median(qsea[~np.isnan(qsea)]),
np.ma.median(qair[~np.isnan(qair)]))
else: else:
qsea = qsea_calc(sst,P) qsea = qsea_calc(sst, P)
qair = q_calc(Ta,RH,P) qair = q_calc(Ta, RH, P)
logging.info('method %s | qsea:%s, qair:%s',method,np.ma.median(qsea),np.ma.median(qair)) logging.info('method %s | qsea:%s, qair:%s', meth,
np.ma.median(qsea[~np.isnan(qsea)]),
np.ma.median(qair[~np.isnan(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.debug('method %s qsea and qair cannot be nan | sst:%s, Ta:%s, P:%s, RH:%s',method,np.ma.median(sst),np.ma.median(Ta),np.ma.median(P),np.ma.median(RH)) logging.debug('method %s qsea and qair cannot be nan | sst:%s, Ta:%s,'
'P:%s, RH:%s', meth, np.ma.median(sst[~np.isnan(sst)]),
np.ma.median(Ta[~np.isnan(Ta)]),
np.ma.median(P[~np.isnan(P)]),
np.ma.median(RH[~np.isnan(RH)]))
# first guesses # first guesses
inan=np.where(np.isnan(spd+T+SST+lat+RH+P)) inan = np.where(np.isnan(spd+T+SST+lat+RH+P))
t10n, q10n = np.copy(Ta), np.copy(qair) t10n, q10n = np.copy(Ta), np.copy(qair)
tv10n = t10n*(1 + 0.61*q10n) tv10n = t10n*(1 + 0.61*q10n)
rho = (0.34838*P)/(tv10n) rho = (0.34838*P)/(tv10n)
rhoa = P*100/(287.1*(T+CtoK)*(1+0.61*qair)) # difference with rho is that it uses T instead of Ta (which includes lapse rate) rhoa = P*100/(287.1*(T+CtoK)*(1+0.61*qair))
lv = (2.501-0.00237*(SST))*1e6 lv = (2.501-0.00237*SST)*1e6
dt=sst-Ta dt = sst - Ta
dq=qsea-qair dq = qsea - qair
if (method == "COARE3.5"): if (meth == "C35"):
cp=1004.67 #cpa = 1004.67, cpv = cpa*(1+0.84*Q) cp = 1004.67 # cpa = 1004.67, cpv = cpa*(1+0.84*Q)
wetc = 0.622*lv*qsea/(287.1*sst**2) wetc = 0.622*lv*qsea/(287.1*sst**2)
ug=0.5*np.ones(np.shape(spd)) ug = 0.5*np.ones(np.shape(spd))
wind=np.sqrt(np.copy(spd)**2+ug**2) wind = np.sqrt(np.copy(spd)**2+ug**2)
dter=0.3*np.ones(np.shape(spd)) #cool skin temperature depression dter = 0.3*np.ones(np.shape(spd)) # cool skin temperature depression
dqer=wetc*dter dqer = wetc*dter
Rnl=0.97*(5.67e-8*(SST-dter*jcool+CtoK)**4-Rl) Rnl = 0.97*(5.67e-8*(SST-dter*jcool+CtoK)**4-Rl)
u10=wind*np.log(10/1e-4)/np.log(hh_in[0]/1e-4) u10 = wind*np.log(10/1e-4)/np.log(hh_in[0]/1e-4)
usr = 0.035*u10 usr = 0.035*u10
u10n=np.copy(u10) u10n = np.copy(u10)
zo = 0.011*usr**2/g + 0.11*visc_air(T)/usr zo = 0.011*usr**2/g + 0.11*visc_air(T)/usr
cdn = (kappa/np.log(10/zo))**2 cdn = (kappa/np.log(10/zo))**2
cqn = 0.00115*np.ones(np.shape(spd)) cqn = 0.00115*np.ones(np.shape(spd))
ctn = cqn/np.sqrt(cdn) ctn = cqn/np.sqrt(cdn)
cd = (kappa/np.log(hh_in[0]/zo))**2 cd = (kappa/np.log(hh_in[0]/zo))**2
ct = kappa/np.log(hh_in[1]/10/np.exp(kappa/ctn)) ct = kappa/np.log(hh_in[1]/10/np.exp(kappa/ctn))
CC = kappa*ct/cd CC = kappa*ct/cd
Ribcu = -hh_in[0]/zi/0.004/1.2**3 Ribcu = -hh_in[0]/zi/0.004/1.2**3
Ribu = -g*hh_in[0]/(T+CtoK)*((dt-dter*jcool)+0.61*(T+CtoK)*(dq))/wind**2 Ribu = (-g*hh_in[0]/(T+CtoK)*((dt-dter*jcool)+0.61*(T+CtoK)*(dq)) /
zetu=np.where(Ribu<0,CC*Ribu/(1+Ribu/Ribcu),CC*Ribu*(1+27/9*Ribu/CC)) wind**2)
k50=np.where(zetu>50) # stable with very thin M-O length relative to zu zetu = np.where(Ribu < 0, CC*Ribu/(1+Ribu/Ribcu),
CC*Ribu*(1+27/9*Ribu/CC))
k50 = np.where(zetu > 50) # stable, very thin M-O relative to zu
monob = hh_in[0]/zetu monob = hh_in[0]/zetu
gf=wind/spd gf = wind/spd
usr = wind*kappa/(np.log(hh_in[0]/zo)-psiu_40(hh_in[0]/monob)) usr = wind*kappa/(np.log(hh_in[0]/zo)-psiu_40(hh_in[0]/monob))
tsr = -(dt-dter*jcool)*kappa/(np.log(hh_in[1]/10/np.exp(kappa/ctn))-psit_26(hh_in[1]/monob)) tsr = (-(dt-dter*jcool)*kappa/(np.log(hh_in[1]/10/np.exp(kappa/ctn)) -
qsr = -(dq-wetc*dter*jcool)*kappa/(np.log(hh_in[2]/10/np.exp(kappa/ctn))-psit_26(hh_in[2]/monob)) psit_26(hh_in[1]/monob)))
tsrv=tsr+0.61*(T+CtoK)*qsr qsr = (-(dq-wetc*dter*jcool)*kappa/(np.log(hh_in[2]/10 /
ac = np.zeros((len(spd),3)) np.exp(kappa/ctn))-psit_26(hh_in[2]/monob)))
tsrv = tsr+0.61*(T+CtoK)*qsr
ac = np.zeros((len(spd), 3))
charn = 0.011*np.ones(np.shape(spd)) charn = 0.011*np.ones(np.shape(spd))
charn =np.where(wind>10,0.011+(wind-10)/(18-10)*(0.018-0.011),np.where(wind>18,0.018,0.011)) charn = np.where(wind > 10, 0.011+(wind-10)/(18-10)*(0.018-0.011),
ac[:,0]=charn np.where(wind > 18, 0.018, 0.011))
ac[:, 0] = charn
else: else:
cp = 1004.67 * (1 + 0.00084*qsea) cp = 1004.67*(1 + 0.00084*qsea)
wind,u10n=np.copy(spd),np.copy(spd) u10n = np.copy(spd)
cdn = cdn_calc(u10n,Ta,None,method) monob = -100*np.ones(u10n.shape) # Monin-Obukhov length
psim, psit, psiq = np.zeros(u10n.shape), np.zeros(u10n.shape), np.zeros(u10n.shape)
psim[inan], psit[inan], psiq[inan] = np.nan,np.nan,np.nan
cd = cd_calc(cdn,hh_in[0],ref_ht,psim)
zo = 0.0001*np.ones(u10n.shape)
zo[inan]=np.nan
monob = -100*np.ones(u10n.shape) # Monin-Obukhov length
monob[inan] = np.nan monob[inan] = np.nan
cdn = cdn_calc(u10n, Ta, None, meth)
psim, psit, psiq = (np.zeros(u10n.shape), np.zeros(u10n.shape),
np.zeros(u10n.shape))
psim[inan], psit[inan], psiq[inan] = np.nan, np.nan, np.nan
cd = cd_calc(cdn, hh_in[0], ref_ht, psim)
tsr, tsrv = np.zeros(u10n.shape), np.zeros(u10n.shape)
qsr = np.zeros(u10n.shape)
tsr[inan], tsrv[inan], qsr[inan] = np.nan, np.nan, np.nan
if (meth == "UA"):
wind = np.sqrt(np.power(np.copy(spd),2)+np.power(0.5,2))
zo, zot = 0.0001*np.ones(u10n.shape), 0.0001*np.ones(u10n.shape)
zol = hh_in[0]/monob
elif (meth == "ERA5"):
wind = np.sqrt(np.power(np.copy(spd),2)+np.power(0.5,2))
usr = np.sqrt(cd*wind**2)
Ribu = ((g*hh_in[0]*((2*(Ta-sst))/(Ta+sst-g*hh_in[0]) +
0.61*(qair-qsea)))/np.power(wind, 2))
zo = 0.11*visc_air(Ta)/usr+0.018*np.power(usr, 2)/g
zot = 0.40*visc_air(Ta)/usr
zol = (Ribu*((np.log((hh_in[0]+zo)/zo)-psim_calc((hh_in[0]+zo) /
monob, meth)+psim_calc(zo/monob, meth)) /
(np.log((hh_in[0]+zo)/zot) -
psit_calc((hh_in[0]+zo)/monob, meth) +
psit_calc(zot/monob, meth))))
else:
wind = np.copy(spd)
zo = 0.0001*np.ones(u10n.shape)
zo[inan] = np.nan
usr = np.sqrt(cd * wind**2) usr = np.sqrt(cd * wind**2)
tsr, tsrv, qsr = np.zeros(u10n.shape), np.zeros(u10n.shape), np.zeros(u10n.shape) # tolerance for u,t,q,usr,tsr,qsr
tsr[inan], tsrv[inan], qsr[inan] = np.nan, np.nan, np.nan tol = np.array([0.01, 0.01, 5e-05, 0.005, 0.001, 5e-07])
tol =np.array([0.01,0.01,5e-05,0.005,0.001,5e-07]) # tolerance u,t,q,usr,tsr,qsr
it = 0 it = 0
ind=np.where(spd>0) ind = np.where(spd > 0)
ii =True ii = True
itera=np.zeros(spd.shape)*np.nan itera = np.zeros(spd.shape)*np.nan
while np.any(ii): while np.any(ii):
it += 1 it += 1
# print('iter: '+str(it)+', p: '+str(np.shape(ind)[1])) # print('iter: '+str(it)+', p: '+str(np.shape(ind)[1]))
if it>n: if it > n:
break break
if (method == "COARE3.5"): if (meth == "C35"):
### This allows all iterations regardless of convergence as in original old = np.array([np.copy(u10n[ind]), np.copy(usr[ind]),
zet=kappa*g*hh_in[0]/(T+CtoK)*(tsr +0.61*(T+CtoK)*qsr)/(usr**2) np.copy(tsr[ind]), np.copy(qsr[ind])])
monob=hh_in[0]/zet zet = (kappa*g[ind]*hh_in[0]/(T[ind]+CtoK)*(tsr[ind]+0.61 *
zo,cd,ct,cq=cd_C35(u10n,wind,usr,ac[:,0],monob,(T+CtoK),hh_in,lat) (T[ind]+CtoK)*qsr[ind])/(usr[ind]**2))
usr=wind*cd monob[ind] = hh_in[0]/zet
qsr=-(dq-wetc*dter*jcool)*cq zo[ind], cd[ind], ct[ind], cq[ind] = cd_C35(u10n[ind], wind[ind],
tsr=-(dt-dter*jcool)*ct usr[ind], ac[ind,0],
tsrv=tsr+0.61*(T+CtoK)*qsr monob[ind],
ug = get_gust((T+CtoK),usr,tsrv,zi,lat) # gustiness (T[ind]+CtoK), hh_in,
wind = np.sqrt(np.copy(spd)**2 + ug**2) lat[ind])
gf=wind/spd usr[ind] = wind[ind]*cd[ind]
dter, dqer=get_skin(sst,qsea,rhoa,jcool,Rl,Rs,Rnl,cp,lv,usr,tsr,qsr,lat) qsr[ind] = -(dq[ind]-wetc[ind]*dter[ind]*jcool)*cq[ind]
Rnl=0.97*(5.67e-8*(SST-dter*jcool+CtoK)**4-Rl) tsr[ind] = -(dt[ind]-dter[ind]*jcool)*ct[ind]
if it==1: # save first iteration solution for case of zetu>50 tsrv[ind] = tsr[ind]+0.61*(T[ind]+CtoK)*qsr[ind]
usr50,tsr50,qsr50,L50=usr[k50],tsr[k50],qsr[k50],monob[k50] ug[ind] = get_gust(1.2, (T[ind]+CtoK), usr[ind], tsrv[ind],
dter50, dqer50=dter[k50], dqer[k50] zi, lat[ind]) # gustiness
u10n = usr/kappa/gf*np.log(10/zo) wind[ind] = np.sqrt(np.copy(spd[ind])**2 + ug[ind]**2)
ac=charnock_C35(wind,u10n,usr,seastate,waveage,wcp,sigH,lat) gf = wind/spd
new=np.array([np.copy(u10n),np.copy(usr),np.copy(tsr),np.copy(qsr)]) dter[ind], dqer[ind] = get_skin(sst[ind], qsea[ind], rhoa[ind],
### otherwise iterations stop when u,usr,tsr,qsr converge but gives different results than above option Rl[ind], Rs[ind], Rnl[ind], cp,
# old=np.array([np.copy(u10n[ind]),np.copy(usr[ind]),np.copy(tsr[ind]),np.copy(qsr[ind])]) lv[ind], usr[ind], tsr[ind],
# zet=kappa*g[ind]*hh_in[0]/(T[ind]+CtoK)*(tsr[ind] +0.61*(T[ind]+CtoK)*qsr[ind])/(usr[ind]**2) qsr[ind], lat[ind])
# monob[ind]=hh_in[0]/zet Rnl = 0.97*(5.67e-8*(SST-dter*jcool+CtoK)**4-Rl)
# zo[ind],cd[ind],ct[ind],cq[ind]=cd_C35(u10n[ind],wind[ind],usr[ind],ac[ind,0],monob[ind],(T[ind]+CtoK),hh_in,lat[ind]) if it == 1: # save first iteration solution for case of zetu>50
# usr[ind]=wind[ind]*cd[ind] usr50, tsr50, qsr50, L50 = (usr[k50], tsr[k50], qsr[k50],
# qsr[ind]=-(dq[ind]-wetc[ind]*dter[ind]*jcool)*cq[ind] monob[k50])
# tsr[ind]=-(dt[ind]-dter[ind]*jcool)*ct[ind] dter50, dqer50 = dter[k50], dqer[k50]
# tsrv[ind]=tsr[ind]+0.61*(T[ind]+CtoK)*qsr[ind] u10n[ind] = usr[ind]/kappa/gf[ind]*np.log(10/zo[ind])
# ug[ind] = get_gust((T[ind]+CtoK),usr[ind],tsrv[ind],zi,lat[ind]) # gustiness ac[ind][:] = charnock_C35(wind[ind], u10n[ind], usr[ind], seastate,
# wind[ind] = np.sqrt(np.copy(spd[ind])**2 + ug[ind]**2) waveage, wcp[ind], sigH[ind], lat[ind])
# gf=wind/spd new = np.array([np.copy(u10n[ind]), np.copy(usr[ind]),
# dter[ind], dqer[ind]=get_skin(sst[ind],qsea[ind],rhoa[ind],jcool,Rl[ind],Rs[ind],Rnl[ind],cp,lv[ind],usr[ind],tsr[ind],qsr[ind],lat[ind]) np.copy(tsr[ind]), np.copy(qsr[ind])])
# Rnl=0.97*(5.67e-8*(SST-dter*jcool+CtoK)**4-Rl) d = np.abs(new-old)
# if it==1: # save first iteration solution for case of zetu>50 ind = np.where((d[0,:] > tol[0])+(d[1,:] > tol[3]) +
# usr50,tsr50,qsr50,L50=usr[k50],tsr[k50],qsr[k50],monob[k50] (d[2,:] > tol[4])+(d[3,:] > tol[5]))
# dter50, dqer50=dter[k50], dqer[k50] itera[ind] = np.ones(1)*it
# u10n[ind] = usr[ind]/kappa/gf[ind]*np.log(10/zo[ind]) if np.shape(ind)[0] == 0:
# ac[ind][:]=charnock_C35(wind[ind],u10n[ind],usr[ind],seastate,waveage,wcp[ind],sigH[ind],lat[ind]) break
# new=np.array([np.copy(u10n[ind]),np.copy(usr[ind]),np.copy(tsr[ind]),np.copy(qsr[ind])]) else:
# print(str(it)+'L= '+str(monob),file=open('/noc/mpoc/surface_data/sbiri/SAMOS/C35_int.txt','a')) ii = ((d[0,:] > tol[0])+(d[1,:] > tol[3])+(d[2,:] > tol[4]) +
# print(str(it)+'dter= '+str(dter),file=open('/noc/mpoc/surface_data/sbiri/SAMOS/C35_int.txt','a')) (d[3,:] > tol[5]))
# print(str(it)+'ac= '+str(ac[:,0]),file=open('/noc/mpoc/surface_data/sbiri/SAMOS/C35_int.txt','a'))
# d=np.abs(new-old)
# ind=np.where((d[0,:]>tol[0])+(d[1,:]>tol[3])+(d[2,:]>tol[4])+(d[3,:]>tol[5]))
# itera[ind]=np.ones(1)*it
# if np.shape(ind)[0]==0:
# break
# else:
# ii = (d[0,:]>tol[0])+(d[1,:]>tol[3])+(d[2,:]>tol[4])+(d[3,:]>tol[5])
else: else:
old=np.array([np.copy(u10n[ind]),np.copy(t10n[ind]),np.copy(q10n[ind]),np.copy(usr[ind]),np.copy(tsr[ind]),np.copy(qsr[ind])]) old = np.array([np.copy(u10n[ind]), np.copy(t10n[ind]),
cdn[ind] = cdn_calc(u10n[ind],Ta[ind],None,method) np.copy(q10n[ind]), np.copy(usr[ind]),
np.copy(tsr[ind]), np.copy(qsr[ind])])
cdn[ind] = cdn_calc(u10n[ind], Ta[ind], None, meth)
if (np.all(np.isnan(cdn))): if (np.all(np.isnan(cdn))):
break #sys.exit("cdn cannot be nan") break # sys.exit("cdn cannot be nan")
logging.debug('%s at iteration %s cdn negative',method,it) logging.debug('break %s at iteration %s cdn<0', meth, it)
zo[ind] = ref_ht/np.exp(kappa/np.sqrt(cdn[ind])) zo[ind] = ref_ht/np.exp(kappa/np.sqrt(cdn[ind]))
psim[ind] = psim_calc(hh_in[0]/monob[ind],method) psim[ind] = psim_calc(hh_in[0]/monob[ind], meth)
cd[ind] = cd_calc(cdn[ind], hh_in[0], ref_ht, psim[ind]) cd[ind] = cd_calc(cdn[ind], hh_in[0], ref_ht, psim[ind])
ctn[ind], cqn[ind] = ctcqn_calc(hh_in[1]/monob[ind],cdn[ind],u10n[ind],zo[ind],Ta[ind],method) ctn[ind], cqn[ind] = ctcqn_calc(hh_in[1]/monob[ind], cdn[ind],
psit[ind] = psit_calc(hh_in[1]/monob[ind],method) u10n[ind], zo[ind], Ta[ind], meth)
psiq[ind] = psit_calc(hh_in[2]/monob[ind],method) psit[ind] = psit_calc(hh_in[1]/monob[ind], meth)
ct[ind], cq[ind] = ctcq_calc(cdn[ind], cd[ind], ctn[ind], cqn[ind], hh_in[1], hh_in[2], ref_ht, psit[ind], psiq[ind]) psiq[ind] = psit_calc(hh_in[2]/monob[ind], meth)
usr[ind] = np.sqrt(cd[ind]*wind[ind]**2) ct[ind], cq[ind] = ctcq_calc(cdn[ind], cd[ind], ctn[ind], cqn[ind],
hh_in[1], hh_in[2], ref_ht,
psit[ind], psiq[ind])
usr[ind] = np.sqrt(cd[ind]*wind[ind]**2)
tsr[ind] = ct[ind]*wind[ind]*(-dt[ind])/usr[ind] tsr[ind] = ct[ind]*wind[ind]*(-dt[ind])/usr[ind]
qsr[ind] = cq[ind]*wind[ind]*(-dq[ind])/usr[ind] qsr[ind] = cq[ind]*wind[ind]*(-dq[ind])/usr[ind]
fact = (np.log(hh_in[1]/ref_ht)-psit[ind])/kappa if (meth == "UA"):
t10n[ind] = Ta[ind] - (tsr[ind]*fact) zot[ind] = 10/(np.exp(kappa**2/(ctn[ind]*np.log(10/zo[ind]))))
fact = (np.log(hh_in[2]/ref_ht)-psiq[ind])/kappa zol[ind] = hh_in[0]/monob[ind]
q10n[ind] = qair[ind] - (qsr[ind]*fact) t10n[ind] = np.where(zol[ind]<-0.465,Ta[ind]-(tsr[ind]/kappa) *
(np.log((-0.465*monob[ind])/zot[ind]) -
psit_calc(-0.465,meth)+0.8 *
(np.power(0.465,-1/3) -
np.power(-zol[ind],-1/3))), \
np.where((zol[ind]>-0.465) & (zol[ind]<0),
Ta[ind]-(tsr[ind]/kappa) *
(np.log(hh_in[1]/zot[ind]) -
psit_calc(zol[ind],meth)), \
np.where((zol[ind]>0) & (zol[ind]<1),
Ta[ind]-(tsr[ind]/kappa) *
(np.log(hh_in[1]/zot[ind])+5*monob[ind]),
Ta[ind]-(tsr[ind]/kappa) *
(np.log(monob[ind]/zot[ind])+5 +
5*np.log(monob[ind])+monob[ind]-1))))
# Zeng et al. 1998 (11-14)
q10n[ind] = np.where(zol[ind]<-0.465,qair[ind] -
(qsr[ind]/kappa) *
(np.log((-0.465*monob[ind])/zot[ind]) -
psit_calc(-0.465,meth)+0.8 *
(np.power(0.465,-1/3) -
np.power(-zol[ind],-1/3))), \
np.where((zol[ind]>-0.465) & (zol[ind]<0),
qair[ind]-(qsr[ind]/kappa) *
(np.log(hh_in[1]/zot[ind]) -
psit_calc(zol[ind],meth)), \
np.where((zol[ind]>0) & (zol[ind]<1), \
qair[ind]-(qsr[ind]/kappa) *
(np.log(hh_in[1]/zot[ind])+5*monob[ind]),
qair[ind]-(qsr[ind]/kappa) *
(np.log(monob[ind]/zot[ind])+5 +
5*np.log(monob[ind])+monob[ind]-1))))
else:
fact = (np.log(hh_in[1]/ref_ht)-psit[ind])/kappa
t10n[ind] = Ta[ind] - (tsr[ind]*fact)
fact = (np.log(hh_in[2]/ref_ht)-psiq[ind])/kappa
q10n[ind] = qair[ind] - (qsr[ind]*fact)
tv10n[ind] = t10n[ind]*(1+0.61*q10n[ind]) tv10n[ind] = t10n[ind]*(1+0.61*q10n[ind])
tsrv[ind] = tsr[ind]+0.61*t10n[ind]*qsr[ind] tsrv[ind] = tsr[ind]+0.61*t10n[ind]*qsr[ind]
monob[ind] = (tv10n[ind]*usr[ind]**2)/(g[ind]*kappa*tsrv[ind]) if (meth == "ERA5"):
psim[ind] = psim_calc(hh_in[0]/monob[ind],method) zol[ind] = (kappa*g[ind]*hh_in[0]/Ta[ind]*(tsr[ind] +
psit[ind] = psit_calc(hh_in[1]/monob[ind],method) 0.61*Ta[ind]*qsr[ind])/(usr[ind]**2))
psiq[ind] = psit_calc(hh_in[2]/monob[ind],method) monob[ind] = hh_in[0]/zol[ind]
u10n[ind] = wind[ind] -(usr[ind]/kappa)*(np.log(hh_in[0]/ref_ht)-psim[ind]) else:
u10n[u10n<0]=np.nan # 0.5*old[0,np.where(u10n<0)] monob[ind] = (tv10n[ind]*usr[ind]**2)/(g[ind]*kappa*tsrv[ind])
new=np.array([np.copy(u10n[ind]),np.copy(t10n[ind]),np.copy(q10n[ind]),np.copy(usr[ind]),np.copy(tsr[ind]),np.copy(qsr[ind])]) psim[ind] = psim_calc(hh_in[0]/monob[ind], meth)
d=np.abs(new-old) psit[ind] = psit_calc(hh_in[1]/monob[ind], meth)
ind=np.where((d[0,:]>tol[0])+(d[1,:]>tol[1])+(d[2,:]>tol[2])+(d[3,:]>tol[3])+(d[4,:]>tol[4])+(d[5,:]>tol[5])) psiq[ind] = psit_calc(hh_in[2]/monob[ind], meth)
itera[ind]=np.ones(1)*it if (meth == "UA"):
if np.shape(ind)[0]==0: u10n[ind] = np.where(zol[ind]<-1.574,(usr[ind]/kappa) *
break ((np.log(-1.574*monob[ind]/zo[ind]) -
psim_calc(-1.574,meth))+1.14 *
(np.power(-monob[ind],1/3) -
np.power(1.574,1/3))), \
np.where((zol[ind]>-1.574) & (zol[ind]<0),
(usr[ind]/kappa)*(np.log(ref_ht/zo[ind]) -
psim_calc(zol[ind])), \
np.where((zol[ind]>0) & (zol[ind]<1),
(usr[ind]/kappa)*(np.log(ref_ht/zo[ind]) +
5*zol[ind]),(usr[ind]/kappa) *
(np.log(monob[ind]/zo[ind])+5 +
5*np.log(zol[ind])+zol[ind]-1))))
# Zeng et al. 1998 (7-10)
u10n[u10n<0] = np.nan
wind[ind] = np.sqrt(np.power(np.copy(spd[ind]),2) +
np.power(get_gust(1,Ta[ind], usr[ind],
tsrv[ind],zi,lat[ind]),2))
# Zeng et al. 1998 (20)
elif (meth == "ERA5"):
wind[ind] = np.sqrt(np.copy(spd[ind])**2 +
(get_gust(1, Ta[ind], usr[ind], tsrv[ind],
zi, lat[ind]))**2)
u10n[ind] = (wind[ind]-(usr[ind]/kappa)*(np.log(hh_in[0] /
ref_ht)-psim[ind]))
u10n[u10n < 0] = np.nan
else: else:
ii = (d[0,ind]>tol[0])+(d[1,ind]>tol[1])+(d[2,ind]>tol[2])+(d[3,ind]>tol[3])+(d[4,ind]>tol[4])+(d[5,ind]>tol[5]) u10n[ind] = (wind[ind]-(usr[ind]/kappa)*(np.log(hh_in[0] /
ref_ht)-psim[ind]))
u10n[u10n < 0] = np.nan # 0.5*old[0,np.where(u10n<0)]
new = np.array([np.copy(u10n[ind]), np.copy(t10n[ind]),
np.copy(q10n[ind]), np.copy(usr[ind]),
np.copy(tsr[ind]), np.copy(qsr[ind])])
d = np.abs(new-old)
ind = np.where((d[0, :] > tol[0])+(d[1, :] > tol[1]) +
(d[2, :] > tol[2])+(d[3, :] > tol[3]) +
(d[4, :] > tol[4])+(d[5, :] > tol[5]))
itera[ind] = np.ones(1)*it
if np.shape(ind)[0] == 0:
break
else:
ii = ((d[0, ind] > tol[0])+(d[1, ind] > tol[1]) +
(d[2, ind] > tol[2])+(d[3, ind] > tol[3]) +
(d[4, ind] > tol[4])+(d[5, ind] > tol[5]))
# calculate output parameters # calculate output parameters
if (method == "COARE3.5"): if (meth == "C35"):
usr[k50],tsr[k50],qsr[k50],monob[k50]=usr50,tsr50,qsr50,L50 usr[k50], tsr[k50], qsr[k50], monob[k50] = usr50, tsr50, qsr50, L50
dter[k50], dqer[k50]=dter50, dqer50 dter[k50], dqer[k50] = dter50, dqer50
rhoa = P*100/(287.1*(T+CtoK)*(1+0.61*qair)) rhoa = P*100/(287.1*(T+CtoK)*(1+0.61*qair))
rr=zo*usr/visc_air(T+CtoK) rr = zo*usr/visc_air(T+CtoK)
zoq=np.where(5.8e-5/rr**0.72>1.6e-4,1.6e-4,5.8e-5/rr**0.72) zoq = np.where(5.8e-5/rr**0.72 > 1.6e-4, 1.6e-4, 5.8e-5/rr**0.72)
zot=zoq zot = zoq
psiT=psit_26(hh_in[1]/monob) psiT = psit_26(hh_in[1]/monob)
psi10T=psit_26(10/monob) psi10T = psit_26(10/monob)
psi=psiu_26(hh_in[0]/monob) psi = psiu_26(hh_in[0]/monob)
psirf=psiu_26(hh_out[0]/monob) psirf = psiu_26(hh_out[0]/monob)
q10 = qair + qsr/kappa*(np.log(10/hh_in[2])-psi10T+psiT) q10 = qair + qsr/kappa*(np.log(10/hh_in[2])-psi10T+psiT)
tau = rhoa*usr*usr/gf tau = rhoa*usr*usr/gf
sensible=-rhoa*cp*usr*tsr sensible = -rhoa*cp*usr*tsr
latent=-rhoa*lv*usr*qsr latent = -rhoa*lv*usr*qsr
cd = tau/rhoa/wind/np.where(spd<0.1,0.1,spd) cd = tau/rhoa/wind/np.where(spd < 0.1, 0.1, spd)
cdn = 1000*kappa**2/np.log(10/zo)**2 cdn = 1000*kappa**2/np.log(10/zo)**2
ct = -usr*tsr/wind/(dt-dter*jcool) ct = -usr*tsr/wind/(dt-dter*jcool)
ctn = 1000*kappa**2/np.log(10/zo)/np.log(10/zot) ctn = 1000*kappa**2/np.log(10/zo)/np.log(10/zot)
...@@ -267,12 +435,15 @@ def flux_calc_v3(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi = 600, \ ...@@ -267,12 +435,15 @@ def flux_calc_v3(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi = 600, \
psim = psiu_26(hh_in[0]/monob) psim = psiu_26(hh_in[0]/monob)
psit = psit_26(hh_in[1]/monob) psit = psit_26(hh_in[1]/monob)
u10n = u10+psiu_26(10/monob)*usr/kappa/gf u10n = u10+psiu_26(10/monob)*usr/kappa/gf
t10n = T + tsr/kappa*(np.log(10/hh_in[1])-psi10T+psiT) + tlapse*(hh_in[1]-10)+ psi10T*tsr/kappa t10n = (T+tsr/kappa*(np.log(10/hh_in[1])-psi10T+psiT) +
tlapse*(hh_in[1]-10)+psi10T*tsr/kappa)
q10n = q10 + psi10T*qsr/kappa q10n = q10 + psi10T*qsr/kappa
tv10n = t10n*(1+0.61*q10n) tv10n = t10n*(1+0.61*q10n)
urefs = spd + usr/kappa/gf*(np.log(hh_out[0]/hh_in[0])-psirf+psi) urefs = spd + usr/kappa/gf*(np.log(hh_out[0]/hh_in[0])-psirf+psi)
trefs =T + tsr/kappa*(np.log(hh_out[1]/hh_in[1])-psit_26(hh_out[1]/monob)+psiT) + tlapse*(hh_in[1]-hh_out[1]) trefs = (T+tsr/kappa*(np.log(hh_out[1]/hh_in[1]) -
qrefs= qair + qsr/kappa*(np.log(hh_out[2]/hh_in[2])-psit_26(hh_out[2]/monob)+psiT) psit_26(hh_out[1]/monob)+psiT)+tlapse*(hh_in[1]-hh_out[1]))
qrefs = (qair+qsr/kappa*(np.log(hh_out[2]/hh_in[2]) -
psit_26(hh_out[2]/monob)+psiT))
else: else:
rho = (0.34838*P)/(tv10n) rho = (0.34838*P)/(tv10n)
t10n = t10n-(273.16+tlapse*ref_ht) t10n = t10n-(273.16+tlapse*ref_ht)
...@@ -282,59 +453,39 @@ def flux_calc_v3(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi = 600, \ ...@@ -282,59 +453,39 @@ def flux_calc_v3(spd, T, SST, lat, RH, P, hin, hout, wcp, sigH, zi = 600, \
zo = ref_ht/np.exp(kappa/cdn**0.5) zo = ref_ht/np.exp(kappa/cdn**0.5)
zot = ref_ht/(np.exp(kappa**2/(ctn*np.log(ref_ht/zo)))) zot = ref_ht/(np.exp(kappa**2/(ctn*np.log(ref_ht/zo))))
zoq = ref_ht/(np.exp(kappa**2/(cqn*np.log(ref_ht/zo)))) zoq = ref_ht/(np.exp(kappa**2/(cqn*np.log(ref_ht/zo))))
urefs=spd-(usr/kappa)*(np.log(hh_in[0]/hh_out[0])-psim+psim_calc(hh_out[0]/monob,method)) urefs = (spd-(usr/kappa)*(np.log(hh_in[0]/hh_out[0])-psim +
trefs=Ta-(tsr/kappa)*(np.log(hh_in[1]/hh_out[1])-psit+psit_calc(hh_out[0]/monob,method)) psim_calc(hh_out[0]/monob, meth)))
trefs = (Ta-(tsr/kappa)*(np.log(hh_in[1]/hh_out[1])-psit +
psit_calc(hh_out[0]/monob, meth)))
trefs = trefs-(273.16+tlapse*hh_out[1]) trefs = trefs-(273.16+tlapse*hh_out[1])
qrefs=qair-(qsr/kappa)*(np.log(hh_in[2]/hh_out[2])-psit+psit_calc(hh_out[2]/monob,method)) qrefs = (qair-(qsr/kappa)*(np.log(hh_in[2]/hh_out[2]) -
res=np.zeros((27,len(spd))) psit+psit_calc(hh_out[2]/monob, meth)))
res[0][:]=tau res = np.zeros((27, len(spd)))
res[1][:]=sensible res[0][:] = tau
res[2][:]=latent res[1][:] = sensible
res[3][:]=monob res[2][:] = latent
res[4][:]=cd res[3][:] = monob
res[5][:]=cdn res[4][:] = cd
res[6][:]=ct res[5][:] = cdn
res[7][:]=ctn res[6][:] = ct
res[8][:]=cq res[7][:] = ctn
res[9][:]=cqn res[8][:] = cq
res[10][:]=tsrv res[9][:] = cqn
res[11][:]=tsr res[10][:] = tsrv
res[12][:]=qsr res[11][:] = tsr
res[13][:]=usr res[12][:] = qsr
res[14][:]=psim res[13][:] = usr
res[15][:]=psit res[14][:] = psim
res[16][:]=u10n res[15][:] = psit
res[17][:]=t10n res[16][:] = u10n
res[18][:]=tv10n res[17][:] = t10n
res[19][:]=q10n res[18][:] = tv10n
res[20][:]=zo res[19][:] = q10n
res[21][:]=zot res[20][:] = zo
res[22][:]=zoq res[21][:] = zot
res[23][:]=urefs res[22][:] = zoq
res[24][:]=trefs res[23][:] = urefs
res[25][:]=qrefs res[24][:] = trefs
res[26][:]=itera res[25][:] = qrefs
res[26][:] = itera
return res, ind return res, ind
def charnock_C35(wind,u10n,usr,seastate,waveage,wcp,sigH,lat):
g=gc(lat,None)
a1, a2=0.0017, -0.0050
charnC=np.where(u10n>19,a1*19+a2,a1*u10n+a2)
A, B=0.114, 0.622 #wave-age dependent coefficients
Ad, Bd=0.091, 2.0 #Sea-state/wave-age dependent coefficients
charnW=A*(usr/wcp)**B
zoS=sigH*Ad*(usr/wcp)**Bd
charnS=(zoS*g)/usr**2
charn=np.where(wind>10,0.011+(wind-10)/(18-10)*(0.018-0.011),np.where(wind>18,0.018,0.011*np.ones(np.shape(wind))))
if waveage:
if seastate:
charn=charnS
else:
charn=charnW
else:
charn=charnC
ac = np.zeros((len(wind),3))
ac[:,0] = charn
ac[:,1] = charnC
ac[:,2] = charnW
return ac
\ No newline at end of file
import numpy as np import numpy as np
""" Conversion factor for [:math:`^\\circ` C] to [:math:`^\\circ` K] """
CtoK = 273.16 # 273.15 CtoK = 273.16 # 273.15
""" von Karman's constant """ """ Conversion factor for (^\circ\,C) to (^\\circ\\,K) """
kappa = 0.4 # NOTE: 0.41 kappa = 0.4 # NOTE: 0.41
""" von Karman's constant """
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def charnock_C35(wind, u10n, usr, seastate, waveage, wcp, sigH, lat): def charnock_C35(wind, u10n, usr, seastate, waveage, wcp, sigH, lat):
""" Calculates Charnock number following Edson et al. 2013 based on
C35 matlab code (coare35vn.m)
Parameters
----------
wind : float
wind speed (m/s)
u10n : float
neutral 10m wind speed (m/s)
usr : float
friction velocity (m/s)
seastate : bool
0 or 1
waveage : bool
0 or 1
wcp : float
phase speed of dominant waves (m/s)
sigH : float
significant wave height (m)
lat : float
latitude (deg)
Returns
-------
ac : float
Charnock number
"""
g = gc(lat, None) g = gc(lat, None)
a1, a2 = 0.0017, -0.0050 a1, a2 = 0.0017, -0.0050
charnC = np.where(u10n > 19, a1*19+a2, a1*u10n+a2) charnC = np.where(u10n > 19, a1*19+a2, a1*u10n+a2)
...@@ -34,6 +62,37 @@ def charnock_C35(wind, u10n, usr, seastate, waveage, wcp, sigH, lat): ...@@ -34,6 +62,37 @@ def charnock_C35(wind, u10n, usr, seastate, waveage, wcp, sigH, lat):
def cd_C35(u10n, wind, usr, charn, monob, Ta, hh_in, lat): def cd_C35(u10n, wind, usr, charn, monob, Ta, hh_in, lat):
""" Calculates exchange coefficients following Edson et al. 2013 based on
C35 matlab code (coare35vn.m)
Parameters
----------
u10n : float
neutral 10m wind speed (m/s)
wind : float
wind speed (m/s)
charn : float
Charnock number
monob : float
Monin-Obukhov stability length
Ta : float
air temperature (K)
hh_in : float
input sensor's height (m)
lat : float
latitude (deg)
Returns
-------
zo : float
surface roughness (m)
cdhf : float
drag coefficient
cthf : float
heat exchange coefficient
cqhf : float
moisture exchange coefficient
"""
g = gc(lat, None) g = gc(lat, None)
zo = charn*usr**2/g+0.11*visc_air(Ta)/usr # surface roughness zo = charn*usr**2/g+0.11*visc_air(Ta)/usr # surface roughness
rr = zo*usr/visc_air(Ta) rr = zo*usr/visc_air(Ta)
...@@ -47,15 +106,31 @@ def cd_C35(u10n, wind, usr, charn, monob, Ta, hh_in, lat): ...@@ -47,15 +106,31 @@ def cd_C35(u10n, wind, usr, charn, monob, Ta, hh_in, lat):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def cdn_calc(u10n, Ta, Tp, method="Smith80"): def cdn_calc(u10n, Ta, Tp, method="S80"):
if (method == "Smith80"): """ Calculates neutral drag coefficient
Parameters
----------
u10n : float
neutral 10m wind speed (m/s)
Ta : float
air temperature (K)
Tp : float
wave period
method : str
Returns
-------
cdn : float
"""
if (method == "S80"):
cdn = np.where(u10n <= 3, (0.61+0.567/u10n)*0.001, cdn = np.where(u10n <= 3, (0.61+0.567/u10n)*0.001,
(0.61+0.063*u10n)*0.001) (0.61+0.063*u10n)*0.001)
elif (method == "LP82"): elif (method == "LP82"):
cdn = np.where((u10n < 11) & (u10n >= 4), 1.2*0.001, cdn = np.where((u10n < 11) & (u10n >= 4), 1.2*0.001,
np.where((u10n <= 25) & (u10n >= 11), np.where((u10n <= 25) & (u10n >= 11),
(0.49+0.065*u10n)*0.001, 1.14*0.001)) (0.49+0.065*u10n)*0.001, 1.14*0.001))
elif (method == "Smith88" or method == "COARE3.0" or elif (method == "S88" or method == "C30" or
method == "COARE4.0" or method == "UA" or method == "ERA5"): method == "COARE4.0" or method == "UA" or method == "ERA5"):
cdn = cdn_from_roughness(u10n, Ta, None, method) cdn = cdn_from_roughness(u10n, Ta, None, method)
elif (method == "HEXOS"): elif (method == "HEXOS"):
...@@ -64,7 +139,7 @@ def cdn_calc(u10n, Ta, Tp, method="Smith80"): ...@@ -64,7 +139,7 @@ def cdn_calc(u10n, Ta, Tp, method="Smith80"):
elif (method == "HEXOSwave"): elif (method == "HEXOSwave"):
cdn = cdn_from_roughness(u10n, Ta, Tp, method) cdn = cdn_from_roughness(u10n, Ta, Tp, method)
elif (method == "YT96"): elif (method == "YT96"):
# for u<3 same as Smith80 # for u<3 same as S80
cdn = np.where((u10n < 6) & (u10n >= 3), cdn = np.where((u10n < 6) & (u10n >= 3),
(0.29+3.1/u10n+7.7/u10n**2)*0.001, (0.29+3.1/u10n+7.7/u10n**2)*0.001,
np.where((u10n <= 26) & (u10n >= 6), np.where((u10n <= 26) & (u10n >= 6),
...@@ -78,7 +153,23 @@ def cdn_calc(u10n, Ta, Tp, method="Smith80"): ...@@ -78,7 +153,23 @@ def cdn_calc(u10n, Ta, Tp, method="Smith80"):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def cdn_from_roughness(u10n, Ta, Tp, method="Smith88"): def cdn_from_roughness(u10n, Ta, Tp, method="S88"):
""" Calculates neutral drag coefficient from roughness length
Parameters
----------
u10n : float
neutral 10m wind speed (m/s)
Ta : float
air temperature (K)
Tp : float
wave period
method : str
Returns
-------
cdn : float
"""
g, tol = 9.812, 0.000001 g, tol = 9.812, 0.000001
cdn, usr = np.zeros(Ta.shape), np.zeros(Ta.shape) cdn, usr = np.zeros(Ta.shape), np.zeros(Ta.shape)
cdnn = (0.61+0.063*u10n)*0.001 cdnn = (0.61+0.063*u10n)*0.001
...@@ -86,13 +177,13 @@ def cdn_from_roughness(u10n, Ta, Tp, method="Smith88"): ...@@ -86,13 +177,13 @@ def cdn_from_roughness(u10n, Ta, Tp, method="Smith88"):
for it in range(5): for it in range(5):
cdn = np.copy(cdnn) cdn = np.copy(cdnn)
usr = np.sqrt(cdn*u10n**2) usr = np.sqrt(cdn*u10n**2)
if (method == "Smith88"): if (method == "S88"):
# .....Charnock roughness length (equn 4 in Smith 88) # .....Charnock roughness length (equn 4 in Smith 88)
zc = 0.011*np.power(usr, 2)/g zc = 0.011*np.power(usr, 2)/g
# .....smooth surface roughness length (equn 6 in Smith 88) # .....smooth surface roughness length (equn 6 in Smith 88)
zs = 0.11*visc_air(Ta)/usr zs = 0.11*visc_air(Ta)/usr
zo = zc + zs # .....equns 7 & 8 in Smith 88 to calculate new CDN zo = zc + zs # .....equns 7 & 8 in Smith 88 to calculate new CDN
elif (method == "COARE3.0"): elif (method == "C30"):
zc = 0.011 + (u10n-10)/(18-10)*(0.018-0.011) zc = 0.011 + (u10n-10)/(18-10)*(0.018-0.011)
zc = np.where(u10n < 10, 0.011, np.where(u10n > 18, 0.018, zc)) zc = np.where(u10n < 10, 0.011, np.where(u10n > 18, 0.018, zc))
zs = 0.11*visc_air(Ta)/usr zs = 0.11*visc_air(Ta)/usr
...@@ -116,9 +207,32 @@ def cdn_from_roughness(u10n, Ta, Tp, method="Smith88"): ...@@ -116,9 +207,32 @@ def cdn_from_roughness(u10n, Ta, Tp, method="Smith88"):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def ctcqn_calc(zol, cdn, u10n, zo, Ta, method="Smith80"): def ctcqn_calc(zol, cdn, u10n, zo, Ta, method="S80"):
if (method == "Smith80" or method == "Smith88" or method == "YT96"): """ Calculates neutral heat and moisture exchange coefficients
cqn = np.ones(Ta.shape)*1.20*0.001 # from Smith88
Parameters
----------
zol : float
height over MO length
cdn : float
neatral drag coefficient
u10n : float
neutral 10m wind speed (m/s)
zo : float
surface roughness (m)
Ta : float
air temperature (K)
method : str
Returns
-------
ctn : float
neutral heat exchange coefficient
cqn : float
neutral moisture exchange coefficient
"""
if (method == "S80" or method == "S88" or method == "YT96"):
cqn = np.ones(Ta.shape)*1.20*0.001 # from S88
ctn = np.ones(Ta.shape)*1.00*0.001 ctn = np.ones(Ta.shape)*1.00*0.001
elif (method == "LP82"): elif (method == "LP82"):
cqn = np.where((zol <= 0) & (u10n > 4) & (u10n < 14), 1.15*0.001, cqn = np.where((zol <= 0) & (u10n > 4) & (u10n < 14), 1.15*0.001,
...@@ -128,7 +242,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, method="Smith80"): ...@@ -128,7 +242,7 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, method="Smith80"):
elif (method == "HEXOS" or method == "HEXOSwave"): elif (method == "HEXOS" or method == "HEXOSwave"):
cqn = np.where((u10n <= 23) & (u10n >= 3), 1.1*0.001, np.nan) cqn = np.where((u10n <= 23) & (u10n >= 3), 1.1*0.001, np.nan)
ctn = np.where((u10n <= 18) & (u10n >= 3), 1.1*0.001, np.nan) ctn = np.where((u10n <= 18) & (u10n >= 3), 1.1*0.001, np.nan)
elif (method == "COARE3.0" or method == "COARE4.0"): elif (method == "C30" or method == "COARE4.0"):
usr = (cdn*u10n**2)**0.5 usr = (cdn*u10n**2)**0.5
rr = zo*usr/visc_air(Ta) rr = zo*usr/visc_air(Ta)
zoq = 5.5e-5/rr**0.6 zoq = 5.5e-5/rr**0.6
...@@ -162,6 +276,23 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, method="Smith80"): ...@@ -162,6 +276,23 @@ def ctcqn_calc(zol, cdn, u10n, zo, Ta, method="Smith80"):
def cd_calc(cdn, height, ref_ht, psim): 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.power(kappa, -1) * cd = (cdn*np.power(1+np.sqrt(cdn)*np.power(kappa, -1) *
(np.log(height/ref_ht)-psim), -2)) (np.log(height/ref_ht)-psim), -2))
return cd return cd
...@@ -169,16 +300,56 @@ def cd_calc(cdn, height, ref_ht, psim): ...@@ -169,16 +300,56 @@ def cd_calc(cdn, height, ref_ht, psim):
def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq): def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq):
""" Calculates heat and moisture exchange coefficients at reference height
Parameters
----------
cdn : float
neutral drag coefficient
cd : float
drag coefficient at reference height
ctn : float
neutral heat exchange coefficient
cqn : float
neutral moisture exchange coefficient
h_t : float
original temperature sensor height (m)
h_q : float
original moisture sensor height (m)
ref_ht : float
reference height (m)
psit : float
heat stability function
psiq : float
moisture stability function
Returns
-------
ct : float
cq : float
"""
ct = ctn*(cd/cdn)**0.5/(1+ctn*((np.log(h_t/ref_ht)-psit)/(kappa*cdn**0.5))) ct = ctn*(cd/cdn)**0.5/(1+ctn*((np.log(h_t/ref_ht)-psit)/(kappa*cdn**0.5)))
cq = cqn*(cd/cdn)**0.5/(1+cqn*((np.log(h_q/ref_ht)-psiq)/(kappa*cdn**0.5))) cq = cqn*(cd/cdn)**0.5/(1+cqn*((np.log(h_q/ref_ht)-psiq)/(kappa*cdn**0.5)))
return ct, cq return ct, cq
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psim_calc(zol, method="Smith80"): def psim_calc(zol, method="S80"):
""" Calculates momentum stability function
Parameters
----------
zol : float
height over MO length
method : str
Returns
-------
psim : float
"""
coeffs = get_stabco(method) coeffs = get_stabco(method)
alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2] alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
if (method == "COARE3.0" or method == "COARE4.0"): if (method == "C30" or method == "COARE4.0"):
psim = np.where(zol < 0, psim_conv_coare3(zol, alpha, beta, gamma), psim = np.where(zol < 0, psim_conv_coare3(zol, alpha, beta, gamma),
psim_stab_coare3(zol, alpha, beta, gamma)) psim_stab_coare3(zol, alpha, beta, gamma))
elif (method == "ERA5"): elif (method == "ERA5"):
...@@ -191,10 +362,22 @@ def psim_calc(zol, method="Smith80"): ...@@ -191,10 +362,22 @@ def psim_calc(zol, method="Smith80"):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psit_calc(zol, method="Smith80"): def psit_calc(zol, method="S80"):
""" Calculates heat stability function
Parameters
----------
zol : float
height over MO length
method : str
Returns
-------
psit : float
"""
coeffs = get_stabco(method) coeffs = get_stabco(method)
alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2] alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
if (method == "COARE3.0" or method == "COARE4.0"): if (method == "C30" or method == "COARE4.0"):
psit = np.where(zol < 0, psi_conv_coare3(zol, alpha, beta, gamma), psit = np.where(zol < 0, psi_conv_coare3(zol, alpha, beta, gamma),
psi_stab_coare3(zol, alpha, beta, gamma)) psi_stab_coare3(zol, alpha, beta, gamma))
elif (method == "ERA5"): elif (method == "ERA5"):
...@@ -207,8 +390,18 @@ def psit_calc(zol, method="Smith80"): ...@@ -207,8 +390,18 @@ def psit_calc(zol, method="Smith80"):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def get_stabco(method="Smith80"): def get_stabco(method="S80"):
if (method == "Smith80" or method == "Smith88" or method == "LY04" or """ Gives the coefficients \\alpha, \\beta, \\gamma for stability functions
Parameters
----------
method : str
Returns
-------
coeffs : float
"""
if (method == "S80" or method == "S88" or method == "LY04" or
method == "UA" or method == "ERA5"): method == "UA" or method == "ERA5"):
alpha, beta, gamma = 16, 0.25, 5 # Smith 1980, from Dyer (1974) alpha, beta, gamma = 16, 0.25, 5 # Smith 1980, from Dyer (1974)
elif (method == "LP82"): elif (method == "LP82"):
...@@ -217,7 +410,7 @@ def get_stabco(method="Smith80"): ...@@ -217,7 +410,7 @@ def get_stabco(method="Smith80"):
alpha, beta, gamma = 16, 0.25, 8 alpha, beta, gamma = 16, 0.25, 8
elif (method == "YT96"): elif (method == "YT96"):
alpha, beta, gamma = 20, 0.25, 5 alpha, beta, gamma = 20, 0.25, 5
elif (method == "COARE3.0" or method == "COARE4.0"): elif (method == "C30" or method == "COARE4.0"):
# use separate subroutine # use separate subroutine
alpha, beta, gamma = 15, 1/3, 5 # not sure about gamma=34.15 alpha, beta, gamma = 15, 1/3, 5 # not sure about gamma=34.15
else: else:
...@@ -231,6 +424,22 @@ def get_stabco(method="Smith80"): ...@@ -231,6 +424,22 @@ def get_stabco(method="Smith80"):
def psi_conv_coare3(zol, alpha, beta, gamma): def psi_conv_coare3(zol, alpha, beta, gamma):
""" Calculates heat stability function for unstable conditions
for method C30
Parameters
----------
zol : float
height over MO length
alpha : float
beta : float
gamma : float
constants given by get_stabco
Returns
-------
psit : float
"""
x = (1-alpha*zol)**0.5 # Kansas unstable x = (1-alpha*zol)**0.5 # Kansas unstable
psik = 2*np.log((1+x)/2.) psik = 2*np.log((1+x)/2.)
y = (1-34.15*zol)**beta y = (1-34.15*zol)**beta
...@@ -242,7 +451,21 @@ def psi_conv_coare3(zol, alpha, beta, gamma): ...@@ -242,7 +451,21 @@ def psi_conv_coare3(zol, alpha, beta, gamma):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psi_stab_coare3(zol, alpha, beta, gamma): # Stable def psi_stab_coare3(zol, alpha, beta, gamma):
""" Calculates heat stability function for stable conditions
for method C30
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psi : float
"""
c = np.where(0.35*zol > 50, 50, 0.35*zol) # Stable c = np.where(0.35*zol > 50, 50, 0.35*zol) # Stable
psit = -((1+2*zol/3)**1.5+0.6667*(zol-14.28)/np.exp(c)+8.525) psit = -((1+2*zol/3)**1.5+0.6667*(zol-14.28)/np.exp(c)+8.525)
return psit return psit
...@@ -250,6 +473,20 @@ def psi_stab_coare3(zol, alpha, beta, gamma): # Stable ...@@ -250,6 +473,20 @@ def psi_stab_coare3(zol, alpha, beta, gamma): # Stable
def psi_stab_era5(zol, alpha, beta, gamma): def psi_stab_era5(zol, alpha, beta, gamma):
""" Calculates heat stability function for stable conditions
for method ERA5
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psit : float
"""
# eq (3.22) p. 39 IFS Documentation cy46r1 # eq (3.22) p. 39 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
...@@ -257,6 +494,19 @@ def psi_stab_era5(zol, alpha, beta, gamma): ...@@ -257,6 +494,19 @@ def psi_stab_era5(zol, alpha, beta, gamma):
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psi_conv(zol, alpha, beta, gamma): def psi_conv(zol, alpha, beta, gamma):
""" Calculates heat stability function for unstable conditions
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psit : float
"""
xtmp = (1-alpha*zol)**beta xtmp = (1-alpha*zol)**beta
psit = 2*np.log((1+xtmp**2)*0.5) psit = 2*np.log((1+xtmp**2)*0.5)
return psit return psit
...@@ -264,30 +514,65 @@ def psi_conv(zol, alpha, beta, gamma): ...@@ -264,30 +514,65 @@ def psi_conv(zol, alpha, beta, gamma):
def psi_stab(zol, alpha, beta, gamma): def psi_stab(zol, alpha, beta, gamma):
""" Calculates heat stability function for stable conditions
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psit : float
"""
psit = -gamma*zol psit = -gamma*zol
return psit return psit
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psit_26(zet): def psit_26(zol):
""" """ Computes temperature structure function as in C35
computes temperature structure function as in COARE3.5
Parameters
----------
zol : float
height over MO length
Returns
-------
psi : float
""" """
dzet = np.where(0.35*zet > 50, 50, 0.35*zet) # stable dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable
psi = -((1+0.6667*zet)**1.5+0.6667*(zet-14.28)*np.exp(-dzet)+8.525) psi = -((1+0.6667*zol)**1.5+0.6667*(zol-14.28)*np.exp(-dzol)+8.525)
k = np.where(zet < 0) # unstable k = np.where(zol < 0) # unstable
x = (1-15*zet[k])**0.5 x = (1-15*zol[k])**0.5
psik = 2*np.log((1+x)/2) psik = 2*np.log((1+x)/2)
x = (1-34.15*zet[k])**0.3333 x = (1-34.15*zol[k])**0.3333
psic = (1.5*np.log((1+x+x**2)/3)-np.sqrt(3)*np.arctan((1+2*x) / psic = (1.5*np.log((1+x+x**2)/3)-np.sqrt(3)*np.arctan((1+2*x) /
np.sqrt(3))+4*np.arctan(1)/np.sqrt(3)) np.sqrt(3))+4*np.arctan(1)/np.sqrt(3))
f = zet[k]**2/(1+zet[k]**2) f = zol[k]**2/(1+zol[k]**2)
psi[k] = (1-f)*psik+f*psic psi[k] = (1-f)*psik+f*psic
return psi return psi
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psim_conv_coare3(zol, alpha, beta, gamma): def psim_conv_coare3(zol, alpha, beta, gamma):
""" Calculates momentum stability function for unstable conditions
for method C30
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psim : float
"""
x = (1-15*zol)**0.25 # Kansas unstable x = (1-15*zol)**0.25 # Kansas unstable
psik = 2*np.log((1+x)/2)+np.log((1+x*x)/2)-2*np.arctan(x)+2*np.arctan(1) psik = 2*np.log((1+x)/2)+np.log((1+x*x)/2)-2*np.arctan(x)+2*np.arctan(1)
y = (1-10.15*zol)**0.3333 # Convective y = (1-10.15*zol)**0.3333 # Convective
...@@ -300,6 +585,20 @@ def psim_conv_coare3(zol, alpha, beta, gamma): ...@@ -300,6 +585,20 @@ def psim_conv_coare3(zol, alpha, beta, gamma):
def psim_stab_coare3(zol, alpha, beta, gamma): def psim_stab_coare3(zol, alpha, beta, gamma):
""" Calculates momentum stability function for stable conditions
for method C30
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psim : float
"""
c = np.where(0.35*zol > 50, 50, 0.35*zol) # Stable c = np.where(0.35*zol > 50, 50, 0.35*zol) # Stable
psim = -((1+1*zol)**1.0+0.6667*(zol-14.28)/np.exp(-c)+8.525) psim = -((1+1*zol)**1.0+0.6667*(zol-14.28)/np.exp(-c)+8.525)
return psim return psim
...@@ -307,6 +606,20 @@ def psim_stab_coare3(zol, alpha, beta, gamma): ...@@ -307,6 +606,20 @@ def psim_stab_coare3(zol, alpha, beta, gamma):
def psim_stab_era5(zol, alpha, beta, gamma): def psim_stab_era5(zol, alpha, beta, gamma):
""" Calculates momentum stability function for stable conditions
for method ERA5
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psim : float
"""
# eq (3.22) p. 39 IFS Documentation cy46r1 # eq (3.22) p. 39 IFS Documentation cy46r1
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 = -b*(zol-c/d)*np.exp(-d*zol)-a*zol-(b*c)/d
...@@ -315,6 +628,19 @@ def psim_stab_era5(zol, alpha, beta, gamma): ...@@ -315,6 +628,19 @@ def psim_stab_era5(zol, alpha, beta, gamma):
def psim_conv(zol, alpha, beta, gamma): def psim_conv(zol, alpha, beta, gamma):
""" Calculates momentum stability function for unstable conditions
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psim : float
"""
xtmp = (1-alpha*zol)**beta xtmp = (1-alpha*zol)**beta
psim = (2*np.log((1+xtmp)*0.5)+np.log((1+xtmp**2)*0.5) - psim = (2*np.log((1+xtmp)*0.5)+np.log((1+xtmp**2)*0.5) -
2*np.arctan(xtmp)+np.pi/2) 2*np.arctan(xtmp)+np.pi/2)
...@@ -323,50 +649,112 @@ def psim_conv(zol, alpha, beta, gamma): ...@@ -323,50 +649,112 @@ def psim_conv(zol, alpha, beta, gamma):
def psim_stab(zol, alpha, beta, gamma): def psim_stab(zol, alpha, beta, gamma):
""" Calculates momentum stability function for stable conditions
Parameters
----------
zol : float
height over MO length
alpha, beta, gamma : float
constants given by get_stabco
Returns
-------
psim : float
"""
psim = -gamma*zol psim = -gamma*zol
return psim return psim
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def psiu_26(zet): def psiu_26(zol):
""" Computes velocity structure function C35
Parameters
----------
zol : float
height over MO length
Returns
-------
psi : float
""" """
computes velocity structure function COARE3.5 dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable
"""
dzet = np.where(0.35*zet > 50, 50, 0.35*zet) # 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*zet+b*(zet-c/d)*np.exp(-dzet)+b*c/d) psi = -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d)
k = np.where(zet < 0) # unstable k = np.where(zol < 0) # unstable
x = (1-15*zet[k])**0.25 x = (1-15*zol[k])**0.25
psik = 2*np.log((1+x)/2)+np.log((1+x**2)/2)-2*np.arctan(x)+2*np.arctan(1) psik = 2*np.log((1+x)/2)+np.log((1+x**2)/2)-2*np.arctan(x)+2*np.arctan(1)
x = (1-10.15*zet[k])**0.3333 x = (1-10.15*zol[k])**0.3333
psic = (1.5*np.log((1+x+x**2)/3)-np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) + psic = (1.5*np.log((1+x+x**2)/3)-np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) +
4*np.arctan(1)/np.sqrt(3)) 4*np.arctan(1)/np.sqrt(3))
f = zet[k]**2/(1+zet[k]**2) f = zol[k]**2/(1+zol[k]**2)
psi[k] = (1-f)*psik+f*psic psi[k] = (1-f)*psik+f*psic
return psi return psi
# ------------------------------------------------------------------------------ # ------------------------------------------------------------------------------
def psiu_40(zet): def psiu_40(zol):
""" """ Computes velocity structure function C35
computes velocity structure function COARE3.5
Parameters
----------
zol : float
height over MO length
Returns
-------
psi : float
""" """
dzet = np.where(0.35*zet > 50, 50, 0.35*zet) # stable dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable
a, b, c, d = 1, 3/4, 5, 0.35 a, b, c, d = 1, 3/4, 5, 0.35
psi = -(a*zet+b*(zet-c/d)*np.exp(-dzet)+b*c/d) psi = -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d)
k = np.where(zet < 0) # unstable k = np.where(zol < 0) # unstable
x = (1-18*zet[k])**0.25 x = (1-18*zol[k])**0.25
psik = 2*np.log((1+x)/2)+np.log((1+x**2)/2)-2*np.arctan(x)+2*np.arctan(1) psik = 2*np.log((1+x)/2)+np.log((1+x**2)/2)-2*np.arctan(x)+2*np.arctan(1)
x = (1-10*zet[k])**0.3333 x = (1-10*zol[k])**0.3333
psic = (1.5*np.log((1+x+x**2)/3)-np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) + psic = (1.5*np.log((1+x+x**2)/3)-np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) +
4*np.arctan(1)/np.sqrt(3)) 4*np.arctan(1)/np.sqrt(3))
f = zet[k]**2/(1+zet[k]**2) f = zol[k]**2/(1+zol[k]**2)
psi[k] = (1-f)*psik+f*psic psi[k] = (1-f)*psik+f*psic
return psi return psi
# --------------------------------------------------------------------- # ---------------------------------------------------------------------
def get_skin(sst, qsea, rho, jcool, Rl, Rs, Rnl, cp, lv, usr, tsr, qsr, lat): def get_skin(sst, qsea, rho, Rl, Rs, Rnl, cp, lv, usr, tsr, qsr, lat):
""" Computes cool skin
Parameters
----------
sst : float
sea surface temperature ($^\circ$\,C)
qsea : float
specific humidity over sea (g/kg)
rho : float
density of air (kg/m^3)
Rl : float
downward longwave radiation (W/m^2)
Rs : float
downward shortwave radiation (W/m^2)
cp : float
specific heat of air at constant pressure
lv : float
latent heat of vaporization
usr : float
friction velocity
tsr : float
star temperature
qsr : float
star humidity
lat : float
latitude
Returns
-------
dter : float
dqer : float
"""
# coded following Saunders (1967) with lambda = 6 # coded following Saunders (1967) with lambda = 6
g = gc(lat, None) g = gc(lat, None)
if (np.nanmin(sst) > 200): # if Ta in Kelvin convert to Celsius if (np.nanmin(sst) > 200): # if Ta in Kelvin convert to Celsius
...@@ -397,6 +785,27 @@ def get_skin(sst, qsea, rho, jcool, Rl, Rs, Rnl, cp, lv, usr, tsr, qsr, lat): ...@@ -397,6 +785,27 @@ def get_skin(sst, qsea, rho, jcool, Rl, Rs, Rnl, cp, lv, usr, tsr, qsr, lat):
def get_gust(beta, Ta, usr, tsrv, zi, lat): def get_gust(beta, Ta, usr, tsrv, zi, lat):
""" Computes 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)
lat : float
latitude
Returns
-------
ug : float
"""
if (np.max(Ta) < 200): # convert to K if in Celsius if (np.max(Ta) < 200): # convert to K if in Celsius
Ta = Ta+273.16 Ta = Ta+273.16
if np.isnan(zi): if np.isnan(zi):
...@@ -410,6 +819,17 @@ def get_gust(beta, Ta, usr, tsrv, zi, lat): ...@@ -410,6 +819,17 @@ def get_gust(beta, Ta, usr, tsrv, zi, lat):
def get_heights(h): def get_heights(h):
""" Reads input heights for velocity, temperature and humidity
Parameters
----------
h : float
input heights (m)
Returns
-------
hh : array
"""
hh = np.zeros(3) hh = np.zeros(3)
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
...@@ -422,10 +842,17 @@ def get_heights(h): ...@@ -422,10 +842,17 @@ def get_heights(h):
def svp_calc(T): def svp_calc(T):
""" """ Calculates saturation vapour pressure
calculates saturation vapour pressure
T is in Kelvin Parameters
svp in mb, pure water ----------
T : float
temperature (K)
Returns
-------
svp : float
in mb, pure water
""" """
if (np.nanmin(T) < 200): # if T in Celsius convert to Kelvin if (np.nanmin(T) < 200): # if T in Celsius convert to Kelvin
T = T+273.16 T = T+273.16
...@@ -435,10 +862,19 @@ def svp_calc(T): ...@@ -435,10 +862,19 @@ def svp_calc(T):
def qsea_calc(sst, pres): def qsea_calc(sst, pres):
""" """ Computes specific humidity of the sea surface air
sst in Kelvin
pres in mb Parameters
qsea in kg/kg ----------
sst : float
sea surface temperature (K)
pres : float
pressure (mb)
Returns
-------
qsea : float
(kg/kg)
""" """
if (np.nanmin(sst) < 200): # if sst in Celsius convert to Kelvin if (np.nanmin(sst) < 200): # if sst in Celsius convert to Kelvin
sst = sst+273.16 sst = sst+273.16
...@@ -451,11 +887,20 @@ def qsea_calc(sst, pres): ...@@ -451,11 +887,20 @@ def qsea_calc(sst, pres):
def q_calc(Ta, rh, pres): def q_calc(Ta, rh, pres):
""" """ Computes specific humidity following Haltiner and Martin p.24
rh in %
air in K, if not it will be converted to K Parameters
pres in mb ----------
qair in kg/kg, as in Haltiner and Martin p.24 Ta : float
air temperature (K)
rh : float
relative humidity (%)
pres : float
air pressure (mb)
Returns
-------
qair : float, (kg/kg)
""" """
if (np.nanmin(Ta) < 200): # if sst in Celsius convert to Kelvin if (np.nanmin(Ta) < 200): # if sst in Celsius convert to Kelvin
Ta = Ta+273.15 Ta = Ta+273.15
...@@ -466,9 +911,18 @@ def q_calc(Ta, rh, pres): ...@@ -466,9 +911,18 @@ def q_calc(Ta, rh, pres):
def bucksat(T, P): def bucksat(T, P):
""" """ Computes saturation vapor pressure (mb) as in C35
computes saturation vapor pressure [mb] as in COARE3.5
given T [degC] and P [mb] Parameters
----------
T : float
temperature ($^\\circ$\\,C)
P : float
pressure (mb)
Returns
-------
exx : float
""" """
T = np.asarray(T) T = np.asarray(T)
if (np.nanmin(T) > 200): # if Ta in Kelvin convert to Celsius if (np.nanmin(T) > 200): # if Ta in Kelvin convert to Celsius
...@@ -479,9 +933,18 @@ def bucksat(T, P): ...@@ -479,9 +933,18 @@ def bucksat(T, P):
def qsat26sea(T, P): def qsat26sea(T, P):
""" """ Computes surface saturation specific humidity (g/kg) as in C35
computes surface saturation specific humidity [g/kg] as in COARE3.5
given T [degC] and P [mb] Parameters
----------
T : float
temperature ($^\\circ$\\,C)
P : float
pressure (mb)
Returns
-------
qs : float
""" """
T = np.asarray(T) T = np.asarray(T)
if (np.nanmin(T) > 200): # if Ta in Kelvin convert to Celsius if (np.nanmin(T) > 200): # if Ta in Kelvin convert to Celsius
...@@ -494,9 +957,19 @@ def qsat26sea(T, P): ...@@ -494,9 +957,19 @@ def qsat26sea(T, P):
def qsat26air(T, P, rh): def qsat26air(T, P, rh):
""" """ Computes saturation specific humidity (g/kg) as in C35
computes saturation specific humidity [g/kg] as in COARE3.5
given T [degC] and P [mb] Parameters
----------
T : float
temperature ($^\circ$\,C)
P : float
pressure (mb)
Returns
-------
q : float
em : float
""" """
T = np.asarray(T) T = np.asarray(T)
if (np.nanmin(T) > 200): # if Ta in Kelvin convert to Celsius if (np.nanmin(T) > 200): # if Ta in Kelvin convert to Celsius
...@@ -509,13 +982,19 @@ def qsat26air(T, P, rh): ...@@ -509,13 +982,19 @@ def qsat26air(T, P, rh):
def gc(lat, lon=None): def gc(lat, lon=None):
""" """ Computes gravity relative to latitude
computes gravity relative to latitude
inputs: Parameters
lat : latitudes in deg ----------
lon : longitudes (optional) lat : float
output: latitude ($^\circ$)
gc: gravity constant lon : float
longitude ($^\circ$, optional)
Returns
-------
gc : float
gravity constant (m/s^2)
""" """
gamma = 9.7803267715 gamma = 9.7803267715
c1 = 0.0052790414 c1 = 0.0052790414
...@@ -535,13 +1014,18 @@ def gc(lat, lon=None): ...@@ -535,13 +1014,18 @@ def gc(lat, lon=None):
def visc_air(Ta): def visc_air(Ta):
""" """ Computes the kinematic viscosity of dry air as a function of air temp.
Computes the kinematic viscosity of dry air as a function of air temp.
following Andreas (1989), CRREL Report 89-11. following Andreas (1989), CRREL Report 89-11.
input:
Ta : air temperature [Celsius] Parameters
output ----------
visa : kinematic viscosity [m^2/s] Ta : float
air temperature ($^\circ$\,C)
Returns
-------
visa : float
kinematic viscosity (m^2/s)
""" """
Ta = np.asarray(Ta) Ta = np.asarray(Ta)
if (np.nanmin(Ta) > 200): # if Ta in Kelvin convert to Celsius if (np.nanmin(Ta) > 200): # if Ta in Kelvin convert to Celsius
......
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