flux_subs.py 25 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"):
78
            # Charnock roughness length (eq. 4 in Smith 88)
sbiri's avatar
sbiri committed
79
            zc = 0.011*np.power(usr, 2)/g
80
            #  smooth surface roughness length (eq. 6 in Smith 88)
sbiri's avatar
sbiri committed
81
            zs = 0.11*visc_air(Ta)/usr
82
            zo = zc + zs  #  eq. 7 & 8 in Smith 88
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
        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)
93 94 95 96
#            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
97 98 99 100 101 102
            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
103 104
            # 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
105
        else:
sbiri's avatar
sbiri committed
106
            print("unknown method for cdn_from_roughness "+meth)
sbiri's avatar
sbiri committed
107
        cdnn = (kappa/np.log(10/zo))**2
sbiri's avatar
sbiri committed
108
    cdn = np.where(np.abs(cdnn-cdn) < tol, cdnn, np.nan)
sbiri's avatar
sbiri committed
109
    return cdnn
sbiri's avatar
sbiri committed
110
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
111

sbiri's avatar
sbiri committed
112

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

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

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


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

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

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


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

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

sbiri's avatar
sbiri committed
248 249 250 251 252
    Returns
    -------
    ct : float
    cq : float
    """
sbiri's avatar
sbiri committed
253 254 255
    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
256 257 258
# ---------------------------------------------------------------------


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

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

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


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

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

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


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

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

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


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

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

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

sbiri's avatar
sbiri committed
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378

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)
379 380 381 382 383 384 385
    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
386 387 388 389
    return psi
# ---------------------------------------------------------------------


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

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

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


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

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

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


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

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

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

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

sbiri's avatar
sbiri committed
461 462 463 464
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
465 466 467
    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
468
    return psim
sbiri's avatar
sbiri committed
469 470 471 472
# ---------------------------------------------------------------------


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

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

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


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

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

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


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

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

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


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

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

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

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


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

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

sbiri's avatar
sbiri committed
644 645 646 647
    Returns
    -------
    ug : float
    """
648
    if (np.nanmax(Ta) < 200):  # convert to K if in Celsius
sbiri's avatar
sbiri committed
649 650
        Ta = Ta+273.16
    g = gc(lat, None)
sbiri's avatar
sbiri committed
651
    Bf = (-g/Ta)*usr*tsrv
sbiri's avatar
sbiri committed
652 653 654 655 656 657
    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
658
def get_heights(h):
sbiri's avatar
sbiri committed
659
    """ Reads input heights for velocity, temperature and humidity
sbiri's avatar
sbiri committed
660

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

sbiri's avatar
sbiri committed
666 667 668 669
    Returns
    -------
    hh : array
    """
sbiri's avatar
sbiri committed
670 671 672 673 674
    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
675
    else:
sbiri's avatar
sbiri committed
676
        hh[0], hh[1], hh[2] = h[0], h[1], h[2]
sbiri's avatar
sbiri committed
677
    return hh
sbiri's avatar
sbiri committed
678 679 680
# ---------------------------------------------------------------------


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

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

sbiri's avatar
sbiri committed
689 690 691 692
    Returns
    -------
    svp : float
        in mb, pure water
sbiri's avatar
sbiri committed
693 694 695 696 697 698 699 700 701
    """
    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
702
    """ Computes specific humidity of the  sea surface air
sbiri's avatar
sbiri committed
703

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

sbiri's avatar
sbiri committed
711 712
    Returns
    -------
sbiri's avatar
sbiri committed
713
    qsea : float
sbiri's avatar
sbiri committed
714
        (kg/kg)
sbiri's avatar
sbiri committed
715 716
    """
    if (np.nanmin(sst) < 200):  # if sst in Celsius convert to Kelvin
sbiri's avatar
sbiri committed
717
        sst = sst+273.16
sbiri's avatar
sbiri committed
718 719 720 721 722 723 724 725 726
    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
727
    """ Computes specific humidity following Haltiner and Martin p.24
sbiri's avatar
sbiri committed
728

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

sbiri's avatar
sbiri committed
738 739 740
    Returns
    -------
    qair : float, (kg/kg)
sbiri's avatar
sbiri committed
741 742 743 744 745 746 747 748 749 750
    """
    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
751
    """ Computes saturation vapor pressure (mb) as in C35
sbiri's avatar
sbiri committed
752

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

sbiri's avatar
sbiri committed
760 761 762
    Returns
    -------
    exx : float
sbiri's avatar
sbiri committed
763 764 765 766 767 768 769 770 771 772
    """
    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
773
    """ Computes surface saturation specific humidity (g/kg) as in C35
sbiri's avatar
sbiri committed
774

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

sbiri's avatar
sbiri committed
782 783 784
    Returns
    -------
    qs : float
sbiri's avatar
sbiri committed
785 786 787 788 789 790 791 792 793 794 795 796
    """
    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
797
    """ Computes saturation specific humidity (g/kg) as in C35
sbiri's avatar
sbiri committed
798

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

sbiri's avatar
sbiri committed
806 807 808 809
    Returns
    -------
    q : float
    em : float
sbiri's avatar
sbiri committed
810 811 812 813 814 815 816 817 818 819 820 821
    """
    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
822
    """ Computes gravity relative to latitude
sbiri's avatar
sbiri committed
823

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

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


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

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

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