flux_subs.py 33.2 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
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
647 648
def get_L(L, lat, usr, tsr, qsr, t10n, tv10n, qair, h_in, T, Ta, th, tv, sst,
          dt, dq, wind, monob, meth):
sbiri's avatar
sbiri committed
649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699
    """
    calculates Monin-Obukhov length and virtual star temperature

    Parameters
    ----------
    L : int
        Monin-Obukhov length definition options
           0 : default for S80, S88, LP82, YT96 and LY04
           1 : following UA (Zeng et al., 1998), default for UA
           2 : following ERA5 (IFS Documentation cy46r1), default for ERA5
           3 : COARE3.5 (Edson et al., 2013), default for C30, C35 and C40
    lat : float
        latitude
    usr : float
        friction wind speed (m/s)
    tsr : float
        star temperature (K)
    qsr : float
        star specific humidity (g/kg)
    t10n : float
        neutral temperature at 10m (K)
    tv10n : float
        neutral virtual temperature at 10m (K)
    qair : float
        air specific humidity (g/kg)
    h_in : float
        sensor heights (m)
    T : float
        air temperature (K)
    Ta : float
        air temperature (K)
    th : float
        potential temperature (K)
    tv : float
        virtual temperature (K)
    sst : float
        sea surface temperature (K)
    dt : float
        temperature difference (K)
    dq : float
        specific humidity difference (g/kg)
    wind : float
        wind speed (m/s)
    monob : float
        Monin-Obukhov length from previous iteration step (m)
    meth : str
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96", "UA",
        "LY04", "C30", "C35", "C40", "ERA5"

    Returns
    -------
700 701 702 703
    tsrv : float
        virtual star temperature (K)
    monob : float
        M-O length (m)
sbiri's avatar
sbiri committed
704 705

    """
sbiri's avatar
sbiri committed
706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732
    g = gc(lat)
    if (L == 0):
        tsrv = tsr+0.61*t10n*qsr
        monob = ((tv10n*np.power(usr, 2))/(g*kappa*tsrv))
        monob = np.where(np.fabs(monob) < 1, np.where(monob < 0, -1, 1), monob)
    elif (L == 1):
        tsrv = tsr*(1.+0.61*qair)+0.61*th*qsr
        monob = ((tv*np.power(usr, 2))/(kappa*g*tsrv))
    elif (L == 2):
        tsrv = tsr+0.61*t10n*qsr
        Rb = ((g*h_in[0]*((2*dt)/(Ta+sst-g*h_in[0])+0.61*dq)) /
              np.power(wind, 2))
        zo = (0.11*visc_air(Ta)/usr+0.018*np.power(usr, 2)/g)
        zot = 0.40*visc_air(Ta)/usr
        zol = (Rb*(np.power(np.log((h_in[0]+zo)/zo)-psim_calc((h_in[0]+zo) /
                                                              monob, meth) +
                            psim_calc(zo/monob, meth), 2) /
                   (np.log((h_in[0]+zo)/zot) -
                    psit_calc((h_in[0]+zo)/monob, meth) +
                    psit_calc(zot/monob, meth))))
        monob = h_in[0]/zol
    elif (L == 3):
        tsrv = tsr+0.61*(T+CtoK)*qsr
        zol = (kappa*g*h_in[0]/(T+CtoK)*(tsr+0.61*(T+CtoK)*qsr) /
               np.power(usr, 2))
        monob = h_in[0]/zol
    return tsrv, monob
sbiri's avatar
sbiri committed
733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
#------------------------------------------------------------------------------


