flux_subs.py 25.2 KB
Newer Older
sbiri's avatar
sbiri committed
1 2 3
import numpy as np

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

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

sbiri's avatar
sbiri committed
10

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

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

sbiri's avatar
sbiri committed
24 25 26 27
    Returns
    -------
    cdn : float
    """
sbiri's avatar
sbiri committed
28
    if (meth == "S80"):
sbiri's avatar
sbiri committed
29 30
        cdn = np.where(u10n <= 3, (0.61+0.567/u10n)*0.001,
                       (0.61+0.063*u10n)*0.001)
sbiri's avatar
sbiri committed
31
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
32 33 34
        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
35 36 37 38
    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
39
        # for u<3 same as S80
sbiri's avatar
sbiri committed
40 41 42 43
        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
44
    elif (meth == "LY04"):
sbiri's avatar
sbiri committed
45 46
        cdn = np.where(u10n >= 0.5,
                       (0.142+(2.7/u10n)+(u10n/13.09))*0.001, np.nan)
sbiri's avatar
sbiri committed
47
    else:
sbiri's avatar
sbiri committed
48
        print("unknown method cdn: "+meth)
sbiri's avatar
sbiri committed
49
    return cdn
sbiri's avatar
sbiri committed
50 51 52
# ---------------------------------------------------------------------


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

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

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

sbiri's avatar
sbiri committed
113

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

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

sbiri's avatar
sbiri committed
131 132 133 134 135 136 137
    Returns
    -------
    ctn : float
        neutral heat exchange coefficient
    cqn : float
        neutral moisture exchange coefficient
    """
sbiri's avatar
sbiri committed
138
    if (meth == "S80" or meth == "S88" or meth == "YT96"):
sbiri's avatar
sbiri committed
139
        cqn = np.ones(Ta.shape)*1.20*0.001  # from S88
sbiri's avatar
sbiri committed
140
        ctn = np.ones(Ta.shape)*1.00*0.001
sbiri's avatar
sbiri committed
141
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
142 143 144 145
        cqn = np.where((zol <= 0) & (u10n > 4) & (u10n < 14), 1.15*0.001,
                       np.nan)
        ctn = np.where((zol <= 0) & (u10n > 4) & (u10n < 25), 1.13*0.001,
                       0.66*0.001)
sbiri's avatar
sbiri committed
146 147 148 149 150
    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
151
        # Zeng et al. 1998 (25)
sbiri's avatar
sbiri committed
152 153
        re=usr*zo/visc_air(Ta)
        zoq = zo/np.exp(2.67*np.power(re, 1/4)-2.57)
sbiri's avatar
sbiri committed
154 155 156 157 158
        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
159 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
    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
189 190 191 192 193 194
        # 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
195
    else:
sbiri's avatar
sbiri committed
196
        print("unknown method ctcqn: "+meth)
sbiri's avatar
sbiri committed
197
    return ctn, cqn
sbiri's avatar
sbiri committed
198 199 200
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
204 205 206 207 208 209 210 211 212 213
    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
214

sbiri's avatar
sbiri committed
215 216 217 218
    Returns
    -------
    cd : float
    """
sbiri's avatar
sbiri committed
219 220
    cd = (cdn*np.power(1+np.sqrt(cdn)*np.power(kappa, -1) *
          (np.log(height/ref_ht)-psim), -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
    """
sbiri's avatar
sbiri committed
254 255 256
    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)))
    return ct, cq
sbiri's avatar
sbiri committed
257 258 259
# ---------------------------------------------------------------------


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

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

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


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

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

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


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

sbiri's avatar
sbiri committed
317 318
    Parameters
    ----------
sbiri's avatar
sbiri committed
319 320
    meth : str

