flux_subs.py 23.3 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
5
""" Conversion factor for $^\circ\,$C to K """
sbiri's avatar
sbiri committed
6

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
# ---------------------------------------------------------------------
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
def get_stabco(meth="S80"):
    """ Gives the coefficients \\alpha, \\beta, \\gamma for stability functions

    Parameters
    ----------
    meth : str

    Returns
    -------
    coeffs : float
    """
    alpha, beta, gamma = 0, 0, 0
    if (meth == "S80" or meth == "S88" or meth == "LY04" or
        meth == "UA" or meth == "ERA5" or meth == "C30" or meth == "C35" or
        meth == "C40"):
        alpha, beta, gamma = 16, 0.25, 5  # Smith 1980, from Dyer (1974)
    elif (meth == "LP82"):
        alpha, beta, gamma = 16, 0.25, 7
    elif (meth == "YT96"):
        alpha, beta, gamma = 20, 0.25, 5
    else:
        print("unknown method stabco: "+meth)
    coeffs = np.zeros(3)
    coeffs[0] = alpha
    coeffs[1] = beta
    coeffs[2] = gamma
    return coeffs
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
38

sbiri's avatar
sbiri committed
39

sbiri's avatar
sbiri committed
40
def cdn_calc(u10n, Ta, Tp, lat, meth="S80"):
sbiri's avatar
sbiri committed
41
    """ Calculates neutral drag coefficient
sbiri's avatar
sbiri committed
42

sbiri's avatar
sbiri committed
43 44 45 46 47 48 49 50
    Parameters
    ----------
    u10n : float
        neutral 10m wind speed (m/s)
    Ta   : float
        air temperature (K)
    Tp   : float
        wave period
51 52
    lat : float
        latitude
sbiri's avatar
sbiri committed
53 54
    meth : str

sbiri's avatar
sbiri committed
55 56 57 58
    Returns
    -------
    cdn : float
    """
59
    cdn = np.zeros(Ta.shape)*np.nan
sbiri's avatar
sbiri committed
60
    if (meth == "S80"):
sbiri's avatar
sbiri committed
61 62
        cdn = np.where(u10n <= 3, (0.61+0.567/u10n)*0.001,
                       (0.61+0.063*u10n)*0.001)
sbiri's avatar
sbiri committed
63
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
64 65 66
        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
67 68 69 70
    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
71
        # for u<3 same as S80
sbiri's avatar
sbiri committed
72 73 74 75
        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
76
    elif (meth == "LY04"):
sbiri's avatar
sbiri committed
77 78
        cdn = np.where(u10n >= 0.5,
                       (0.142+(2.7/u10n)+(u10n/13.09))*0.001, np.nan)
sbiri's avatar
sbiri committed
79
    else:
sbiri's avatar
sbiri committed
80
        print("unknown method cdn: "+meth)
sbiri's avatar
sbiri committed
81
    return cdn
sbiri's avatar
sbiri committed
82 83 84
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
88 89 90 91 92 93 94 95
    Parameters
    ----------
    u10n : float
        neutral 10m wind speed (m/s)
    Ta   : float
        air temperature (K)
    Tp   : float
        wave period
96 97
    lat : float
        latitude
sbiri's avatar
sbiri committed
98 99
    meth : str

