flux_subs.py 24.4 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 11

def charnock_C35(wind, u10n, usr, seastate, waveage, wcp, sigH, lat):
sbiri's avatar
sbiri committed
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 38
    """ Calculates Charnock number following Edson et al. 2013 based on 
    C35 matlab code (coare35vn.m)
    
    Parameters
    ----------
    wind : float
        wind speed (m/s)
    u10n : float
        neutral 10m wind speed (m/s)
    usr  : float
        friction velocity (m/s)
    seastate : bool
        0 or 1
    waveage  : bool
        0 or 1
    wcp      : float
        phase speed of dominant waves (m/s)
    sigH     : float
        significant wave height (m)
    lat      : float
        latitude (deg)
    
    Returns
    -------
    ac : float
        Charnock number
    """
sbiri's avatar
sbiri committed
39 40 41 42 43 44 45 46 47 48
    g = gc(lat, None)
    a1, a2 = 0.0017, -0.0050
    charnC = np.where(u10n > 19, a1*19+a2, a1*u10n+a2)
    A, B = 0.114, 0.622  # wave-age dependent coefficients
    Ad, Bd = 0.091, 2.0  # Sea-state/wave-age dependent coefficients
    charnW = A*(usr/wcp)**B
    zoS = sigH*Ad*(usr/wcp)**Bd
    charnS = (zoS*g)/usr**2
    charn = np.where(wind > 10, 0.011+(wind-10)/(18-10)*(0.018-0.011),
                     np.where(wind > 18, 0.018, 0.011*np.ones(np.shape(wind))))
sbiri's avatar
sbiri committed
49 50
    if waveage:
        if seastate:
sbiri's avatar
sbiri committed
51
            charn = charnS
sbiri's avatar
sbiri committed
52
        else:
sbiri's avatar
sbiri committed
53
            charn = charnW
sbiri's avatar
sbiri committed
54
    else:
sbiri's avatar
sbiri committed
55 56 57 58 59
        charn = charnC
    ac = np.zeros((len(wind), 3))
    ac[:, 0] = charn
    ac[:, 1] = charnS
    ac[:, 2] = charnW
sbiri's avatar
sbiri committed
60
    return ac
sbiri's avatar
sbiri committed
61 62 63 64
# ---------------------------------------------------------------------


def cd_C35(u10n, wind, usr, charn, monob, Ta, hh_in, lat):
sbiri's avatar
sbiri committed
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
    """ Calculates exchange coefficients following Edson et al. 2013 based on 
    C35 matlab code (coare35vn.m)
    
    Parameters
    ----------
    u10n : float
        neutral 10m wind speed (m/s)
    wind : float
        wind speed (m/s)    
    charn : float
        Charnock number
    monob : float
        Monin-Obukhov stability length
    Ta    : float
        air temperature (K)
    hh_in : float
        input sensor's height (m)
    lat      : float
        latitude (deg)
    
    Returns
    -------
    zo : float
        surface roughness (m)
    cdhf : float
        drag coefficient
    cthf : float
        heat exchange coefficient
    cqhf : float
        moisture exchange coefficient
    """
sbiri's avatar
sbiri committed
96 97 98 99 100 101 102 103 104 105 106 107 108
    g = gc(lat, None)
    zo = charn*usr**2/g+0.11*visc_air(Ta)/usr  # surface roughness
    rr = zo*usr/visc_air(Ta)
    # These thermal roughness lengths give Stanton and
    zoq = np.where(5.8e-5/rr**0.72 > 1.6e-4, 1.6e-4, 5.8e-5/rr**0.72)
    zot = zoq  # Dalton numbers that closely approximate COARE 3.0
    cdhf = kappa/(np.log(hh_in[0]/zo)-psiu_26(hh_in[0]/monob))
    cthf = kappa/(np.log(hh_in[1]/zot)-psit_26(hh_in[1]/monob))
    cqhf = kappa/(np.log(hh_in[2]/zoq)-psit_26(hh_in[2]/monob))
    return zo, cdhf, cthf, cqhf
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
def cdn_calc(u10n, Ta, Tp, method="S80"):
    """ Calculates neutral drag coefficient
    
    Parameters
    ----------
    u10n : float
        neutral 10m wind speed (m/s)
    Ta   : float
        air temperature (K)
    Tp   : float
        wave period
    method : str
    
    Returns
    -------
    cdn : float
    """
    if (method == "S80"):
