flux_subs.py 22.9 KB
Newer Older
sbiri's avatar
sbiri committed
1
import numpy as np
2
from VaporPressure import VaporPressure
sbiri's avatar
sbiri committed
3 4

CtoK = 273.16  # 273.15
sbiri's avatar
sbiri committed
5 6
""" Conversion factor for (^\circ\,C) to (^\\circ\\,K) """

sbiri's avatar
sbiri committed
7
kappa = 0.4  # NOTE: 0.41
sbiri's avatar
sbiri committed
8
""" von Karman's constant """
sbiri's avatar
sbiri committed
9
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
10

sbiri's avatar
sbiri committed
11

sbiri's avatar
sbiri committed
12
def cdn_calc(u10n, Ta, Tp, lat, meth="S80"):
sbiri's avatar
sbiri committed
13
    """ Calculates neutral drag coefficient
sbiri's avatar
sbiri committed
14

sbiri's avatar
sbiri committed
15 16 17 18 19 20 21 22
    Parameters
    ----------
    u10n : float
        neutral 10m wind speed (m/s)
    Ta   : float
        air temperature (K)
    Tp   : float
        wave period
sbiri's avatar
sbiri committed
23 24
    meth : str

sbiri's avatar
sbiri committed
25 26 27 28
    Returns
    -------
    cdn : float
    """
29
    cdn = np.zeros(Ta.shape)*np.nan
sbiri's avatar
sbiri committed
30
    if (meth == "S80"):
sbiri's avatar
sbiri committed
31 32
        cdn = np.where(u10n <= 3, (0.61+0.567/u10n)*0.001,
                       (0.61+0.063*u10n)*0.001)
sbiri's avatar
sbiri committed
33
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
34 35 36
        cdn = np.where((u10n < 11) & (u10n >= 4), 1.2*0.001,
                       np.where((u10n <= 25) & (u10n >= 11),
                       (0.49+0.065*u10n)*0.001, 1.14*0.001))
sbiri's avatar
sbiri committed
37 38 39 40
    elif (meth == "S88" or meth == "UA" or meth == "ERA5" or meth == "C30" or
          meth == "C35" or meth == "C40"):
        cdn = cdn_from_roughness(u10n, Ta, None, lat, meth)
    elif (meth == "YT96"):
sbiri's avatar
sbiri committed
41
        # for u<3 same as S80
sbiri's avatar
sbiri committed
42 43 44 45
        cdn = np.where((u10n < 6) & (u10n >= 3),
                       (0.29+3.1/u10n+7.7/u10n**2)*0.001,
                       np.where((u10n <= 26) & (u10n >= 6),
                       (0.60 + 0.070*u10n)*0.001, (0.61+0.567/u10n)*0.001))
sbiri's avatar
sbiri committed
46
    elif (meth == "LY04"):
sbiri's avatar
sbiri committed
47 48
        cdn = np.where(u10n >= 0.5,
                       (0.142+(2.7/u10n)+(u10n/13.09))*0.001, np.nan)
sbiri's avatar
sbiri committed
49
    else:
sbiri's avatar
sbiri committed
50
        print("unknown method cdn: "+meth)
sbiri's avatar
sbiri committed
51
    return cdn
sbiri's avatar
sbiri committed
52 53 54
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
55
def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"):
sbiri's avatar
sbiri committed
56
    """ Calculates neutral drag coefficient from roughness length
sbiri's avatar
sbiri committed
57

sbiri's avatar
sbiri committed
58 59 60 61 62 63 64 65
    Parameters
    ----------
    u10n : float
        neutral 10m wind speed (m/s)
    Ta   : float
        air temperature (K)
    Tp   : float
        wave period
sbiri's avatar
sbiri committed
66 67
    meth : str

sbiri's avatar
sbiri committed
68 69 70 71
    Returns
    -------
    cdn : float
    """
sbiri's avatar
sbiri committed
72
    g, tol = gc(lat, None), 0.000001