sbiri's avatar
sbiri committed
100 101 102 103
    Returns
    -------
    cdn : float
    """
sbiri's avatar
sbiri committed
104
    g, tol = gc(lat, None), 0.000001
sbiri's avatar
sbiri committed
105 106 107
    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
108 109
    for it in range(5):
        cdn = np.copy(cdnn)
sbiri's avatar
sbiri committed
110
        usr = np.sqrt(cdn*u10n**2)
sbiri's avatar
sbiri committed
111
        if (meth == "S88"):
112
            # Charnock roughness length (eq. 4 in Smith 88)
sbiri's avatar
sbiri committed
113
            zc = 0.011*np.power(usr, 2)/g
114
            #  smooth surface roughness length (eq. 6 in Smith 88)
sbiri's avatar
sbiri committed
115
            zs = 0.11*visc_air(Ta)/usr
116
            zo = zc + zs  #  eq. 7 & 8 in Smith 88
sbiri's avatar
sbiri committed
117
        elif (meth == "UA"):
sbiri's avatar
sbiri committed
118 119
            # 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
120 121 122 123 124 125 126
        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)
127 128 129
            # a = np.where(u10n > 19, 0.0017*19-0.0050,
            #             np.where((u10n > 7) & (u10n <= 18),
            #                       0.0017*u10n-0.0050, a))
130
            a = np.where(u10n > 19, 0.0017*19-0.0050, 0.0017*u10n-0.0050)
sbiri's avatar
sbiri committed
131 132 133 134 135 136
            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"):
137
            # eq. (3.26) p.38 over sea IFS Documentation cy46r1
138
            zo = 0.018*np.power(usr, 2)/g+0.11*visc_air(Ta)/usr
sbiri's avatar
sbiri committed
139
        else:
sbiri's avatar
sbiri committed
140
            print("unknown method for cdn_from_roughness "+meth)
sbiri's avatar
sbiri committed
141
        cdnn = (kappa/np.log(10/zo))**2
sbiri's avatar
sbiri committed
142
    cdn = np.where(np.abs(cdnn-cdn) < tol, cdnn, np.nan)
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
    return cdn
# ---------------------------------------------------------------------


def cd_calc(cdn, height, ref_ht, psim):
    """ Calculates drag coefficient at reference height

    Parameters
    ----------
    cdn : float
        neutral drag coefficient
    height : float
        original sensor height (m)
    ref_ht : float
        reference height (m)
    psim : float
        momentum stability function

    Returns
    -------
    cd : float
    """
    cd = (cdn/np.power(1+(np.sqrt(cdn)*(np.log(height/ref_ht)-psim))/kappa, 2))
    return cd
sbiri's avatar
sbiri committed
167
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
168

sbiri's avatar
sbiri committed
169

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

sbiri's avatar
sbiri committed
173 174 175 176 177
    Parameters
    ----------
    zol  : float
        height over MO length
    cdn  : float
178
        neutral drag coefficient
sbiri's avatar
sbiri committed
179 180 181 182 183 184
    u10n : float
        neutral 10m wind speed (m/s)
    zo   : float
        surface roughness (m)
    Ta   : float
        air temperature (K)
sbiri's avatar
sbiri committed
185 186
    meth : str

sbiri's avatar
sbiri committed
187 188 189 190 191 192 193
    Returns
    -------
    ctn : float
        neutral heat exchange coefficient
    cqn : float
        neutral moisture exchange coefficient
    """
sbiri's avatar
sbiri committed
194
    if (meth == "S80" or meth == "S88" or meth == "YT96"):
sbiri's avatar
sbiri committed
195
        cqn = np.ones(Ta.shape)*1.20*0.001  # from S88
sbiri's avatar
sbiri committed
196
        ctn = np.ones(Ta.shape)*1.00*0.001
sbiri's avatar
sbiri committed
197
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
198
        cqn = np.where((zol <= 0) & (u10n > 4) & (u10n < 14), 1.15*0.001,
199
                       1*0.001)
sbiri's avatar
sbiri committed
200 201
        ctn = np.where((zol <= 0) & (u10n > 4) & (u10n < 25), 1.13*0.001,
                       0.66*0.001)
sbiri's avatar
sbiri committed
202 203 204 205 206
    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
207
        # Zeng et al. 1998 (25)
sbiri's avatar
sbiri committed
208 209
        re=usr*zo/visc_air(Ta)
        zoq = zo/np.exp(2.67*np.power(re, 1/4)-2.57)
sbiri's avatar
sbiri committed
210 211 212 213 214
        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
215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
    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"):
245
        # eq. (3.26) p.38 over sea IFS Documentation cy46r1
sbiri's avatar
sbiri committed
246 247 248 249 250
        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
251
    else:
sbiri's avatar
sbiri committed
252
        print("unknown method ctcqn: "+meth)
sbiri's avatar
sbiri committed
253
    return ctn, cqn
sbiri's avatar
sbiri committed
254 255 256
# ---------------------------------------------------------------------