sbiri's avatar
sbiri committed
127 128
        cdn = np.where(u10n <= 3, (0.61+0.567/u10n)*0.001,
                       (0.61+0.063*u10n)*0.001)
sbiri's avatar
sbiri committed
129
    elif (method == "LP82"):
sbiri's avatar
sbiri committed
130 131 132
        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
133
    elif (method == "S88" or method == "UA" or method == "ERA5"):
sbiri's avatar
sbiri committed
134 135
        cdn = cdn_from_roughness(u10n, Ta, None, method)
    elif (method == "YT96"):
sbiri's avatar
sbiri committed
136
        # for u<3 same as S80
sbiri's avatar
sbiri committed
137 138 139 140
        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
141
    elif (method == "LY04"):
sbiri's avatar
sbiri committed
142 143
        cdn = np.where(u10n >= 0.5,
                       (0.142+(2.7/u10n)+(u10n/13.09))*0.001, np.nan)
sbiri's avatar
sbiri committed
144 145 146
    else:
        print("unknown method cdn: "+method)
    return cdn
sbiri's avatar
sbiri committed
147 148 149
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166
def cdn_from_roughness(u10n, Ta, Tp, method="S88"):
    """ Calculates neutral drag coefficient from roughness length
    
    Parameters
    ----------
    u10n : float
        neutral 10m wind speed (m/s)
    Ta   : float
        air temperature (K)
    Tp   : float
        wave period
    method : str
    
    Returns
    -------
    cdn : float
    """
sbiri's avatar
sbiri committed
167 168 169 170
    g, tol = 9.812, 0.000001
    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
171 172
    for it in range(5):
        cdn = np.copy(cdnn)
sbiri's avatar
sbiri committed
173
        usr = np.sqrt(cdn*u10n**2)
sbiri's avatar
sbiri committed
174
        if (method == "S88"):
sbiri's avatar
sbiri committed
175 176 177 178 179 180 181 182 183 184 185
            # .....Charnock roughness length (equn 4 in Smith 88)
            zc = 0.011*np.power(usr, 2)/g
            # .....smooth surface roughness length (equn 6 in Smith 88)
            zs = 0.11*visc_air(Ta)/usr
            zo = zc + zs  # .....equns 7 & 8 in Smith 88 to calculate new CDN
        elif (method == "UA"):
            # 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
        elif (method == "ERA5"):
            # 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
186
        else:
sbiri's avatar
sbiri committed
187
            print("unknown method for cdn_from_roughness "+method)
sbiri's avatar
sbiri committed
188
        cdnn = (kappa/np.log(10/zo))**2
sbiri's avatar
sbiri committed
189
    cdn = np.where(np.abs(cdnn-cdn) < tol, cdnn, np.nan)
sbiri's avatar
sbiri committed
190
    return cdnn
sbiri's avatar
sbiri committed
191
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
192

sbiri's avatar
sbiri committed
193

sbiri's avatar
sbiri committed
194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
def ctcqn_calc(zol, cdn, u10n, zo, Ta, method="S80"):
    """ Calculates neutral heat and moisture exchange coefficients
    
    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)
    method : str
    
    Returns
    -------
    ctn : float
        neutral heat exchange coefficient
    cqn : float
        neutral moisture exchange coefficient
    """
    if (method == "S80" or method == "S88" or method == "YT96"):
        cqn = np.ones(Ta.shape)*1.20*0.001  # from S88
sbiri's avatar
sbiri committed
220
        ctn = np.ones(Ta.shape)*1.00*0.001
sbiri's avatar
sbiri committed
221
    elif (method == "LP82"):
sbiri's avatar
sbiri committed
222 223 224 225
        cqn = np.where((zol <= 0) & (u10n > 4) & (u10n < 14), 1.15*0.001,
                       np.nan)
        ctn = np.where((zol <= 0) & (u10n > 4) & (u10n < 25), 1.13*0.001,
                       0.66*0.001)