sbiri's avatar
sbiri committed
73 74 75
    cdn, usr = np.zeros(Ta.shape), np.zeros(Ta.shape)
    cdnn = (0.61+0.063*u10n)*0.001
    zo, zc, zs = np.zeros(Ta.shape), np.zeros(Ta.shape), np.zeros(Ta.shape)
sbiri's avatar
sbiri committed
76 77
    for it in range(5):
        cdn = np.copy(cdnn)
sbiri's avatar
sbiri committed
78
        usr = np.sqrt(cdn*u10n**2)
sbiri's avatar
sbiri committed
79
        if (meth == "S88"):
80
            # Charnock roughness length (eq. 4 in Smith 88)
sbiri's avatar
sbiri committed
81
            zc = 0.011*np.power(usr, 2)/g
82
            #  smooth surface roughness length (eq. 6 in Smith 88)
sbiri's avatar
sbiri committed
83
            zs = 0.11*visc_air(Ta)/usr
84
            zo = zc + zs  #  eq. 7 & 8 in Smith 88
sbiri's avatar
sbiri committed
85
        elif (meth == "UA"):
sbiri's avatar
sbiri committed
86 87
            # valid for 0<u<18m/s # Zeng et al. 1998 (24)
            zo = 0.013*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr
sbiri's avatar
sbiri committed
88 89 90 91 92 93 94
        elif (meth == "C30"):
            a = 0.011*np.ones(Ta.shape)
            a = np.where(u10n > 10, 0.011+(u10n-10)/(18-10)*(0.018-0.011),
                         np.where(u10n > 18, 0.018, a))
            zo = a*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr
        elif (meth == "C35"):
            a = 0.011*np.ones(Ta.shape)
95 96 97 98
#            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)
sbiri's avatar
sbiri committed
99 100 101 102 103 104
            zo = 0.11*visc_air(Ta)/usr+a*np.power(usr, 2)/g
        elif (meth == "C40"):
            a = 0.011*np.ones(Ta.shape)
            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"):
sbiri's avatar
sbiri committed
105
            # eq. (3.26) p.40 over sea IFS Documentation cy46r1
106
            zo = 0.018*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr
sbiri's avatar
sbiri committed
107
        else:
sbiri's avatar
sbiri committed
108
            print("unknown method for cdn_from_roughness "+meth)
sbiri's avatar
sbiri committed
109
        cdnn = (kappa/np.log(10/zo))**2
sbiri's avatar
sbiri committed
110
    cdn = np.where(np.abs(cdnn-cdn) < tol, cdnn, np.nan)
sbiri's avatar
sbiri committed
111
    return cdnn
sbiri's avatar
sbiri committed
112
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
113

sbiri's avatar
sbiri committed
114

sbiri's avatar
sbiri committed
115
def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"):
sbiri's avatar
sbiri committed
116
    """ Calculates neutral heat and moisture exchange coefficients
sbiri's avatar
sbiri committed
117

sbiri's avatar
sbiri committed
118 119 120 121 122 123 124 125 126 127 128 129
    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)
sbiri's avatar
sbiri committed
130 131
    meth : str

sbiri's avatar
sbiri committed
132 133 134 135 136 137 138
    Returns
    -------
    ctn : float
        neutral heat exchange coefficient
    cqn : float
        neutral moisture exchange coefficient
    """
sbiri's avatar
sbiri committed
139
    if (meth == "S80" or meth == "S88" or meth == "YT96"):
sbiri's avatar
sbiri committed
140
        cqn = np.ones(Ta.shape)*1.20*0.001  # from S88
sbiri's avatar
sbiri committed
141
        ctn = np.ones(Ta.shape)*1.00*0.001
sbiri's avatar
sbiri committed
142
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
143
        cqn = np.where((zol <= 0) & (u10n > 4) & (u10n < 14), 1.15*0.001,
144
                       1*0.001)