257
def ctcq_calc(cdn, cd, ctn, cqn, ht, hq, ref_ht, psit, psiq):
sbiri's avatar
sbiri committed
258
    """ Calculates heat and moisture exchange coefficients at reference height
sbiri's avatar
sbiri committed
259

sbiri's avatar
sbiri committed
260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
    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
280

sbiri's avatar
sbiri committed
281 282 283
    Returns
    -------
    ct : float
284
       heat exchange coefficient
sbiri's avatar
sbiri committed
285
    cq : float
286
       moisture exchange coefficient
sbiri's avatar
sbiri committed
287
    """
288
    ct = (ctn*np.sqrt(cd/cdn) /
289
          (1+ctn*((np.log(ht/ref_ht)-psit)/(kappa*np.sqrt(cdn)))))
290
    cq = (cqn*np.sqrt(cd/cdn) /
291
          (1+cqn*((np.log(hq/ref_ht)-psiq)/(kappa*np.sqrt(cdn)))))
sbiri's avatar
sbiri committed
292
    return ct, cq
sbiri's avatar
sbiri committed
293 294 295
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
296
def psim_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
297
    """ Calculates momentum stability function
sbiri's avatar
sbiri committed
298

sbiri's avatar
sbiri committed
299 300 301 302
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
303 304
    meth : str

sbiri's avatar
sbiri committed
305 306 307 308
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
309
    if (meth == "ERA5"):
310
        psim = psim_era5(zol)
sbiri's avatar
sbiri committed
311 312
    elif (meth == "C30" or meth == "C35" or meth == "C40"):
        psim = psiu_26(zol, meth)
sbiri's avatar
sbiri committed
313
    else:
314 315
        psim = np.where(zol < 0, psim_conv(zol, meth),
                        psim_stab(zol, meth))
sbiri's avatar
sbiri committed
316
    return psim
sbiri's avatar
sbiri committed
317 318 319
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
320
def psit_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
321
    """ Calculates heat stability function
sbiri's avatar
sbiri committed
322

sbiri's avatar
sbiri committed
323 324 325 326
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
327
    meth : str
328
        parameterisation method
sbiri's avatar
sbiri committed
329

sbiri's avatar
sbiri committed
330 331 332 333
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
334
    if (meth == "ERA5"):
335 336
        psit = np.where(zol < 0, psi_conv(zol, meth),
                        psi_era5(zol))
sbiri's avatar
sbiri committed
337 338
    elif (meth == "C30" or meth == "C35" or meth == "C40"):
        psit = psit_26(zol)
sbiri's avatar
sbiri committed
339
    else:
340 341
        psit = np.where(zol < 0, psi_conv(zol, meth),
                        psi_stab(zol, meth))
sbiri's avatar
sbiri committed
342
    return psit
sbiri's avatar
sbiri committed
343 344 345
# ---------------------------------------------------------------------


346
def psi_era5(zol):
sbiri's avatar
sbiri committed
347
    """ Calculates heat stability function for stable conditions
sbiri's avatar
sbiri committed
348
        for method ERA5
sbiri's avatar
sbiri committed
349

sbiri's avatar
sbiri committed
350 351 352 353
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
354

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

sbiri's avatar
sbiri committed
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
379 380 381 382 383 384 385 386
    dzol = np.where(d*zol > 50, 50, d*zol)
    psi = np.where(zol > 0,-(np.power(1+b*zol, 1.5)+b*(zol-14.28) *
                             np.exp(-dzol)+8.525), np.nan)
    psik = np.where(zol < 0, 2*np.log((1+np.sqrt(1-15*zol))/2), np.nan)
    psic = np.where(zol < 0, 1.5*np.log((1+np.power(1-34.15*zol, 1/3) +
                    np.power(1-34.15*zol, 2/3))/3)-np.sqrt(3) *
                    np.arctan(1+2*np.power(1-34.15*zol, 1/3))/np.sqrt(3) +
                    4*np.arctan(1)/np.sqrt(3), np.nan)
387 388
    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
# ---------------------------------------------------------------------


393
def psi_conv(zol, meth):
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
    Parameters
    ----------
    zol : float
        height over MO length
400 401
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
402

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


415
def psi_stab(zol, meth):
sbiri's avatar
sbiri committed
416
    """ Calculates heat stability function for stable conditions