sbiri's avatar
sbiri committed
226 227 228
    elif (method == "LY04"):
        cqn = 34.6*0.001*cdn**0.5
        ctn = np.where(zol <= 0, 32.7*0.001*cdn**0.5, 18*0.001*cdn**0.5)
sbiri's avatar
sbiri committed
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
    elif (method == "UA"):
        usr = (cdn * u10n**2)**0.5
        # Zeng et al. 1998 (25)
        zoq = zo*np.exp(-(2.67*np.power(usr*zo/visc_air(Ta), 1/4)-2.57))
        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)
    elif (method == "ERA5"):
        # 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
245 246 247
    else:
        print("unknown method ctcqn: "+method)
    return ctn, cqn
sbiri's avatar
sbiri committed
248 249 250
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
251
def cd_calc(cdn, height, ref_ht, psim):
sbiri's avatar
sbiri committed
252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268
    """ 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
    """
sbiri's avatar
sbiri committed
269 270
    cd = (cdn*np.power(1+np.sqrt(cdn)*np.power(kappa, -1) *
          (np.log(height/ref_ht)-psim), -2))
sbiri's avatar
sbiri committed
271
    return cd
sbiri's avatar
sbiri committed
272 273 274
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
275
def ctcq_calc(cdn, cd, ctn, cqn, h_t, h_q, ref_ht, psit, psiq):
sbiri's avatar
sbiri committed
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
    """ Calculates heat and moisture exchange coefficients at reference height
    
    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
    
    Returns
    -------
    ct : float
    cq : float
    """
sbiri's avatar
sbiri committed
304 305 306
    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
307 308 309
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
310 311 312 313 314 315 316 317 318 319 320 321 322
def psim_calc(zol, method="S80"):
    """ Calculates momentum stability function
    
    Parameters
    ----------
    zol : float
        height over MO length
    method : str
    
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
323 324
    coeffs = get_stabco(method)
    alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
sbiri's avatar
sbiri committed
325
    if (method == "ERA5"):
sbiri's avatar
sbiri committed
326 327
        psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma),
                        psim_stab_era5(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
328
    else:
sbiri's avatar
sbiri committed
329 330
        psim = np.where(zol < 0, psim_conv(zol, alpha, beta, gamma),
                        psim_stab(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
331
    return psim
sbiri's avatar
sbiri committed
332 333 334
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
335 336 337 338 339 340 341 342 343 344 345 346 347
def psit_calc(zol, method="S80"):
    """ Calculates heat stability function
    
    Parameters
    ----------
    zol : float
        height over MO length
    method : str
    
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
348 349
    coeffs = get_stabco(method)
    alpha, beta, gamma = coeffs[0], coeffs[1], coeffs[2]
sbiri's avatar
sbiri committed
350
    if (method == "ERA5"):