sbiri's avatar
sbiri committed
145 146
        ctn = np.where((zol <= 0) & (u10n > 4) & (u10n < 25), 1.13*0.001,
                       0.66*0.001)
sbiri's avatar
sbiri committed
147 148 149 150 151
    elif (meth == "LY04"):
        cqn = 34.6*0.001*np.sqrt(cdn)
        ctn = np.where(zol <= 0, 32.7*0.001*np.sqrt(cdn), 18*0.001*np.sqrt(cdn))
    elif (meth == "UA"):
        usr = np.sqrt(cdn*np.power(u10n, 2))
sbiri's avatar
sbiri committed
152
        # Zeng et al. 1998 (25)
sbiri's avatar
sbiri committed
153 154
        re=usr*zo/visc_air(Ta)
        zoq = zo/np.exp(2.67*np.power(re, 1/4)-2.57)
sbiri's avatar
sbiri committed
155 156 157 158 159
        zot = zoq
        cqn = np.where((u10n > 0.5) & (u10n < 18), np.power(kappa, 2) /
                       (np.log(10/zo)*np.log(10/zoq)), np.nan)
        ctn = np.where((u10n > 0.5) & (u10n < 18), np.power(kappa, 2) /
                       (np.log(10/zo)*np.log(10/zoq)), np.nan)
sbiri's avatar
sbiri committed
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189
    elif (meth == "C30"):
        usr = np.sqrt(cdn*np.power(u10n, 2))
        rr = zo*usr/visc_air(Ta)
        zoq = np.where(5e-5/np.power(rr, 0.6) > 1.15e-4, 1.15e-4,
                       5e-5/np.power(rr, 0.6))  # moisture roughness
        zot=zoq  # temperature roughness
        cqn = kappa**2/np.log(10/zo)/np.log(10/zoq)
        ctn = kappa**2/np.log(10/zo)/np.log(10/zot)
    elif (meth == "C35"):
        usr = np.sqrt(cdn*np.power(u10n, 2))
        rr = zo*usr/visc_air(Ta)
        zoq = np.where(5.8e-5/np.power(rr, 0.72) > 1.6e-4, 1.6e-4,
                       5.8e-5/np.power(rr, 0.72))  # moisture roughness
        zot=zoq  # temperature roughness
        cqn = kappa**2/np.log(10/zo)/np.log(10/zoq)
        ctn = kappa**2/np.log(10/zo)/np.log(10/zot)
    elif (meth == "C40"):
        usr = np.sqrt(cdn*np.power(u10n, 2))
        rr = zo*usr/visc_air(Ta)
        zot = np.where(1.0e-4/np.power(rr, 0.55) > 2.4e-4/np.power(rr, 1.2),
                       2.4e-4/np.power(rr, 1.2),
                       1.0e-4/np.power(rr, 0.55)) # temperature roughness
        zoq = np.where(2.0e-5/np.power(rr,0.22) > 1.1e-4/np.power(rr,0.9),
                       1.1e-4/np.power(rr,0.9), 2.0e-5/np.power(rr,0.22))
        # moisture roughness determined by the CLIMODE, GASEX and CBLAST data
#        zoq = np.where(5e-5/np.power(rr, 0.6) > 1.15e-4, 1.15e-4,
#                       5e-5/np.power(rr, 0.6))  # moisture roughness as in C30
        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"):
sbiri's avatar
sbiri committed
190 191 192 193 194 195
        # eq. (3.26) p.40 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
        cqn = kappa**2/np.log(10/zo)/np.log(10/zoq)
        ctn = kappa**2/np.log(10/zo)/np.log(10/zot)
sbiri's avatar
sbiri committed
196
    else:
sbiri's avatar
sbiri committed
197
        print("unknown method ctcqn: "+meth)
sbiri's avatar
sbiri committed
198
    return ctn, cqn