sbiri's avatar
sbiri committed
321 322 323 324
    Returns
    -------
    coeffs : float
    """
sbiri's avatar
sbiri committed
325 326 327
    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
328
        alpha, beta, gamma = 16, 0.25, 5  # Smith 1980, from Dyer (1974)
sbiri's avatar
sbiri committed
329
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
330
        alpha, beta, gamma = 16, 0.25, 7
sbiri's avatar
sbiri committed
331
    elif (meth == "YT96"):
sbiri's avatar
sbiri committed
332 333
        alpha, beta, gamma = 20, 0.25, 5
    else:
sbiri's avatar
sbiri committed
334
        print("unknown method stabco: "+meth)
sbiri's avatar
sbiri committed
335 336 337 338 339
    coeffs = np.zeros(3)
    coeffs[0] = alpha
    coeffs[1] = beta
    coeffs[2] = gamma
    return coeffs
sbiri's avatar
sbiri committed
340 341 342 343
# ---------------------------------------------------------------------


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

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

sbiri's avatar
sbiri committed
354 355 356 357
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
358 359 360
    # 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
361
    return psit
sbiri's avatar
sbiri committed
362 363
# ---------------------------------------------------------------------

sbiri's avatar
sbiri committed
364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390

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)
    psi = np.where(zol < 0, (1-(np.power(zol, 2)/(1+np.power(zol, 2))))*2 *
                   np.log((1+np.sqrt(1-15*zol))/2)+(np.power(zol, 2) /
                   (1+np.power(zol, 2))) *
                   (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)), psi)
    return psi
# ---------------------------------------------------------------------


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

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

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


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

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

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


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

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

sbiri's avatar
sbiri committed
441 442 443 444
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
445 446 447 448 449 450 451 452
    # 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
453
    """ Calculates momentum stability function for unstable conditions
sbiri's avatar
sbiri committed
454

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

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


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

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

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


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

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

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


sbiri's avatar
sbiri committed
532 533
def psiu_40(zol):
    """ Computes velocity structure function C35
sbiri's avatar
sbiri committed
534

sbiri's avatar
sbiri committed
535 536 537 538
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
539

sbiri's avatar
sbiri committed
540 541 542
    Returns
    -------
    psi : float
sbiri's avatar
sbiri committed
543
    """
sbiri's avatar
sbiri committed
544
    dzol = np.where(0.35*zol > 50, 50, 0.35*zol)  # stable
sbiri's avatar
sbiri committed
545
    a, b, c, d = 1, 3/4, 5, 0.35
sbiri's avatar
sbiri committed
546 547 548
    psi = -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d)
    k = np.where(zol < 0)  # unstable
    x = (1-18*zol[k])**0.25
sbiri's avatar
sbiri committed
549
    psik = 2*np.log((1+x)/2)+np.log((1+x**2)/2)-2*np.arctan(x)+2*np.arctan(1)
sbiri's avatar
sbiri committed
550
    x = (1-10*zol[k])**0.3333
sbiri's avatar
sbiri committed
551 552
    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))
sbiri's avatar
sbiri committed
553
    f = zol[k]**2/(1+zol[k]**2)
sbiri's avatar
sbiri committed
554 555 556 557 558
    psi[k] = (1-f)*psik+f*psic
    return psi
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
562 563 564 565 566 567 568 569 570 571 572 573
    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
574 575
    Rnl : float
        upwelling IR radiation (W/m^2)
sbiri's avatar
sbiri committed
576 577 578 579
    cp : float
       specific heat of air at constant pressure
    lv : float
       latent heat of vaporization
sbiri's avatar
sbiri committed
580 581
    tkt : float
       cool skin thickness
sbiri's avatar
sbiri committed
582 583 584 585 586 587 588 589
    usr : float
       friction velocity
    tsr : float
       star temperature
    qsr : float
       star humidity
    lat : float
       latitude
sbiri's avatar
sbiri committed
590

sbiri's avatar
sbiri committed
591 592 593
    Returns
    -------
    dter : float
sbiri's avatar
sbiri committed
594 595
    dqer : float

sbiri's avatar
sbiri committed
596
    """
sbiri's avatar
sbiri committed
597 598 599
    # 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
600
        sst = sst-273.16
sbiri's avatar
sbiri committed
601 602 603 604
    # ************  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
605
    Al = 2.1e-5*np.power(sst+3.2, 0.79)
sbiri's avatar
sbiri committed
606
    be = 0.026
sbiri's avatar
sbiri committed
607 608
    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
609 610 611 612 613 614 615
    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
616
    xlamx = 6*np.ones(sst.shape)
sbiri's avatar
sbiri committed
617
    xlamx = np.where(alq > 0, 6/(1+(bigc*alq/usr**4)**0.75)**0.333, 6)
sbiri's avatar
sbiri committed
618 619 620
    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
621 622
    dter = qcol*tkt/tcw
    dqer = wetc*dter
sbiri's avatar
sbiri committed
623
    return dter, dqer, tkt
sbiri's avatar
sbiri committed
624 625 626 627
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
630 631 632 633 634 635 636 637 638 639 640 641 642 643
    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
644

sbiri's avatar
sbiri committed
645 646 647 648
    Returns
    -------
    ug : float
    """