def get_hum(hum, T, sst, P, qmeth):
    """
    Get specific humidity output

    Parameters
    ----------
    hum : array
        humidity input switch 2x1 [x, values] default is relative humidity
            x='rh' : relative humidity in %
            x='q' : specific humidity (g/kg)
            x='Td' : dew point temperature (K)
    T : float
        air temperature in K
    sst : float
        sea surface temperature in K
    P : float
        air pressure at sea level in hPa
    qmeth : str
        method to calculate specific humidity from vapor pressure

    Returns
    -------
    qair : float
        specific humidity of air
    qsea : float
        specific humidity over sea surface

    """
    if (hum == None):
        RH = np.ones(sst.shape)*80
        qsea = qsat_sea(sst, P, qmeth)/1000     # surface water q (g/kg)
        qair = qsat_air(T, P, RH, qmeth)/1000   # q of air (g/kg)
    elif (hum[0] not in ['rh', 'q', 'Td']):
        print("unknown humidity input")
        qair, qsea = np.nan, np.nan
    elif (hum[0] == 'rh'):
        RH = hum[1]
        if (np.all(RH < 1)):
            print("input relative humidity units should be \%")
            qair, qsea = np.nan, np.nan
        qsea = qsat_sea(sst, P, qmeth)/1000    # surface water q (g/kg)
        qair = qsat_air(T, P, RH, qmeth)/1000  # q of air (g/kg)
    elif (hum[0] == 'q'):
        qair = hum[1]
        qsea = qsat_sea(sst, P, qmeth)/1000  # surface water q (g/kg)
    elif (hum[0] == 'Td'):
        Td = hum[1] # dew point temperature (K)
        Td = np.where(Td < 200, np.copy(Td)+CtoK, np.copy(Td))
        T = np.where(T < 200, np.copy(T)+CtoK, np.copy(T))
        esd = 611.21*np.exp(17.502*((Td-273.16)/(Td-32.19)))
        es = 611.21*np.exp(17.502*((T-273.16)/(T-32.19)))
        RH = 100*esd/es
        qair = qsat_air(T, P, RH, qmeth)/1000  # q of air (g/kg)
        qsea = qsat_sea(sst, P, qmeth)/1000    # surface water q (g/kg)
    return qair, qsea
#-------------------------------------------------------------------------
sbiri's avatar
sbiri committed
792 793