sbiri's avatar
sbiri committed
199 200 201
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
202
def cd_calc(cdn, height, ref_ht, psim):
sbiri's avatar
sbiri committed
203
    """ Calculates drag coefficient at reference height
sbiri's avatar
sbiri committed
204

sbiri's avatar
sbiri committed
205 206 207 208 209 210 211 212 213 214
    Parameters
    ----------
    cdn : float
        neutral drag coefficient
    height : float
        original sensor height (m)
    ref_ht : float
        reference height (m)
    psim : float
        momentum stability function
sbiri's avatar
sbiri committed
215

sbiri's avatar
sbiri committed
216 217 218 219
    Returns
    -------
    cd : float
    """
220
    cd = (cdn/np.power(1+(np.sqrt(cdn)*(np.log(height/ref_ht)-psim))/kappa, 2))
sbiri's avatar
sbiri committed
221
    return cd
sbiri's avatar
sbiri committed
222 223 224
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
225
def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq):
sbiri's avatar
sbiri committed
226
    """ Calculates heat and moisture exchange coefficients at reference height
sbiri's avatar
sbiri committed
227

sbiri's avatar
sbiri committed
228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
    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
sbiri's avatar
sbiri committed
248

sbiri's avatar
sbiri committed
249 250 251 252 253
    Returns
    -------
    ct : float
    cq : float
    """
254 255 256 257
    ct = (ctn*np.sqrt(cd/cdn) /
          (1+ctn*((np.log(h_t/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)))))
sbiri's avatar
sbiri committed
258
    return ct, cq
sbiri's avatar
sbiri committed
259 260 261
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
262
def psim_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
263
    """ Calculates momentum stability function
sbiri's avatar
sbiri committed
264

sbiri's avatar
sbiri committed
265 266 267 268
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
269 270
    meth : str

sbiri's avatar
sbiri committed
271 272 273 274
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
275
    coeffs = get_stabco(meth)
sbiri's avatar
sbiri committed
276
    alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
sbiri's avatar
sbiri committed
277
    if (meth == "ERA5"):
sbiri's avatar
sbiri committed
278 279
        psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma),
                        psim_stab_era5(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
280 281
    elif (meth == "C30" or meth == "C35" or meth == "C40"):
        psim = psiu_26(zol, meth)
sbiri's avatar
sbiri committed
282
    else:
sbiri's avatar
sbiri committed
283 284
        psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma),
                        psim_stab(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
285
    return psim
sbiri's avatar
sbiri committed
286 287 288
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
289
def psit_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
290
    """ Calculates heat stability function
sbiri's avatar
sbiri committed
291

sbiri's avatar
sbiri committed
292 293 294 295
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
296 297
    meth : str

sbiri's avatar
sbiri committed
298 299 300 301
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
302
    coeffs = get_stabco(meth)
sbiri's avatar
sbiri committed
303
    alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
sbiri's avatar
sbiri committed
304
    if (meth == "ERA5"):