sbiri's avatar
sbiri committed
649 650 651
    if (np.max(Ta) < 200):  # convert to K if in Celsius
        Ta = Ta+273.16
    g = gc(lat, None)
sbiri's avatar
sbiri committed
652
    Bf = (-g/Ta)*usr*tsrv
sbiri's avatar
sbiri committed
653 654 655 656 657 658
    ug = np.ones(np.shape(Ta))*0.2
    ug = np.where(Bf > 0, beta*np.power(Bf*zi, 1/3), 0.2)
    return ug
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
659
def get_heights(h):
sbiri's avatar
sbiri committed
660
    """ Reads input heights for velocity, temperature and humidity
sbiri's avatar
sbiri committed
661

sbiri's avatar
sbiri committed
662 663 664 665
    Parameters
    ----------
    h : float
        input heights (m)
sbiri's avatar
sbiri committed
666

sbiri's avatar
sbiri committed
667 668 669 670
    Returns
    -------
    hh : array
    """
sbiri's avatar
sbiri committed
671 672 673 674 675
    hh = np.zeros(3)
    if (type(h) == float or type(h) == int):
        hh[0], hh[1], hh[2] = h, h, h
    elif len(h) == 2:
        hh[0], hh[1], hh[2] = h[0], h[1], h[1]
sbiri's avatar
sbiri committed
676
    else:
sbiri's avatar
sbiri committed
677
        hh[0], hh[1], hh[2] = h[0], h[1], h[2]
sbiri's avatar
sbiri committed
678
    return hh
sbiri's avatar
sbiri committed
679 680 681
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
682
def svp_calc(T):
sbiri's avatar
sbiri committed
683
    """ Calculates saturation vapour pressure
sbiri's avatar
sbiri committed
684

sbiri's avatar
sbiri committed
685 686 687 688
    Parameters
    ----------
    T : float
        temperature (K)
sbiri's avatar
sbiri committed
689

sbiri's avatar
sbiri committed
690 691 692 693
    Returns
    -------
    svp : float
        in mb, pure water
sbiri's avatar
sbiri committed
694 695 696 697 698 699 700 701 702
    """
    if (np.nanmin(T) < 200):  # if T in Celsius convert to Kelvin
        T = T+273.16
    svp = np.where(np.isnan(T), np.nan, 2.1718e08*np.exp(-4157/(T-33.91-0.16)))
    return svp
# ---------------------------------------------------------------------


def qsea_calc(sst, pres):
sbiri's avatar
sbiri committed
703
    """ Computes specific humidity of the  sea surface air
sbiri's avatar
sbiri committed
704

sbiri's avatar
sbiri committed
705 706 707 708 709 710
    Parameters
    ----------
    sst : float
        sea surface temperature (K)
    pres : float
        pressure (mb)
sbiri's avatar
sbiri committed
711

sbiri's avatar
sbiri committed
712 713
    Returns
    -------
sbiri's avatar
sbiri committed
714
    qsea : float
sbiri's avatar
sbiri committed
715
        (kg/kg)
sbiri's avatar
sbiri committed
716 717
    """
    if (np.nanmin(sst) < 200):  # if sst in Celsius convert to Kelvin
sbiri's avatar
sbiri committed
718
        sst = sst+273.16
sbiri's avatar
sbiri committed
719 720 721 722 723 724 725 726 727
    ed = svp_calc(sst)
    e = 0.98*ed
    qsea = (0.622*e)/(pres-0.378*e)
    qsea = np.where(~np.isnan(sst+pres), qsea, np.nan)
    return qsea
# ---------------------------------------------------------------------