sbiri's avatar
sbiri committed
417

sbiri's avatar
sbiri committed
418 419 420 421
    Parameters
    ----------
    zol : float
        height over MO length
422 423
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
424

sbiri's avatar
sbiri committed
425 426 427 428
    Returns
    -------
    psit : float
    """
429 430
    coeffs = get_stabco(meth)
    gamma = coeffs[2]
sbiri's avatar
sbiri committed
431
    psit = -gamma*zol
sbiri's avatar
sbiri committed
432
    return psit
sbiri's avatar
sbiri committed
433 434 435
# ---------------------------------------------------------------------


436 437
def psim_era5(zol):
    """ Calculates momentum stability function for method ERA5
sbiri's avatar
sbiri committed
438

sbiri's avatar
sbiri committed
439 440 441 442
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
443

sbiri's avatar
sbiri committed
444 445 446 447
    Returns
    -------
    psim : float
    """
448 449 450 451
    # eq (3.20, 3.22) p. 37 IFS Documentation cy46r1
    coeffs = get_stabco("ERA5")
    alpha, beta = coeffs[0], coeffs[1]
    xtmp = np.power(1-alpha*zol, beta)
sbiri's avatar
sbiri committed
452
    a, b, c, d = 1, 2/3, 5, 0.35
453 454 455
    psim = np.where(zol < 0, np.pi/2-2*np.arctan(xtmp) +
                    np.log((np.power(1+xtmp, 2)*(1+np.power(xtmp, 2)))/8),
                    -b*(zol-c/d)*np.exp(-d*zol)-a*zol-(b*c)/d)
sbiri's avatar
sbiri committed
456 457 458 459
    return psim
# ---------------------------------------------------------------------


460
def psim_conv(zol, meth):
sbiri's avatar
sbiri committed
461
    """ Calculates momentum stability function for unstable conditions
sbiri's avatar
sbiri committed
462

sbiri's avatar
sbiri committed
463 464 465 466
    Parameters
    ----------
    zol : float
        height over MO length
467 468
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
469

sbiri's avatar
sbiri committed
470 471 472 473
    Returns
    -------
    psim : float
    """
474 475
    coeffs = get_stabco(meth)
    alpha, beta = coeffs[0], coeffs[1]
476 477
    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
478
            2*np.arctan(xtmp)+np.pi/2)
sbiri's avatar
sbiri committed
479
    return psim
sbiri's avatar
sbiri committed
480 481 482
# ---------------------------------------------------------------------


483
def psim_stab(zol, meth):
sbiri's avatar
sbiri committed
484
    """ Calculates momentum stability function for stable conditions
sbiri's avatar
sbiri committed
485

sbiri's avatar
sbiri committed
486 487 488 489
    Parameters
    ----------
    zol : float
        height over MO length
490 491
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
492

sbiri's avatar
sbiri committed
493 494 495 496
    Returns
    -------
    psim : float
    """
497 498
    coeffs = get_stabco(meth)
    gamma = coeffs[2]
sbiri's avatar
sbiri committed
499 500 501 502 503
    psim = -gamma*zol
    return psim
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
504
def psiu_26(zol, meth):
sbiri's avatar
sbiri committed
505
    """ Computes velocity structure function C35
sbiri's avatar
sbiri committed
506

sbiri's avatar
sbiri committed
507 508 509 510
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
511

sbiri's avatar
sbiri committed
512 513 514
    Returns
    -------
    psi : float