sbiri's avatar
sbiri committed
305 306
        psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma),
                        psi_stab_era5(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
307 308
    elif (meth == "C30" or meth == "C35" or meth == "C40"):
        psit = psit_26(zol)
sbiri's avatar
sbiri committed
309
    else:
sbiri's avatar
sbiri committed
310 311
        psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma),
                        psi_stab(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
312
    return psit
sbiri's avatar
sbiri committed
313 314 315
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
316
def get_stabco(meth="S80"):
sbiri's avatar
sbiri committed
317
    """ Gives the coefficients \\alpha, \\beta, \\gamma for stability functions
sbiri's avatar
sbiri committed
318

sbiri's avatar
sbiri committed
319 320
    Parameters
    ----------
sbiri's avatar
sbiri committed
321 322
    meth : str

sbiri's avatar
sbiri committed
323 324 325 326
    Returns
    -------
    coeffs : float
    """
sbiri's avatar
sbiri committed
327 328 329
    if (meth == "S80" or meth == "S88" or meth == "LY04" or
        meth == "UA" or meth == "ERA5" or meth == "C30" or meth == "C35" or
        meth == "C40"):
sbiri's avatar
sbiri committed
330
        alpha, beta, gamma = 16, 0.25, 5  # Smith 1980, from Dyer (1974)
sbiri's avatar
sbiri committed
331
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
332
        alpha, beta, gamma = 16, 0.25, 7
sbiri's avatar
sbiri committed
333
    elif (meth == "YT96"):
sbiri's avatar
sbiri committed
334 335
        alpha, beta, gamma = 20, 0.25, 5
    else:
sbiri's avatar
sbiri committed
336
        print("unknown method stabco: "+meth)
sbiri's avatar
sbiri committed
337 338 339 340 341
    coeffs = np.zeros(3)
    coeffs[0] = alpha
    coeffs[1] = beta
    coeffs[2] = gamma
    return coeffs
sbiri's avatar
sbiri committed
342 343 344 345
# ---------------------------------------------------------------------


def psi_stab_era5(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
346
    """ Calculates heat stability function for stable conditions
sbiri's avatar
sbiri committed
347
        for method ERA5
sbiri's avatar
sbiri committed
348

sbiri's avatar
sbiri committed
349 350 351 352 353 354
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
sbiri's avatar
sbiri committed
355

sbiri's avatar
sbiri committed
356 357 358 359
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
360 361 362
    # eq (3.22) p. 39 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
sbiri's avatar
sbiri committed
363
    return psit
sbiri's avatar
sbiri committed
364 365
# ---------------------------------------------------------------------

sbiri's avatar
sbiri committed
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381

def psit_26(zol):
    """ Computes temperature structure function as in C35

    Parameters
    ----------
    zol : float
        height over MO length

    Returns
    -------
    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)
382 383 384 385 386 387 388
    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))
    f = np.power(zol, 2)/(1+np.power(zol, 2))
    psi = np.where(zol < 0, (1-f)*psik+f*psic, psi)