794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904
def get_strs(h_in, monob, wind, zo, zot, zoq, dt, dq, dter, dqer, ct, cq,
             cskin, meth):
    """
    calculates star wind speed, temperature and specific humidity

    Parameters
    ----------
    h_in : float
        sensor heights (m)
    monob : float
        M-O length (m)
    wind : float
        wind speed (m/s)
    zo : float
        momentum roughness length (m)
    zot : float
        temperature roughness length (m)
    zoq : float
        moisture roughness length (m)
    dt : float
        temperature difference (K)
    dq : float
        specific humidity difference (g/kg)
    dter : float
        cskin temperature adjustment (K)
    dqer : float
        cskin q adjustment (q/kg)
    ct : float
        temperature exchange coefficient
    cq : float
        moisture exchange coefficient
    cskin : int
        cool skin adjustment switch
    meth : str
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96", "UA",
        "LY04", "C30", "C35", "C40", "ERA5"

    Returns
    -------
    usr : float
        friction wind speed (m/s)
    tsr : float
        star temperature (K)
    qsr : float
        star specific humidity (g/kg)

    """
    if (meth == "UA"):
        usr = np.where(h_in[0]/monob < -1.574, kappa*wind /
                       (np.log(-1.574*monob/zo)-psim_calc(-1.574, meth) +
                        psim_calc(zo/monob, meth) +
                        1.14*(np.power(-h_in[0]/monob, 1/3) -
                        np.power(1.574, 1/3))),
                       np.where((h_in[0]/monob > -1.574) & (h_in[0]/monob < 0),
                                kappa*wind/(np.log(h_in[0]/zo) -
                                psim_calc(h_in[0]/monob, meth) +
                                psim_calc(zo/monob, meth)),
                                np.where((h_in[0]/monob > 0) &
                                (h_in[0]/monob < 1),
                                kappa*wind/(np.log(h_in[0]/zo) +
                                5*h_in[0]/monob-5*zo/monob),
                                kappa*wind/(np.log(monob/zo)+5-5*zo/monob +
                                5*np.log(h_in[0]/monob)+h_in[0]/monob-1))))
                                # Zeng et al. 1998 (7-10)
        tsr = np.where(h_in[1]/monob < -0.465, kappa*(dt+dter*cskin) /
                       (np.log((-0.465*monob)/zot) -
                        psit_calc(-0.465, meth)+0.8*(np.power(0.465, -1/3) -
                        np.power(-h_in[1]/monob, -1/3))),
                       np.where((h_in[1]/monob > -0.465) & (h_in[1]/monob < 0),
                                kappa*(dt+dter*cskin)/(np.log(h_in[1]/zot) -
                       psit_calc(h_in[1]/monob, meth) +
                       psit_calc(zot/monob, meth)),
                        np.where((h_in[1]/monob > 0) & (h_in[1]/monob < 1),
                                 kappa*(dt+dter*cskin)/(np.log(h_in[1]/zot) +
                                 5*h_in[1]/monob-5*zot/monob),
                                 kappa*(dt+dter*cskin)/(np.log(monob/zot)+5 -
                                 5*zot/monob+5*np.log(h_in[1]/monob) +
                                 h_in[1]/monob-1))))
                                # Zeng et al. 1998 (11-14)
        qsr = np.where(h_in[2]/monob < -0.465, kappa*(dq+dqer*cskin) /
                       (np.log((-0.465*monob)/zoq) -
                        psit_calc(-0.465, meth)+psit_calc(zoq/monob, meth) +
                        0.8*(np.power(0.465, -1/3) -
                             np.power(-h_in[2]/monob, -1/3))),
                       np.where((h_in[2]/monob > -0.465) & (h_in[2]/monob < 0),
                                kappa*(dq+dqer*cskin)/(np.log(h_in[1]/zot) -
                                psit_calc(h_in[2]/monob, meth) +
                                psit_calc(zoq/monob, meth)),
                                np.where((h_in[2]/monob > 0) &
                                         (h_in[2]/monob<1),
                                         kappa*(dq+dqer*cskin) /
                                         (np.log(h_in[1]/zoq)+5*h_in[2]/monob -
                                          5*zoq/monob),
                                         kappa*(dq+dqer*cskin)/
                                         (np.log(monob/zoq)+5-5*zoq/monob +
                                          5*np.log(h_in[2]/monob) +
                                          h_in[2]/monob-1))))
    elif (meth == "C30" or meth == "C35" or meth == "C40"):
        usr = (wind*kappa/(np.log(h_in[0]/zo)-psiu_26(h_in[0]/monob, meth)))
        tsr = ((dt+dter*cskin)*(kappa/(np.log(h_in[1]/zot) -
                                       psit_26(h_in[1]/monob))))
        qsr = ((dq+dqer*cskin)*(kappa/(np.log(h_in[2]/zoq) -
                                       psit_26(h_in[2]/monob))))
    else:
        usr = (wind*kappa/(np.log(h_in[0]/zo)-psim_calc(h_in[0]/monob, meth)))
        tsr = ct*wind*(dt+dter*cskin)/usr
        qsr = cq*wind*(dq+dqer*cskin)/usr
    return usr, tsr, qsr
#------------------------------------------------------------------------------


905
def get_heights(h, dim_len):
sbiri's avatar
sbiri committed
906
    """ Reads input heights for velocity, temperature and humidity
sbiri's avatar
sbiri committed
907

sbiri's avatar
sbiri committed
908 909 910 911
    Parameters
    ----------
    h : float
        input heights (m)
912 913
    dim_len : int
        length dimension
sbiri's avatar
sbiri committed
914

sbiri's avatar
sbiri committed
915 916 917 918
    Returns
    -------
    hh : array
    """
919
    hh = np.zeros((3, dim_len))
sbiri's avatar
sbiri committed
920
    if (type(h) == float or type(h) == int):
921
        hh[0, :], hh[1, :], hh[2, :] = h, h, h
922
    elif (len(h) == 2 and np.ndim(h) == 1):
923
        hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[1]
924
    elif (len(h) == 3 and np.ndim(h) == 1):