sbiri's avatar
sbiri committed
515
    """
sbiri's avatar
sbiri committed
516 517
    if (meth == "C30"):
        dzol = np.where(0.35*zol > 50, 50, 0.35*zol) # stable
518 519 520 521 522 523 524 525 526 527 528
        psi = np.where(zol > 0, -((1+zol)+0.6667*(zol-14.28)*np.exp(-dzol) +
                                  8.525), np.nan)
        x = np.where(zol < 0, np.power(1-15*zol, 0.25), np.nan)
        psik = np.where(zol < 0, 2*np.log((1+x)/2)+np.log((1+np.power(x, 2)) /
                        2)-2*np.arctan(x)+2*np.arctan(1), np.nan)
        x = np.where(zol < 0, np.power(1-10.15*zol, 0.3333), np.nan)
        psic = np.where(zol < 0, 1.5*np.log((1+x+np.power(x, 2))/3) -
                        np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) +
                        4*np.arctan(1)/np.sqrt(3), np.nan)
        f = np.power(zol, 2)/(1+np.power(zol, 2))
        psi = np.where(zol < 0, (1-f)*psik+f*psic, psi)
sbiri's avatar
sbiri committed
529 530 531
    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
532 533 534 535 536 537 538 539 540 541 542
        psi = np.where(zol > 0, -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d),
                       np.nan)
        x = np.where(zol < 0, np.power(1-15*zol, 0.25), np.nan)
        psik = np.where(zol < 0, 2*np.log((1+x)/2)+np.log((1+x**2)/2) -
                        2*np.arctan(x)+2*np.arctan(1), np.nan)
        x = np.where(zol < 0, np.power(1-10.15*zol, 0.3333), np.nan)
        psic = np.where(zol < 0, 1.5*np.log((1+x+np.power(x, 2))/3) -
                        np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) +
                        4*np.arctan(1)/np.sqrt(3), np.nan)
        f = np.power(zol, 2)/(1+np.power(zol, 2))
        psi = np.where(zol < 0, (1-f)*psik+f*psic, psi)
sbiri's avatar
sbiri committed
543 544 545 546
    return psi
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
550 551 552 553 554 555 556 557 558 559 560 561
    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
562 563
    Rnl : float
        upwelling IR radiation (W/m^2)
sbiri's avatar
sbiri committed
564 565 566 567
    cp : float
       specific heat of air at constant pressure
    lv : float
       latent heat of vaporization
sbiri's avatar
sbiri committed
568 569
    tkt : float
       cool skin thickness
sbiri's avatar
sbiri committed
570 571 572 573 574 575 576 577
    usr : float
       friction velocity
    tsr : float
       star temperature
    qsr : float
       star humidity
    lat : float
       latitude
sbiri's avatar
sbiri committed
578

sbiri's avatar
sbiri committed
579 580 581
    Returns
    -------
    dter : float
sbiri's avatar
sbiri committed
582 583
    dqer : float

sbiri's avatar
sbiri committed
584
    """
sbiri's avatar
sbiri committed
585 586 587
    # 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
588
        sst = sst-273.16
sbiri's avatar
sbiri committed
589 590 591 592
    # ************  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
593
    Al = 2.1e-5*np.power(sst+3.2, 0.79)
sbiri's avatar
sbiri committed
594
    be = 0.026
sbiri's avatar
sbiri committed
595 596
    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
597 598 599 600 601 602 603
    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
604
    xlamx = 6*np.ones(sst.shape)
sbiri's avatar
sbiri committed
605
    xlamx = np.where(alq > 0, 6/(1+(bigc*alq/usr**4)**0.75)**0.333, 6)
sbiri's avatar
sbiri committed
606 607 608
    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
609 610
    dter = qcol*tkt/tcw
    dqer = wetc*dter
sbiri's avatar
sbiri committed
611
    return dter, dqer, tkt
sbiri's avatar
sbiri committed
612 613 614 615
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
618 619 620 621 622 623 624 625 626 627 628 629 630 631
    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
632

sbiri's avatar
sbiri committed
633 634 635 636
    Returns
    -------
    ug : float
    """
637
    if (np.nanmax(Ta) < 200):  # convert to K if in Celsius
sbiri's avatar
sbiri committed
638 639
        Ta = Ta+273.16
    g = gc(lat, None)
sbiri's avatar
sbiri committed
640
    Bf = (-g/Ta)*usr*tsrv
sbiri's avatar
sbiri committed
641 642 643 644 645 646
    ug = np.ones(np.shape(Ta))*0.2
    ug = np.where(Bf > 0, beta*np.power(Bf*zi, 1/3), 0.2)
    return ug
# ---------------------------------------------------------------------


647
def get_heights(h, dim_len):
sbiri's avatar
sbiri committed
648
    """ Reads input heights for velocity, temperature and humidity
sbiri's avatar
sbiri committed
649

sbiri's avatar
sbiri committed
650 651 652 653
    Parameters
    ----------
    h : float
        input heights (m)