sbiri's avatar
sbiri committed
389 390 391 392
    return psi
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
393
def psi_conv(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
394
    """ Calculates heat stability function for unstable conditions
sbiri's avatar
sbiri committed
395

sbiri's avatar
sbiri committed
396 397 398 399 400 401
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
sbiri's avatar
sbiri committed
402

sbiri's avatar
sbiri committed
403 404 405 406
    Returns
    -------
    psit : float
    """
407 408
    xtmp = np.power(1-alpha*zol, beta)
    psit = 2*np.log((1+np.power(xtmp, 2))*0.5)
sbiri's avatar
sbiri committed
409
    return psit
sbiri's avatar
sbiri committed
410 411 412 413
# ---------------------------------------------------------------------


def psi_stab(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
414
    """ Calculates heat stability function for stable conditions
sbiri's avatar
sbiri committed
415

sbiri's avatar
sbiri committed
416 417 418 419 420 421
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
sbiri's avatar
sbiri committed
422

sbiri's avatar
sbiri committed
423 424 425 426
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
427
    psit = -gamma*zol
sbiri's avatar
sbiri committed
428
    return psit
sbiri's avatar
sbiri committed
429 430 431 432
# ---------------------------------------------------------------------


def psim_stab_era5(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
433
    """ Calculates momentum stability function for stable conditions
sbiri's avatar
sbiri committed
434
        for method ERA5
sbiri's avatar
sbiri committed
435

sbiri's avatar
sbiri committed
436 437 438 439 440 441
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
sbiri's avatar
sbiri committed
442

sbiri's avatar
sbiri committed
443 444 445 446
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
447 448 449 450 451 452 453 454
    # eq (3.22) p. 39 IFS Documentation cy46r1
    a, b, c, d = 1, 2/3, 5, 0.35
    psim = -b*(zol-c/d)*np.exp(-d*zol)-a*zol-(b*c)/d
    return psim
# ---------------------------------------------------------------------


def psim_conv(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
455
    """ Calculates momentum stability function for unstable conditions
sbiri's avatar
sbiri committed
456

sbiri's avatar
sbiri committed
457 458 459 460 461 462
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
sbiri's avatar
sbiri committed
463

sbiri's avatar
sbiri committed
464 465 466 467
    Returns
    -------
    psim : float
    """
468 469
    xtmp = np.power(1-alpha*zol, beta)
    psim = (2*np.log((1+xtmp)*0.5)+np.log((1+np.power(xtmp, 2))*0.5) -
sbiri's avatar
sbiri committed
470
            2*np.arctan(xtmp)+np.pi/2)
sbiri's avatar
sbiri committed
471
    return psim
sbiri's avatar
sbiri committed
472 473 474 475
# ---------------------------------------------------------------------


def psim_stab(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
476
    """ Calculates momentum stability function for stable conditions
sbiri's avatar
sbiri committed
477

sbiri's avatar
sbiri committed
478 479 480 481 482 483
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
sbiri's avatar
sbiri committed
484

sbiri's avatar
sbiri committed
485 486 487 488
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
489 490 491 492 493
    psim = -gamma*zol
    return psim
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
494
def psiu_26(zol, meth):
sbiri's avatar
sbiri committed
495
    """ Computes velocity structure function C35
sbiri's avatar
sbiri committed
496

sbiri's avatar
sbiri committed
497 498 499 500
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
501

sbiri's avatar
sbiri committed
502 503 504
    Returns
    -------
    psi : float
sbiri's avatar
sbiri committed
505
    """
sbiri's avatar
sbiri committed
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
    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
    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
sbiri's avatar
sbiri committed
530 531 532 533
    return psi
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
534
def get_skin(sst, qsea, rho, Rl, Rs, Rnl, cp, lv, tkt, usr, tsr, qsr, lat):
sbiri's avatar
sbiri committed
535
    """ Computes cool skin
sbiri's avatar
sbiri committed
536

sbiri's avatar
sbiri committed
537 538 539 540 541 542 543 544 545 546 547 548
    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)
sbiri's avatar
sbiri committed
549 550
    Rnl : float
        upwelling IR radiation (W/m^2)
sbiri's avatar
sbiri committed
551 552 553 554
    cp : float
       specific heat of air at constant pressure
    lv : float
       latent heat of vaporization
sbiri's avatar
sbiri committed
555 556
    tkt : float
       cool skin thickness
sbiri's avatar
sbiri committed
557 558 559 560 561 562 563 564
    usr : float
       friction velocity
    tsr : float
       star temperature
    qsr : float
       star humidity
    lat : float
       latitude
sbiri's avatar
sbiri committed
565

sbiri's avatar
sbiri committed
566 567 568
    Returns
    -------
    dter : float
sbiri's avatar
sbiri committed
569 570
    dqer : float

sbiri's avatar
sbiri committed
571
    """
sbiri's avatar
sbiri committed
572 573 574
    # coded following Saunders (1967) with lambda = 6
    g = gc(lat, None)
    if (np.nanmin(sst) > 200):  # if Ta in Kelvin convert to Celsius
sbiri's avatar
sbiri committed
575
        sst = sst-273.16
sbiri's avatar
sbiri committed
576 577 578 579
    # ************  cool skin constants  *******
    # density of water, specific heat capacity of water, water viscosity,
    # thermal conductivity of water
    rhow, cpw, visw, tcw = 1022, 4000, 1e-6, 0.6
sbiri's avatar
sbiri committed
580
    Al = 2.1e-5*np.power(sst+3.2, 0.79)
sbiri's avatar
sbiri committed
581
    be = 0.026
sbiri's avatar
sbiri committed
582 583
    bigc = 16*g*cpw*np.power(rhow*visw, 3)/(np.power(tcw, 2)*np.power(rho, 2))
    wetc = 0.622*lv*qsea/(287.1*np.power(sst+273.16, 2))
sbiri's avatar
sbiri committed
584 585 586 587 588 589 590
    Rns = 0.945*Rs  # albedo correction
    hsb = -rho*cp*usr*tsr
    hlb = -rho*lv*usr*qsr
    qout = Rnl+hsb+hlb
    dels = Rns*(0.065+11*tkt-6.6e-5/tkt*(1-np.exp(-tkt/8.0e-4)))
    qcol = qout-dels
    alq = Al*qcol+be*hlb*cpw/lv
sbiri's avatar
sbiri committed
591
    xlamx = 6*np.ones(sst.shape)
sbiri's avatar
sbiri committed
592
    xlamx = np.where(alq > 0, 6/(1+(bigc*alq/usr**4)**0.75)**0.333, 6)
sbiri's avatar
sbiri committed
593 594 595
    tkt = np.where(alq > 0, xlamx*visw/(np.sqrt(rho/rhow)*usr),
                   np.where(xlamx*visw/(np.sqrt(rho/rhow)*usr) > 0.01, 0.01,
                   xlamx*visw/(np.sqrt(rho/rhow)*usr)))
sbiri's avatar
sbiri committed
596 597
    dter = qcol*tkt/tcw
    dqer = wetc*dter
sbiri's avatar
sbiri committed
598
    return dter, dqer, tkt
sbiri's avatar
sbiri committed
599 600 601 602
# ---------------------------------------------------------------------


def get_gust(beta, Ta, usr, tsrv, zi, lat):
sbiri's avatar
sbiri committed
603
    """ Computes gustiness
sbiri's avatar
sbiri committed
604

sbiri's avatar
sbiri committed
605 606 607 608 609 610 611 612 613 614 615 616 617 618
    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
sbiri's avatar
sbiri committed
619

sbiri's avatar
sbiri committed
620 621 622 623
    Returns
    -------
    ug : float
    """
624
    if (np.nanmax(Ta) < 200):  # convert to K if in Celsius
sbiri's avatar
sbiri committed
625 626
        Ta = Ta+273.16
    g = gc(lat, None)
sbiri's avatar
sbiri committed
627
    Bf = (-g/Ta)*usr*tsrv
sbiri's avatar
sbiri committed
628 629 630 631 632 633
    ug = np.ones(np.shape(Ta))*0.2
    ug = np.where(Bf > 0, beta*np.power(Bf*zi, 1/3), 0.2)
    return ug
# ---------------------------------------------------------------------


634
def get_heights(h, dim_len):
sbiri's avatar
sbiri committed
635
    """ Reads input heights for velocity, temperature and humidity
sbiri's avatar
sbiri committed
636

sbiri's avatar
sbiri committed
637 638 639 640
    Parameters
    ----------
    h : float
        input heights (m)
sbiri's avatar
sbiri committed
641

sbiri's avatar
sbiri committed
642 643 644 645
    Returns
    -------
    hh : array
    """
646
    hh = np.zeros((3, dim_len))
sbiri's avatar
sbiri committed
647
    if (type(h) == float or type(h) == int):
648 649 650 651 652 653 654 655 656 657 658 659 660 661
        hh[0, :], hh[1, :], hh[2, :] = h, h, h
    elif (len(h) == 2 and h.ndim == 1):
        hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[1]
    elif (len(h) == 3 and h.ndim == 1):
        hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[2]
    elif (len(h) == 1 and h.ndim == 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):
        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):
        hh = np.zeros((3, h.shape[1]))
        hh = np.copy(h)
sbiri's avatar
sbiri committed
662
    return hh
sbiri's avatar
sbiri committed
663 664 665
# ---------------------------------------------------------------------


666 667
def qsat_sea(T, P, qmeth):
    """ Computes surface saturation specific humidity (g/kg)
sbiri's avatar
sbiri committed
668

sbiri's avatar
sbiri committed
669 670 671 672 673 674
    Parameters
    ----------
    T : float
        temperature ($^\\circ$\\,C)
    P : float
        pressure (mb)
675 676
    qmeth : str
        method to calculate vapor pressure
sbiri's avatar
sbiri committed
677

sbiri's avatar
sbiri committed
678 679 680
    Returns
    -------
    qs : float
sbiri's avatar
sbiri committed
681 682 683 684
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
685
    ex = VaporPressure(T, P, 'liquid', qmeth)
sbiri's avatar
sbiri committed
686 687 688 689 690 691
    es = 0.98*ex  # reduction at sea surface
    qs = 622*es/(P-0.378*es)
    return qs
# ------------------------------------------------------------------------------


692
def qsat_air(T, P, rh, qmeth):
sbiri's avatar
sbiri committed
693
    """ Computes saturation specific humidity (g/kg) as in C35
sbiri's avatar
sbiri committed
694

sbiri's avatar
sbiri committed
695 696 697 698 699 700
    Parameters
    ----------
    T : float
        temperature ($^\circ$\,C)
    P : float
        pressure (mb)
701 702 703 704
    rh : float
       relative humidity (%)
    qmeth : str
        method to calculate vapor pressure
sbiri's avatar
sbiri committed
705

sbiri's avatar
sbiri committed
706 707 708 709
    Returns
    -------
    q : float
    em : float
sbiri's avatar
sbiri committed
710 711 712 713
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
714
    es = VaporPressure(T, P, 'liquid', qmeth)
sbiri's avatar
sbiri committed
715 716
    em = 0.01*rh*es
    q = 622*em/(P-0.378*em)
717
    return q
sbiri's avatar
sbiri committed
718 719 720 721
# ---------------------------------------------------------------------


def gc(lat, lon=None):
sbiri's avatar
sbiri committed
722
    """ Computes gravity relative to latitude
sbiri's avatar
sbiri committed
723

sbiri's avatar
sbiri committed
724 725 726 727 728 729
    Parameters
    ----------
    lat : float
        latitude ($^\circ$)
    lon : float
        longitude ($^\circ$, optional)
sbiri's avatar
sbiri committed
730

sbiri's avatar
sbiri committed
731 732 733 734
    Returns
    -------
    gc : float
        gravity constant (m/s^2)
sbiri's avatar
sbiri committed
735 736 737 738 739 740 741
    """
    gamma = 9.7803267715
    c1 = 0.0052790414
    c2 = 0.0000232718
    c3 = 0.0000001262
    c4 = 0.0000000007
    if lon is not None:
sbiri's avatar
sbiri committed
742
        lon_m, lat_m = np.meshgrid(lon, lat)
sbiri's avatar
sbiri committed
743 744 745 746
    else:
        lat_m = lat
    phi = lat_m*np.pi/180.
    xx = np.sin(phi)
sbiri's avatar
sbiri committed
747 748
    gc = (gamma*(1+c1*np.power(xx, 2)+c2*np.power(xx, 4)+c3*np.power(xx, 6) +
          c4*np.power(xx, 8)))
sbiri's avatar
sbiri committed
749
    return gc
sbiri's avatar
sbiri committed
750 751 752
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
753
def visc_air(T):
sbiri's avatar
sbiri committed
754
    """ Computes the kinematic viscosity of dry air as a function of air temp.
sbiri's avatar
sbiri committed
755
    following Andreas (1989), CRREL Report 89-11.
sbiri's avatar
sbiri committed
756

sbiri's avatar
sbiri committed
757 758 759 760
    Parameters
    ----------
    Ta : float
        air temperature ($^\circ$\,C)
sbiri's avatar
sbiri committed
761

sbiri's avatar
sbiri committed
762 763 764 765
    Returns
    -------
    visa : float
        kinematic viscosity (m^2/s)
sbiri's avatar
sbiri committed
766
    """
sbiri's avatar
sbiri committed
767 768 769 770 771
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-273.16
    visa = 1.326e-5*(1+6.542e-3*T+8.301e-6*np.power(T, 2) -
                     4.84e-9*np.power(T, 3))
sbiri's avatar
sbiri committed
772
    return visa