def q_calc(Ta, rh, pres):
sbiri's avatar
sbiri committed
728
    """ Computes specific humidity following Haltiner and Martin p.24
sbiri's avatar
sbiri committed
729

sbiri's avatar
sbiri committed
730 731 732 733 734 735 736 737
    Parameters
    ----------
    Ta : float
        air temperature (K)
    rh : float
        relative humidity (%)
    pres : float
        air pressure (mb)
sbiri's avatar
sbiri committed
738

sbiri's avatar
sbiri committed
739 740 741
    Returns
    -------
    qair : float, (kg/kg)
sbiri's avatar
sbiri committed
742 743 744 745 746 747 748 749 750 751
    """
    if (np.nanmin(Ta) < 200):  # if sst in Celsius convert to Kelvin
        Ta = Ta+273.15
    e = np.where(np.isnan(Ta+rh+pres), np.nan, svp_calc(Ta)*rh*0.01)
    qair = np.where(np.isnan(e), np.nan, ((0.62197*e)/(pres-0.378*e)))
    return qair
# ------------------------------------------------------------------------------


def bucksat(T, P):
sbiri's avatar
sbiri committed
752
    """ Computes saturation vapor pressure (mb) as in C35
sbiri's avatar
sbiri committed
753

sbiri's avatar
sbiri committed
754 755 756 757 758 759
    Parameters
    ----------
    T : float
        temperature ($^\\circ$\\,C)
    P : float
        pressure (mb)
sbiri's avatar
sbiri committed
760

sbiri's avatar
sbiri committed
761 762 763
    Returns
    -------
    exx : float
sbiri's avatar
sbiri committed
764 765 766 767 768 769 770 771 772 773
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
    exx = 6.1121*np.exp(17.502*T/(T+240.97))*(1.0007+3.46e-6*P)
    return exx
# ------------------------------------------------------------------------------


def qsat26sea(T, P):
sbiri's avatar
sbiri committed
774
    """ Computes surface saturation specific humidity (g/kg) as in C35
sbiri's avatar
sbiri committed
775

sbiri's avatar
sbiri committed
776 777 778 779 780 781
    Parameters
    ----------
    T : float
        temperature ($^\\circ$\\,C)
    P : float
        pressure (mb)
sbiri's avatar
sbiri committed
782

sbiri's avatar
sbiri committed
783 784 785
    Returns
    -------
    qs : float
sbiri's avatar
sbiri committed
786 787 788 789 790 791 792 793 794 795 796 797
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
    ex = bucksat(T, P)
    es = 0.98*ex  # reduction at sea surface
    qs = 622*es/(P-0.378*es)
    return qs
# ------------------------------------------------------------------------------


def qsat26air(T, P, rh):
sbiri's avatar
sbiri committed
798
    """ Computes saturation specific humidity (g/kg) as in C35
sbiri's avatar
sbiri committed
799

sbiri's avatar
sbiri committed
800 801 802 803 804 805
    Parameters
    ----------
    T : float
        temperature ($^\circ$\,C)
    P : float
        pressure (mb)
sbiri's avatar
sbiri committed
806

sbiri's avatar
sbiri committed
807 808 809 810
    Returns
    -------
    q : float
    em : float
sbiri's avatar
sbiri committed
811 812 813 814 815 816 817 818 819 820 821 822
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
    es = bucksat(T, P)
    em = 0.01*rh*es
    q = 622*em/(P-0.378*em)
    return q, em
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
825 826 827 828 829 830
    Parameters
    ----------
    lat : float
        latitude ($^\circ$)
    lon : float
        longitude ($^\circ$, optional)
sbiri's avatar
sbiri committed
831

sbiri's avatar
sbiri committed
832 833 834 835
    Returns
    -------
    gc : float
        gravity constant (m/s^2)
sbiri's avatar
sbiri committed
836 837 838 839 840 841 842
    """
    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
843
        lon_m, lat_m = np.meshgrid(lon, lat)
sbiri's avatar
sbiri committed
844 845 846 847
    else:
        lat_m = lat
    phi = lat_m*np.pi/180.
    xx = np.sin(phi)
sbiri's avatar
sbiri committed
848 849
    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
850
    return gc
sbiri's avatar
sbiri committed
851 852 853
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
858 859 860 861
    Parameters
    ----------
    Ta : float
        air temperature ($^\circ$\,C)
sbiri's avatar
sbiri committed
862

sbiri's avatar
sbiri committed
863 864 865 866
    Returns
    -------
    visa : float
        kinematic viscosity (m^2/s)
sbiri's avatar
sbiri committed
867
    """
sbiri's avatar
sbiri committed
868 869 870 871 872
    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
873
    return visa