sbiri's avatar
sbiri committed
351 352
        psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma),
                        psi_stab_era5(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
353
    else:
sbiri's avatar
sbiri committed
354 355
        psit = np.where(zol < 0, psi_conv(zol, alpha, beta, gamma),
                        psi_stab(zol, alpha, beta, gamma))
sbiri's avatar
sbiri committed
356
    return psit
sbiri's avatar
sbiri committed
357 358 359
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
360 361 362 363 364 365 366 367 368 369 370 371
def get_stabco(method="S80"):
    """ Gives the coefficients \\alpha, \\beta, \\gamma for stability functions
    
    Parameters
    ----------
    method : str
    
    Returns
    -------
    coeffs : float
    """
    if (method == "S80" or method == "S88" or method == "LY04" or
sbiri's avatar
sbiri committed
372 373
            method == "UA" or method == "ERA5"):
        alpha, beta, gamma = 16, 0.25, 5  # Smith 1980, from Dyer (1974)
sbiri's avatar
sbiri committed
374
    elif (method == "LP82"):
sbiri's avatar
sbiri committed
375
        alpha, beta, gamma = 16, 0.25, 7
sbiri's avatar
sbiri committed
376 377 378
    elif (method == "YT96"):
        alpha, beta, gamma = 20, 0.25, 5
    else:
sbiri's avatar
sbiri committed
379
        print("unknown method stabco: "+method)
sbiri's avatar
sbiri committed
380 381 382 383 384
    coeffs = np.zeros(3)
    coeffs[0] = alpha
    coeffs[1] = beta
    coeffs[2] = gamma
    return coeffs
sbiri's avatar
sbiri committed
385 386 387 388
# ---------------------------------------------------------------------


def psi_stab_era5(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
389 390 391 392 393 394 395 396 397 398 399 400 401 402
    """ Calculates heat stability function for stable conditions 
        for method ERA5
    
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
    
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
403 404 405
    # 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
406
    return psit
sbiri's avatar
sbiri committed
407 408 409
# ---------------------------------------------------------------------

def psi_conv(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
410 411 412 413 414 415 416 417 418 419 420 421 422
    """ Calculates heat stability function for unstable conditions
    
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
    
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
423 424
    xtmp = (1-alpha*zol)**beta
    psit = 2*np.log((1+xtmp**2)*0.5)
sbiri's avatar
sbiri committed
425
    return psit
sbiri's avatar
sbiri committed
426 427 428 429
# ---------------------------------------------------------------------


def psi_stab(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
430 431 432 433 434 435 436 437 438 439 440 441 442
    """ Calculates heat stability function for stable conditions
    
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
    
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
443
    psit = -gamma*zol
sbiri's avatar
sbiri committed
444
    return psit
sbiri's avatar
sbiri committed
445 446 447
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
448 449 450 451 452 453 454 455 456 457 458
def psit_26(zol):
    """ Computes temperature structure function as in C35
    
    Parameters
    ----------
    zol : float
        height over MO length
        
    Returns
    -------
    psi : float
sbiri's avatar
sbiri committed
459
    """
sbiri's avatar
sbiri committed
460 461 462 463
    dzol = np.where(0.35*zol > 50, 50, 0.35*zol)  # stable
    psi = -((1+0.6667*zol)**1.5+0.6667*(zol-14.28)*np.exp(-dzol)+8.525)
    k = np.where(zol < 0)  # unstable
    x = (1-15*zol[k])**0.5
sbiri's avatar
sbiri committed
464
    psik = 2*np.log((1+x)/2)
sbiri's avatar
sbiri committed
465
    x = (1-34.15*zol[k])**0.3333
sbiri's avatar
sbiri committed
466 467
    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
468
    f = zol[k]**2/(1+zol[k]**2)
sbiri's avatar
sbiri committed
469 470 471 472 473 474
    psi[k] = (1-f)*psik+f*psic
    return psi
# ---------------------------------------------------------------------


def psim_stab_era5(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
475 476 477 478 479 480 481 482 483 484 485 486 487 488
    """ Calculates momentum stability function for stable conditions 
        for method ERA5
    
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
    
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
489 490 491 492 493 494 495 496
    # 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
497 498 499 500 501 502 503 504 505 506 507 508 509
    """ Calculates momentum stability function for unstable conditions
    
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
    
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
510 511 512
    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
513
    return psim
sbiri's avatar
sbiri committed
514 515 516 517
# ---------------------------------------------------------------------


def psim_stab(zol, alpha, beta, gamma):
sbiri's avatar
sbiri committed
518 519 520 521 522 523 524 525 526 527 528 529 530
    """ Calculates momentum stability function for stable conditions
    
    Parameters
    ----------
    zol : float
        height over MO length
    alpha, beta, gamma : float
        constants given by get_stabco
    
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
531 532 533 534 535
    psim = -gamma*zol
    return psim
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
536 537 538 539 540 541 542 543 544 545 546
def psiu_26(zol):
    """ Computes velocity structure function C35
    
    Parameters
    ----------
    zol : float
        height over MO length
   
    Returns
    -------
    psi : float
sbiri's avatar
sbiri committed
547
    """
sbiri's avatar
sbiri committed
548
    dzol = np.where(0.35*zol > 50, 50, 0.35*zol)  # stable
sbiri's avatar
sbiri committed
549
    a, b, c, d = 0.7, 3/4, 5, 0.35
sbiri's avatar
sbiri committed
550 551 552
    psi = -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d)
    k = np.where(zol < 0)  # unstable
    x = (1-15*zol[k])**0.25
sbiri's avatar
sbiri committed
553
    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
554
    x = (1-10.15*zol[k])**0.3333
sbiri's avatar
sbiri committed
555 556
    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
557
    f = zol[k]**2/(1+zol[k]**2)
sbiri's avatar
sbiri committed
558 559 560 561 562
    psi[k] = (1-f)*psik+f*psic
    return psi
# ------------------------------------------------------------------------------


sbiri's avatar
sbiri committed
563 564 565 566 567 568 569 570 571 572 573
def psiu_40(zol):
    """ Computes velocity structure function C35
    
    Parameters
    ----------
    zol : float
        height over MO length
   
    Returns
    -------
    psi : float
sbiri's avatar
sbiri committed
574
    """
sbiri's avatar
sbiri committed
575
    dzol = np.where(0.35*zol > 50, 50, 0.35*zol)  # stable
sbiri's avatar
sbiri committed
576
    a, b, c, d = 1, 3/4, 5, 0.35
sbiri's avatar
sbiri committed
577 578 579
    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
580
    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
581
    x = (1-10*zol[k])**0.3333
sbiri's avatar
sbiri committed
582 583
    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
584
    f = zol[k]**2/(1+zol[k]**2)
sbiri's avatar
sbiri committed
585 586 587 588 589
    psi[k] = (1-f)*psik+f*psic
    return psi
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623
def get_skin(sst, qsea, rho, Rl, Rs, Rnl, cp, lv, usr, tsr, qsr, lat):
    """ Computes cool skin
    
    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)
    cp : float
       specific heat of air at constant pressure
    lv : float
       latent heat of vaporization
    usr : float
       friction velocity
    tsr : float
       star temperature
    qsr : float
       star humidity
    lat : float
       latitude
    
    Returns
    -------
    dter : float
    dqer : float      
    
    """
sbiri's avatar
sbiri committed
624 625 626
    # 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
627
        sst = sst-273.16
sbiri's avatar
sbiri committed
628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653
    # ************  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
    Al = 2.1e-5*(sst+3.2)**0.79
    be = 0.026
    bigc = 16*g*cpw*(rhow*visw)**3/(tcw*tcw*rho*rho)
    wetc = 0.622*lv*qsea/(287.1*(sst+273.16)**2)
    Rns = 0.945*Rs  # albedo correction
    hsb = -rho*cp*usr*tsr
    hlb = -rho*lv*usr*qsr
    qout = Rnl+hsb+hlb
    tkt = 0.001*np.ones(np.shape(sst))
    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
    xlamx = np.where(alq > 0, 6/(1+(bigc*alq/usr**4)**0.75)**0.333, 6)
    tkt = xlamx*visw/(np.sqrt(rho/rhow)*usr)
    tkt = np.where(alq > 0, np.where(tkt > 0.01, 0.01, tkt), tkt)
    dter = qcol*tkt/tcw
    dqer = wetc*dter
    return dter, dqer
# ---------------------------------------------------------------------


def get_gust(beta, Ta, usr, tsrv, zi, lat):
sbiri's avatar
sbiri committed
654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674
    """ Computes gustiness
    
    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
    
    Returns
    -------
    ug : float
    """
sbiri's avatar
sbiri committed
675 676 677 678 679 680 681 682 683 684 685 686
    if (np.max(Ta) < 200):  # convert to K if in Celsius
        Ta = Ta+273.16
    if np.isnan(zi):
        zi = 600
    g = gc(lat, None)
    Bf = -g/Ta*usr*tsrv
    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
687
def get_heights(h):
sbiri's avatar
sbiri committed
688 689 690 691 692 693 694 695 696 697 698
    """ Reads input heights for velocity, temperature and humidity
    
    Parameters
    ----------
    h : float
        input heights (m)
        
    Returns
    -------
    hh : array
    """
sbiri's avatar
sbiri committed
699 700 701 702 703
    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
704
    else:
sbiri's avatar
sbiri committed
705
        hh[0], hh[1], hh[2] = h[0], h[1], h[2]
sbiri's avatar
sbiri committed
706
    return hh
sbiri's avatar
sbiri committed
707 708 709
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
710
def svp_calc(T):
sbiri's avatar
sbiri committed
711 712 713 714 715 716 717 718 719 720 721
    """ Calculates saturation vapour pressure
    
    Parameters
    ----------
    T : float
        temperature (K)
    
    Returns
    -------
    svp : float
        in mb, pure water
sbiri's avatar
sbiri committed
722 723 724 725 726 727 728 729 730
    """
    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
731 732 733 734 735 736 737 738 739 740 741 742 743
    """ Computes specific humidity of the  sea surface air
    
    Parameters
    ----------
    sst : float
        sea surface temperature (K)
    pres : float
        pressure (mb)
    
    Returns
    -------
    qsea : float 
        (kg/kg)
sbiri's avatar
sbiri committed
744 745
    """
    if (np.nanmin(sst) < 200):  # if sst in Celsius convert to Kelvin
sbiri's avatar
sbiri committed
746
        sst = sst+273.16
sbiri's avatar
sbiri committed
747 748 749 750 751 752 753 754 755
    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
756 757 758 759 760 761 762 763 764 765 766 767 768 769
    """ Computes specific humidity following Haltiner and Martin p.24
    
    Parameters
    ----------
    Ta : float
        air temperature (K)
    rh : float
        relative humidity (%)
    pres : float
        air pressure (mb)
        
    Returns
    -------
    qair : float, (kg/kg)
sbiri's avatar
sbiri committed
770 771 772 773 774 775 776 777 778 779
    """
    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
780 781 782 783 784 785 786 787 788 789 790 791
    """ Computes saturation vapor pressure (mb) as in C35
    
    Parameters
    ----------
    T : float
        temperature ($^\\circ$\\,C)
    P : float
        pressure (mb)
    
    Returns
    -------
    exx : float
sbiri's avatar
sbiri committed
792 793 794 795 796 797 798 799 800 801
    """
    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
802 803 804 805 806 807 808 809 810 811 812 813
    """ Computes surface saturation specific humidity (g/kg) as in C35
    
    Parameters
    ----------
    T : float
        temperature ($^\\circ$\\,C)
    P : float
        pressure (mb)
        
    Returns
    -------
    qs : float
sbiri's avatar
sbiri committed
814 815 816 817 818 819 820 821 822 823 824 825
    """
    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
826 827 828 829 830 831 832 833 834 835 836 837 838
    """ Computes saturation specific humidity (g/kg) as in C35
    
    Parameters
    ----------
    T : float
        temperature ($^\circ$\,C)
    P : float
        pressure (mb)
        
    Returns
    -------
    q : float
    em : float
sbiri's avatar
sbiri committed
839 840 841 842 843 844 845 846 847 848 849 850
    """
    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
851 852 853 854 855 856 857 858 859 860 861 862 863
    """ Computes gravity relative to latitude
    
    Parameters
    ----------
    lat : float
        latitude ($^\circ$)
    lon : float
        longitude ($^\circ$, optional)
    
    Returns
    -------
    gc : float
        gravity constant (m/s^2)
sbiri's avatar
sbiri committed
864 865 866 867 868 869 870
    """
    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
871
        lon_m, lat_m = np.meshgrid(lon, lat)
sbiri's avatar
sbiri committed
872 873 874 875
    else:
        lat_m = lat
    phi = lat_m*np.pi/180.
    xx = np.sin(phi)
sbiri's avatar
sbiri committed
876 877
    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
878
    return gc
sbiri's avatar
sbiri committed
879 880 881
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
882
def visc_air(Ta):
sbiri's avatar
sbiri committed
883
    """ Computes the kinematic viscosity of dry air as a function of air temp.
sbiri's avatar
sbiri committed
884
    following Andreas (1989), CRREL Report 89-11.
sbiri's avatar
sbiri committed
885 886 887 888 889 890 891 892 893 894
    
    Parameters
    ----------
    Ta : float
        air temperature ($^\circ$\,C)
    
    Returns
    -------
    visa : float
        kinematic viscosity (m^2/s)
sbiri's avatar
sbiri committed
895 896
    """
    Ta = np.asarray(Ta)
sbiri's avatar
sbiri committed
897
    if (np.nanmin(Ta) > 200):  # if Ta in Kelvin convert to Celsius
sbiri's avatar
sbiri committed
898 899 900
        Ta = Ta-273.16
    visa = 1.326e-5 * (1 + 6.542e-3*Ta + 8.301e-6*Ta**2 - 4.84e-9*Ta**3)
    return visa