654 655
    dim_len : int
        length dimension
sbiri's avatar
sbiri committed
656

sbiri's avatar
sbiri committed
657 658 659 660
    Returns
    -------
    hh : array
    """
661
    hh = np.zeros((3, dim_len))
sbiri's avatar
sbiri committed
662
    if (type(h) == float or type(h) == int):
663
        hh[0, :], hh[1, :], hh[2, :] = h, h, h
664
    elif (len(h) == 2 and np.ndim(h) == 1):
665
        hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[1]
666
    elif (len(h) == 3 and np.ndim(h) == 1):
667
        hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[2]
668
    elif (len(h) == 1 and np.ndim(h) == 2):
669 670
        hh = np.zeros((3, h.shape[1]))
        hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[0, :], h[0, :]
671
    elif (len(h) == 2 and np.ndim(h) == 2):
672 673
        hh = np.zeros((3, h.shape[1]))
        hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[1, :], h[1, :]
674
    elif (len(h) == 3 and np.ndim(h) == 2):
675 676
        hh = np.zeros((3, h.shape[1]))
        hh = np.copy(h)
sbiri's avatar
sbiri committed
677
    return hh
sbiri's avatar
sbiri committed
678 679 680
# ---------------------------------------------------------------------


681 682
def qsat_sea(T, P, qmeth):
    """ Computes surface saturation specific humidity (g/kg)
sbiri's avatar
sbiri committed
683

sbiri's avatar
sbiri committed
684 685 686 687 688 689
    Parameters
    ----------
    T : float
        temperature ($^\\circ$\\,C)
    P : float
        pressure (mb)
690 691
    qmeth : str
        method to calculate vapor pressure
sbiri's avatar
sbiri committed
692

sbiri's avatar
sbiri committed
693 694 695
    Returns
    -------
    qs : float
sbiri's avatar
sbiri committed
696 697 698 699
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
700
    ex = VaporPressure(T, P, 'liquid', qmeth)
sbiri's avatar
sbiri committed
701 702 703 704 705 706
    es = 0.98*ex  # reduction at sea surface
    qs = 622*es/(P-0.378*es)
    return qs
# ------------------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
710 711 712 713 714 715
    Parameters
    ----------
    T : float
        temperature ($^\circ$\,C)
    P : float
        pressure (mb)
716 717 718 719
    rh : float
       relative humidity (%)
    qmeth : str
        method to calculate vapor pressure
sbiri's avatar
sbiri committed
720

sbiri's avatar
sbiri committed
721 722 723 724
    Returns
    -------
    q : float
    em : float
sbiri's avatar
sbiri committed
725 726 727 728
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
729
    es = VaporPressure(T, P, 'liquid', qmeth)
sbiri's avatar
sbiri committed
730 731
    em = 0.01*rh*es
    q = 622*em/(P-0.378*em)
732
    return q
sbiri's avatar
sbiri committed
733 734 735 736
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
739 740 741 742 743 744
    Parameters
    ----------
    lat : float
        latitude ($^\circ$)
    lon : float
        longitude ($^\circ$, optional)
sbiri's avatar
sbiri committed
745

sbiri's avatar
sbiri committed
746 747 748 749
    Returns
    -------
    gc : float
        gravity constant (m/s^2)
sbiri's avatar
sbiri committed
750 751 752 753 754 755 756
    """
    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
757
        lon_m, lat_m = np.meshgrid(lon, lat)
sbiri's avatar
sbiri committed
758 759 760 761
    else:
        lat_m = lat
    phi = lat_m*np.pi/180.
    xx = np.sin(phi)
sbiri's avatar
sbiri committed
762 763
    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
764
    return gc
sbiri's avatar
sbiri committed
765 766 767
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
772 773 774 775
    Parameters
    ----------
    Ta : float
        air temperature ($^\circ$\,C)
sbiri's avatar
sbiri committed
776

sbiri's avatar
sbiri committed
777 778 779 780
    Returns
    -------
    visa : float
        kinematic viscosity (m^2/s)
sbiri's avatar
sbiri committed
781
    """
sbiri's avatar
sbiri committed
782 783 784 785 786
    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
787
    return visa