925
        hh[0, :], hh[1, :], hh[2, :] = h[0], h[1], h[2]
926
    elif (len(h) == 1 and np.ndim(h) == 2):
927 928
        hh = np.zeros((3, h.shape[1]))
        hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[0, :], h[0, :]
929
    elif (len(h) == 2 and np.ndim(h) == 2):
930 931
        hh = np.zeros((3, h.shape[1]))
        hh[0, :], hh[1, :], hh[2, :] = h[0, :], h[1, :], h[1, :]
932
    elif (len(h) == 3 and np.ndim(h) == 2):
933 934
        hh = np.zeros((3, h.shape[1]))
        hh = np.copy(h)
sbiri's avatar
sbiri committed
935
    return hh
sbiri's avatar
sbiri committed
936 937 938
# ---------------------------------------------------------------------


939 940
def qsat_sea(T, P, qmeth):
    """ Computes surface saturation specific humidity (g/kg)
sbiri's avatar
sbiri committed
941

sbiri's avatar
sbiri committed
942 943 944 945 946 947
    Parameters
    ----------
    T : float
        temperature ($^\\circ$\\,C)
    P : float
        pressure (mb)
948 949
    qmeth : str
        method to calculate vapor pressure
sbiri's avatar
sbiri committed
950

sbiri's avatar
sbiri committed
951 952 953
    Returns
    -------
    qs : float
sbiri's avatar
sbiri committed
954 955 956 957
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
958
    ex = VaporPressure(T, P, 'liquid', qmeth)
sbiri's avatar
sbiri committed
959 960 961 962 963 964
    es = 0.98*ex  # reduction at sea surface
    qs = 622*es/(P-0.378*es)
    return qs
# ------------------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
968 969 970 971 972 973
    Parameters
    ----------
    T : float
        temperature ($^\circ$\,C)
    P : float
        pressure (mb)
974 975 976 977
    rh : float
       relative humidity (%)
    qmeth : str
        method to calculate vapor pressure
sbiri's avatar
sbiri committed
978

sbiri's avatar
sbiri committed
979 980 981 982
    Returns
    -------
    q : float
    em : float
sbiri's avatar
sbiri committed
983 984 985 986
    """
    T = np.asarray(T)
    if (np.nanmin(T) > 200):  # if Ta in Kelvin convert to Celsius
        T = T-CtoK
987
    es = VaporPressure(T, P, 'liquid', qmeth)
sbiri's avatar
sbiri committed
988 989
    em = 0.01*rh*es
    q = 622*em/(P-0.378*em)
990
    return q
sbiri's avatar
sbiri committed
991 992 993 994
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
997 998 999 1000 1001 1002
    Parameters
    ----------
    lat : float
        latitude ($^\circ$)
    lon : float
        longitude ($^\circ$, optional)
sbiri's avatar
sbiri committed
1003

sbiri's avatar
sbiri committed
1004 1005 1006 1007
    Returns
    -------
    gc : float
        gravity constant (m/s^2)
sbiri's avatar
sbiri committed
1008 1009 1010 1011 1012 1013 1014
    """
    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
1015
        lon_m, lat_m = np.meshgrid(lon, lat)
sbiri's avatar
sbiri committed
1016 1017 1018 1019
    else:
        lat_m = lat
    phi = lat_m*np.pi/180.
    xx = np.sin(phi)
sbiri's avatar
sbiri committed
1020 1021
    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
1022
    return gc
sbiri's avatar
sbiri committed
1023 1024 1025
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
1030 1031 1032 1033
    Parameters
    ----------
    Ta : float
        air temperature ($^\circ$\,C)
sbiri's avatar
sbiri committed
1034

sbiri's avatar
sbiri committed
1035 1036 1037 1038
    Returns
    -------
    visa : float
        kinematic viscosity (m^2/s)
sbiri's avatar
sbiri committed
1039
    """
sbiri's avatar
sbiri committed
1040 1041 1042 1043 1044
    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
1045
    return visa