diff --git a/AirSeaFluxCode.py b/AirSeaFluxCode.py index b5cd378151da7291bb19078432c8ef95c8b0c3fa..62b29f713fcc662c5c37d5c9c68ba6c6d72633b6 100644 --- a/AirSeaFluxCode.py +++ b/AirSeaFluxCode.py @@ -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) -def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, - gust, meth, qmeth, n): +def AirSeaFluxCode(spd, T, SST, lat=None, hum=None, P=None, + 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 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 relative wind speed in m/s (is assumed as magnitude difference between wind and surface current vectors) @@ -28,28 +22,52 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, SST : float sea surface temperature in K (will convert if < 200) lat : float - latitude (deg) - RH : float - relative humidity in % + latitude (deg), default 45deg + hum : float + 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 - air pressure + air pressure (hPa), default 1013hPa 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 output height, default is 10m - zi : int - PBL height (m) Rl : float downward longwave radiation (W/m^2) Rs : float downward shortwave radiation (W/m^2) 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 - 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 - 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 ------- res : array that contains @@ -77,9 +95,9 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, 22. surface roughness length (zo) 23. heat roughness length (zot) 24. moisture roughness length (zoq) - 25. velocity at reference height (urefs) - 26. temperature at reference height (trefs) - 27. specific humidity at reference height (qrefs) + 25. velocity at reference height (uref) + 26. temperature at reference height (tref) + 27. specific humidity at reference height (qref) 28. number of iterations until convergence ind : int 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, ref_ht, tlapse = 10, 0.0098 # reference height, lapse rate h_in = get_heights(hin, len(spd)) # heights of input measurements/fields 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 (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") - logging.debug('all input is nan') - if (np.all(np.isnan(RH)) and meth == "C35"): - RH = np.ones(spd.shape)*80 # if empty set to default for COARE3.5 - elif (np.all(np.isnan(RH))): - sys.exit("input RH is empty") - logging.debug('input RH is empty') - if (np.all(np.isnan(P)) and (meth == "C30" or meth == "C40")): + logging.debug('all spd or T or SST input is nan') + if (np.all(lat) == None): # set latitude to 45deg if empty + lat = 45*np.ones(spd.shape) + elif ((np.all(lat) != None) and (np.size(lat) == 1)): + lat = np.ones(spd.shape)*np.copy(lat) + g = gc(lat, None) # acceleration due to gravity + 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 - elif ((np.all(np.isnan(P))) and meth == "C35"): - P = np.ones(spd.shape)*1013 # if empty set to default for COARE3.5 - elif (np.all(np.isnan(P))): - sys.exit("input P is empty") - logging.debug('input P is empty') - if (np.all(np.isnan(Rl)) and meth == "C30"): + elif ((np.all(P) == None) or np.all(np.isnan(P))): + P = np.ones(spd.shape)*1013 + logging.debug('input P is empty and set to 1013hPa') + elif (((np.all(P) != None) or np.all(~np.isnan(P))) and np.size(P) == 1): + P = np.ones(spd.shape)*np.copy(P) + 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 - elif ((np.all(np.isnan(Rl)) and meth == "C35") or - (np.all(np.isnan(Rl)) and meth == "C40")): + elif (((np.all(Rl) == None or np.all(np.isnan(Rl))) and meth == "C35") or + ((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 - 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 - 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 - 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 - if ((np.all(np.isnan(zi))) and (meth == "C30" or meth == "C35" or - meth == "C40")): - zi = 600 # set to default for COARE3.5 - elif ((np.all(np.isnan(zi))) and (meth == "ERA5" or meth == "UA")): - zi = 1000 - elif (np.all(np.isnan(zi))): - zi = 800 + if ((gust == None) and (meth == "C30" or meth == "C35" or meth == "C40")): + gust = [1, 1.2, 600] + elif ((gust == None) and (meth == "UA" or meth == "ERA5")): + gust = [1, 1, 1000] + elif (gust == None): + gust = [1, 1.2, 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) * 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, 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 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) - qair = qsat_air(T, P, RH, qmeth)/1000 # q of air (g/kg) - logging.info('method %s and q method %s | qsea:%s, qair:%s', meth, - qmeth, np.nanmedian(qsea), np.nanmedian(qair)) - else: - qsea = qsat_sea(sst, P, "Buck2")/1000 # surface water q (g/kg) - qair = qsat_air(T, P, RH, "Buck2")/1000 # q of air (g/kg) - logging.info('method %s | qsea:%s, qair:%s', meth, - np.nanmedian(qsea[~np.isnan(qsea)]), - np.nanmedian(qair[~np.isnan(qair)])) + elif (hum[0] == 2): + Td = hum[1] # dew point temperature (K) + Td = np.where(Td < 200, np.copy(Td)+CtoK, np.copy(Td)) + T = np.where(T < 200, np.copy(T)+CtoK, np.copy(T)) + esd = 611.21*np.exp(17.502*((Td-273.16)/(Td-32.19))) + es = 611.21*np.exp(17.502*((T-273.16)/(T-32.19))) + RH = 100*esd/es + qair = qsat_air(T, P, RH, qmeth)/1000 # q of air (g/kg) + 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))): 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 dt = Ta - sst dq = qair - qsea @@ -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) dter = np.ones(T.shape)*0.3 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), 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)) - elif (gust == 0): + elif (gust[0] == 0): wind = np.copy(spd) if (meth == "UA"): @@ -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) Rb = g*h_in[0]*dtv/(tv*wind**2) zol = np.where(Rb >= 0, Rb*np.log(h_in[0]/zo) / - (1-5*np.where(Rb < 0.19, Rb, 0.19)), - Rb*np.log(h_in[0]/zo)) + (1-5*np.where(Rb < 0.19, Rb, 0.19)), + Rb*np.log(h_in[0]/zo)) monob = h_in[0]/zol 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) zoq = zot logging.info('method %s | wind:%s, usr:%s, ' - 'zo:%s, zot:%s, zoq:%s, Rb:%s, monob:%s', meth, - np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), - np.nanmedian(zot), np.nanmedian(zoq), np.nanmedian(Rb), - np.nanmedian(monob)) + 'zo:%s, zot:%s, zoq:%s, Rb:%s, monob:%s', meth, + np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), + np.nanmedian(zot), np.nanmedian(zoq), np.nanmedian(Rb), + np.nanmedian(monob)) elif (meth == "ERA5"): usr = np.sqrt(cd*np.power(wind, 2)) Rb = ((g*h_in[0]*((2*dt)/(Ta+sst-g*h_in[0]) + 0.61*dq))/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 + zoq = 0.62*visc_air(Ta)/usr zol = (Rb*((np.log((h_in[0]+zo)/zo)-psim_calc((h_in[0]+zo) / - monob, meth)+psim_calc(zo/monob, meth)) / - (np.log((h_in[0]+zo)/zot) - - psit_calc((h_in[0]+zo)/monob, meth) + - psit_calc(zot/monob, meth)))) + monob, meth)+psim_calc(zo/monob, meth)) / + (np.log((h_in[0]+zo)/zot) - + psit_calc((h_in[0]+zo)/monob, meth) + + psit_calc(zot/monob, meth)))) monob = h_in[0]/zol logging.info('method %s | wind:%s, usr:%s, ' - 'zo:%s, zot:%s, Rb:%s, monob:%s', meth, - np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), - np.nanmedian(zot), np.nanmedian(Rb), np.nanmedian(monob)) + 'zo:%s, zot:%s, Rb:%s, monob:%s', meth, + np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), + np.nanmedian(zot), np.nanmedian(Rb), np.nanmedian(monob)) elif (meth == "C30" or meth == "C35" or meth == "C40"): usr = np.sqrt(cd*np.power(wind, 2)) a = 0.011*np.ones(T.shape) 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 rr = zo*usr/visc_air(T) 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)) zol = (Rb*((np.log((h_in[0]+zo)/zo)-psim_calc((h_in[0]+zo) / - monob, meth)+psim_calc(zo/monob, meth)) / - (np.log((h_in[0]+zo)/zot) - - psit_calc((h_in[0]+zo)/monob, meth) + - psit_calc(zot/monob, meth)))) + monob, meth)+psim_calc(zo/monob, meth)) / + (np.log((h_in[0]+zo)/zot) - + psit_calc((h_in[0]+zo)/monob, meth) + + psit_calc(zot/monob, meth)))) monob = h_in[0]/zol logging.info('method %s | wind:%s, usr:%s, ' 'zo:%s, zot:%s, Rb:%s, monob:%s', meth, np.nanmedian(wind), np.nanmedian(usr), np.nanmedian(zo), np.nanmedian(zot), np.nanmedian(Rb), np.nanmedian(monob)) 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)) logging.info('method %s | wind:%s, usr:%s, ' '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, np.nanmedian(zot), np.nanmedian(monob)) tsr = (dt+dter*jcool)*kappa/(np.log(hin[1]/zot) - 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)) - # 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) 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): it += 1 if it > n: break - old = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n), - np.copy(usr), np.copy(tsr), np.copy(qsr)]) + if (tol[0] == 'flux'): + 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) if (np.all(np.isnan(cdn))): break @@ -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]) ctn[ind], cqn[ind] = ctcqn_calc(h_in[1, ind]/monob[ind], cdn[ind], 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) 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], - h_in[1, ind], h_in[2, ind], ref_ht, - psit[ind], psiq[ind]) + h_in[1, ind], h_in[2, ind], ref_ht, + psit[ind], psiq[ind]) if (meth == "UA"): usr[ind] = np.where(h_in[0, ind]/monob[ind] < -1.574, kappa*wind[ind] / @@ -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]) - psiu_26(h_in[0, ind]/monob[ind], meth))) logging.info('method %s | dter = %s | Rnl = %s ' - '| usr = %s | tsr = %s | qsr = %s', meth, - np.nanmedian(dter), np.nanmedian(Rnl), - np.nanmedian(usr), np.nanmedian(tsr), - np.nanmedian(qsr)) + '| usr = %s | tsr = %s | qsr = %s', meth, + np.nanmedian(dter), np.nanmedian(Rnl), + np.nanmedian(usr), np.nanmedian(tsr), + np.nanmedian(qsr)) qsr[ind] = ((dq[ind]+dqer[ind]*jcool)*(kappa/(np.log(hin[2, ind] / zoq[ind])-psit_26(hin[2, ind]/monob[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, psim_calc(h_in[0, ind]/monob[ind], meth))) 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] - dter[ind], dqer[ind], tkt[ind] = get_skin(sst[ind], qsea[ind], - rho[ind], Rl[ind], - Rs[ind], Rnl[ind], - cp[ind], lv[ind], - np.copy(tkt[ind]), - usr[ind], tsr[ind], - qsr[ind], lat[ind]) + if (jcool == 1): + dter[ind], dqer[ind], tkt[ind] = get_skin(sst[ind], qsea[ind], + rho[ind], Rl[ind], + Rs[ind], Rnl[ind], + cp[ind], lv[ind], + np.copy(tkt[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] - - dter[ind]*jcool+CtoK, 4)-Rl[ind]) - t10n[ind] = (Ta[ind] + - (tsr[ind]*(np.log(h_in[1, ind]/ref_ht)-psit[ind])/kappa)) - q10n[ind] = (qair[ind] + - (qsr[ind]*(np.log(h_in[2, ind]/ref_ht)-psiq[ind])/kappa)) + dter[ind]*jcool+CtoK, 4)-Rl[ind]) + t10n[ind] = (Ta[ind] - + tsr[ind]/kappa*(np.log(h_in[1, ind]/ref_ht)-psit[ind])) + q10n[ind] = (qair[ind] - + qsr[ind]/kappa*(np.log(h_in[2, ind]/ref_ht)-psiq[ind])) tv10n[ind] = t10n[ind]*(1+0.61*q10n[ind]) if (meth == "UA"): 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, elif (meth == "ERA5"): tsrv[ind] = tsr[ind]+0.61*t10n[ind]*qsr[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])) / - np.power(wind[ind], 2)) + sst[ind]-g[ind]*h_in[0, ind])+0.61*dq[ind])) / + np.power(wind[ind], 2)) zo[ind] = (0.11*visc_air(Ta[ind])/usr[ind]+0.018 * np.power(usr[ind], 2)/g[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]) - psim_calc((h_in[0, ind]+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, psim[ind] = psim_calc(h_in[0, 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) - 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, - spd[ind], 0.1), - np.sqrt(np.power(np.copy(spd[ind]), 2) + - np.power(get_gust(1, tv[ind], usr[ind], - tsrv[ind], zi, lat[ind]), 2))) - # Zeng et al. 1998 (20) - elif (gust == 1 and (meth == "C30" or meth == "C35" or meth == "C40")): + spd[ind], 0.1), + np.sqrt(np.power(np.copy(spd[ind]), 2) + + np.power(get_gust(gust[1], tv[ind], usr[ind], + tsrv[ind], gust[2], lat[ind]), 2))) + # Zeng et al. 1998 (20) + elif (gust[0] == 1 and (meth == "C30" or meth == "C35" or + meth == "C40")): wind[ind] = np.sqrt(np.power(np.copy(spd[ind]), 2) + - np.power(get_gust(1.2, Ta[ind], usr[ind], - tsrv[ind], zi, lat[ind]), 2)) - elif (gust == 1): + np.power(get_gust(gust[1], Ta[ind], usr[ind], + tsrv[ind], gust[2], lat[ind]), 2)) + elif (gust[0] == 1): 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)) - elif (gust == 0): + np.power(get_gust(gust[1], Ta[ind], usr[ind], + tsrv[ind], gust[2], lat[ind]), 2)) + elif (gust[0] == 0): wind[ind] = np.copy(spd[ind]) - if (meth == "UA"): - u10n[ind] = (wind[ind]-(usr[ind]/kappa)*(np.log(h_in[0, ind]/10) - - psim[ind])) - 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 + 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 - new = np.array([np.copy(u10n), np.copy(t10n), np.copy(q10n), - np.copy(usr), np.copy(tsr), np.copy(qsr)]) - 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])) + sensible = -rho*cp*usr*tsr + latent = -rho*lv*usr*qsr + if (gust[0] == 1): + tau = rho*np.power(usr, 2)*(spd/wind) + elif (gust[0] == 0): + 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): ii = False else: @@ -460,22 +518,16 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, # calculate output parameters rho = (0.34838*P)/(tv10n) t10n = t10n-(273.16+tlapse*ref_ht) - sensible = -rho*cp*usr*tsr - latent = -rho*lv*usr*qsr - if (gust == 1): - tau = rho*np.power(usr, 2)*(spd/wind) - elif (gust == 0): - tau = rho*np.power(usr, 2) - zo = ref_ht/np.exp(kappa/cdn**0.5) - 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)))) - urefs = (spd-(usr/kappa)*(np.log(h_in[0]/h_out[0])-psim + - 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))) + # zo = ref_ht/np.exp(kappa/cdn**0.5) + # 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)))) + uref = (spd-usr/kappa*(np.log(h_in[0]/h_out[0])-psim + + psim_calc(h_out[0]/monob, meth))) + tref = (Ta-tsr/kappa*(np.log(h_in[1]/h_out[1])-psit + + psit_calc(h_out[0]/monob, meth))) + tref = tref-(273.16+tlapse*h_out[1]) + qref = (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[0][:] = tau res[1][:] = sensible @@ -501,8 +553,13 @@ def AirSeaFluxCode(spd, T, SST, lat, RH, P, hin, hout, zi, Rl, Rs, jcool, res[21][:] = zo res[22][:] = zot res[23][:] = zoq - res[24][:] = urefs - res[25][:] = trefs - res[26][:] = qrefs + res[24][:] = uref + res[25][:] = tref + res[26][:] = qref 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 + diff --git a/flux_subs.py b/flux_subs.py index 4a149d71139419a006d2511972e5aa51d300cd3d..a8054434c4855b7fe1d754b864131586a900f609 100755 --- a/flux_subs.py +++ b/flux_subs.py @@ -2,11 +2,39 @@ import numpy as np from VaporPressure import VaporPressure 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 """ 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"): @@ -20,6 +48,8 @@ def cdn_calc(u10n, Ta, Tp, lat, meth="S80"): air temperature (K) Tp : float wave period + lat : float + latitude meth : str Returns @@ -63,6 +93,8 @@ def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"): air temperature (K) Tp : float wave period + lat : float + latitude meth : str Returns @@ -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 elif (meth == "C35"): a = 0.011*np.ones(Ta.shape) -# a = np.where(u10n > 19, 0.0017*19-0.0050, -# np.where((u10n > 7) & (u10n <= 18), -# 0.0017*u10n-0.0050, a)) + # a = np.where(u10n > 19, 0.0017*19-0.0050, + # np.where((u10n > 7) & (u10n <= 18), + # 0.0017*u10n-0.0050, a)) 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 elif (meth == "C40"): @@ -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) zo = a*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr # surface roughness 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 else: print("unknown method for cdn_from_roughness "+meth) cdnn = (kappa/np.log(10/zo))**2 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"): zol : float height over MO length cdn : float - neatral drag coefficient + neutral drag coefficient u10n : float neutral 10m wind speed (m/s) zo : float @@ -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) ctn = kappa**2/np.log(10/zo)/np.log(10/zot) 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)) zot = 0.40*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"): # --------------------------------------------------------------------- -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 -# --------------------------------------------------------------------- - - -def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq): +def ctcq_calc(cdn, cd, ctn, cqn, ht, hq, ref_ht, psit, psiq): """ Calculates heat and moisture exchange coefficients at reference height Parameters @@ -249,12 +281,14 @@ def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq): Returns ------- ct : float + heat exchange coefficient cq : float + moisture exchange coefficient """ 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) / - (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 # --------------------------------------------------------------------- @@ -272,16 +306,13 @@ def psim_calc(zol, meth="S80"): ------- psim : float """ - coeffs = get_stabco(meth) - alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2] if (meth == "ERA5"): - psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma), - psim_stab_era5(zol, alpha, beta, gamma)) + psim = psim_era5(zol) elif (meth == "C30" or meth == "C35" or meth == "C40"): psim = psiu_26(zol, meth) else: - psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma), - psim_stab(zol, alpha, beta, gamma)) + psim = np.where(zol < 0, psim_conv(zol, meth), + psim_stab(zol, meth)) return psim # --------------------------------------------------------------------- @@ -294,55 +325,25 @@ def psit_calc(zol, meth="S80"): zol : float height over MO length meth : str + parameterisation method Returns ------- psit : float """ - coeffs = get_stabco(meth) - alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2] if (meth == "ERA5"): - psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma), - psi_stab_era5(zol, alpha, beta, gamma)) + psit = np.where(zol < 0, psi_conv(zol, meth), + psi_era5(zol)) elif (meth == "C30" or meth == "C35" or meth == "C40"): psit = psit_26(zol) else: - psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma), - psi_stab(zol, alpha, beta, gamma)) + psit = np.where(zol < 0, psi_conv(zol, meth), + psi_stab(zol, meth)) return psit # --------------------------------------------------------------------- -def get_stabco(meth="S80"): - """ 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): +def psi_era5(zol): """ Calculates heat stability function for stable conditions for method ERA5 @@ -350,14 +351,12 @@ def psi_stab_era5(zol, alpha, beta, gamma): ---------- 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. 37 IFS Documentation cy46r1 a, b, c, d = 1, 2/3, 5, 0.35 psit = -b*(zol-c/d)*np.exp(-d*zol)-np.power(1+(2/3)*a*zol, 1.5)-(b*c)/d+1 return psit @@ -377,94 +376,103 @@ def psit_26(zol): psi : float """ b, d = 2/3, 0.35 - dzol = np.where(d*zol > 50, 50, d*zol) # stable - psi = -((1+b*zol)**1.5+b*(zol-14.28)*np.exp(-dzol)+8.525) - psik = 2*np.log((1+np.sqrt(1-15*zol))/2) - psic = (1.5*np.log((1+np.power(1-34.15*zol, 1/3) + - np.power(1-34.15*zol, 2/3))/3)-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)) + dzol = np.where(d*zol > 50, 50, d*zol) + psi = np.where(zol > 0,-(np.power(1+b*zol, 1.5)+b*(zol-14.28) * + np.exp(-dzol)+8.525), np.nan) + psik = np.where(zol < 0, 2*np.log((1+np.sqrt(1-15*zol))/2), np.nan) + psic = np.where(zol < 0, 1.5*np.log((1+np.power(1-34.15*zol, 1/3) + + np.power(1-34.15*zol, 2/3))/3)-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)) psi = np.where(zol < 0, (1-f)*psik+f*psic, psi) return psi # --------------------------------------------------------------------- -def psi_conv(zol, alpha, beta, gamma): +def psi_conv(zol, meth): """ Calculates heat stability function for unstable conditions Parameters ---------- zol : float height over MO length - alpha, beta, gamma : float - constants given by get_stabco + meth : str + parameterisation method Returns ------- psit : float """ + coeffs = get_stabco(meth) + alpha, beta = coeffs[0], coeffs[1] xtmp = np.power(1-alpha*zol, beta) psit = 2*np.log((1+np.power(xtmp, 2))*0.5) return psit # --------------------------------------------------------------------- -def psi_stab(zol, alpha, beta, gamma): +def psi_stab(zol, meth): """ Calculates heat stability function for stable conditions Parameters ---------- zol : float height over MO length - alpha, beta, gamma : float - constants given by get_stabco + meth : str + parameterisation method Returns ------- psit : float """ + coeffs = get_stabco(meth) + gamma = coeffs[2] psit = -gamma*zol return psit # --------------------------------------------------------------------- -def psim_stab_era5(zol, alpha, beta, gamma): - """ Calculates momentum stability function for stable conditions - for method ERA5 +def psim_era5(zol): + """ Calculates momentum stability function 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.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 - 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 # --------------------------------------------------------------------- -def psim_conv(zol, alpha, beta, gamma): +def psim_conv(zol, meth): """ Calculates momentum stability function for unstable conditions Parameters ---------- zol : float height over MO length - alpha, beta, gamma : float - constants given by get_stabco + meth : str + parameterisation method Returns ------- psim : float """ + coeffs = get_stabco(meth) + alpha, beta = coeffs[0], coeffs[1] xtmp = np.power(1-alpha*zol, beta) psim = (2*np.log((1+xtmp)*0.5)+np.log((1+np.power(xtmp, 2))*0.5) - 2*np.arctan(xtmp)+np.pi/2) @@ -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 Parameters ---------- zol : float height over MO length - alpha, beta, gamma : float - constants given by get_stabco + meth : str + parameterisation method Returns ------- psim : float """ + coeffs = get_stabco(meth) + gamma = coeffs[2] psim = -gamma*zol return psim # --------------------------------------------------------------------- @@ -505,28 +515,31 @@ def psiu_26(zol, meth): """ if (meth == "C30"): 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) - k = np.where(zol < 0) # unstable - x = np.power(1-15*zol[k], 0.25) - psik = (2*np.log((1+x)/2)+np.log((1+np.power(x, 2))/2)-2*np.arctan(x) + - 2*np.arctan(1)) - x = np.power(1-10.15*zol[k], 0.3333) - psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) * - np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3)) - f = np.power(zol[k], 2)/(1+np.power(zol[k], 2)) - psi[k] = (1-f)*psik+f*psic + psi = np.where(zol > 0, -((1+zol)+0.6667*(zol-14.28)*np.exp(-dzol) + + 8.525), np.nan) + x = np.where(zol < 0, np.power(1-15*zol, 0.25), np.nan) + psik = np.where(zol < 0, 2*np.log((1+x)/2)+np.log((1+np.power(x, 2)) / + 2)-2*np.arctan(x)+2*np.arctan(1), np.nan) + x = np.where(zol < 0, np.power(1-10.15*zol, 0.3333), np.nan) + psic = np.where(zol < 0, 1.5*np.log((1+x+np.power(x, 2))/3) - + np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) + + 4*np.arctan(1)/np.sqrt(3), np.nan) + 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"): dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable a, b, c, d = 0.7, 3/4, 5, 0.35 - psi = -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d) - k = np.where(zol < 0) # unstable - x = np.power(1-15*zol[k], 0.25) - psik = 2*np.log((1+x)/2)+np.log((1+x**2)/2)-2*np.arctan(x)+2*np.arctan(1) - x = np.power(1-10.15*zol[k], 0.3333) - psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) * - np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3)) - f = np.power(zol[k], 2)/(1+np.power(zol[k], 2)) - psi[k] = (1-f)*psik+f*psic + psi = np.where(zol > 0, -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d), + np.nan) + x = np.where(zol < 0, np.power(1-15*zol, 0.25), np.nan) + psik = np.where(zol < 0, 2*np.log((1+x)/2)+np.log((1+x**2)/2) - + 2*np.arctan(x)+2*np.arctan(1), np.nan) + x = np.where(zol < 0, np.power(1-10.15*zol, 0.3333), np.nan) + psic = np.where(zol < 0, 1.5*np.log((1+x+np.power(x, 2))/3) - + np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) + + 4*np.arctan(1)/np.sqrt(3), 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 # --------------------------------------------------------------------- @@ -638,6 +651,8 @@ def get_heights(h, dim_len): ---------- h : float input heights (m) + dim_len : int + length dimension Returns ------- @@ -646,17 +661,17 @@ def get_heights(h, dim_len): hh = np.zeros((3, dim_len)) if (type(h) == float or type(h) == int): 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] - 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] - 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[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[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.copy(h) return hh