flux_subs.py 35 KB
Newer Older
sbiri's avatar
sbiri committed
1
import numpy as np
2
from util_subs import (CtoK, kappa, gc, visc_air)
sbiri's avatar
sbiri committed
3

sbiri's avatar
sbiri committed
4
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
5

sbiri's avatar
sbiri committed
6

sbiri's avatar
sbiri committed
7
def cdn_calc(u10n, Ta, Tp, lat, meth="S80"):
sbiri's avatar
sbiri committed
8
    """
sbiri's avatar
sbiri committed
9
    Calculates neutral drag coefficient
sbiri's avatar
sbiri committed
10

sbiri's avatar
sbiri committed
11 12 13
    Parameters
    ----------
    u10n : float
sbiri's avatar
sbiri committed
14
        neutral 10m wind speed [m/s]
sbiri's avatar
sbiri committed
15
    Ta   : float
sbiri's avatar
sbiri committed
16
        air temperature        [K]
sbiri's avatar
sbiri committed
17 18
    Tp   : float
        wave period
19
    lat : float
sbiri's avatar
sbiri committed
20
        latitude               [degE]
sbiri's avatar
sbiri committed
21 22
    meth : str

sbiri's avatar
sbiri committed
23 24 25 26
    Returns
    -------
    cdn : float
    """
27
    cdn = np.zeros(Ta.shape)*np.nan
sbiri's avatar
sbiri committed
28
    if (meth == "S80"):
29
        cdn = (0.61+0.063*u10n)*0.001
sbiri's avatar
sbiri committed
30
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
31 32 33 34
       cdn = np.where(u10n < 4, 1.14*0.001,
                       np.where((u10n < 11) & (u10n >= 4), 1.2*0.001,
                                (0.49+0.065*u10n)*0.001))
    elif (meth == "S88" or meth == "UA" or meth == "ecmwf" or meth == "C30" or
35
          meth == "C35" or meth == "Beljaars"):
sbiri's avatar
sbiri committed
36 37
        cdn = cdn_from_roughness(u10n, Ta, None, lat, meth)
    elif (meth == "YT96"):
38
        # for u<3 YT96 convert usr in eq. 21 to cdn
sbiri's avatar
sbiri committed
39
        cdn = np.where((u10n < 6) & (u10n >= 3),
40 41 42 43 44
                        (0.29+3.1/u10n+7.7/np.power(u10n, 2))*0.001,
                        np.where((u10n >= 6),
                        (0.60 + 0.070*u10n)*0.001,
                        np.power((0.10038+u10n*2.17e-3 +
                                  np.power(u10n, 2)*2.78e-3 -
sbiri's avatar
sbiri committed
45
                                  np.power(u10n, 3)*4.4e-5)/u10n, 2)))
sbiri's avatar
sbiri committed
46
    elif (meth == "LY04"):
sbiri's avatar
sbiri committed
47
        cdn = np.where(u10n >= 0.5,
48 49
                       (0.142+(2.7/u10n)+(u10n/13.09))*0.001,
                       (0.142+(2.7/0.5)+(0.5/13.09))*0.001)
sbiri's avatar
sbiri committed
50
    else:
sbiri's avatar
sbiri committed
51
        print("unknown method cdn: "+meth)
sbiri's avatar
sbiri committed
52
    return cdn
sbiri's avatar
sbiri committed
53 54 55
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
56
def cdn_from_roughness(u10n, Ta, Tp, lat, meth="S88"):
sbiri's avatar
sbiri committed
57
    """
sbiri's avatar
sbiri committed
58
    Calculates neutral drag coefficient from roughness length
sbiri's avatar
sbiri committed
59

sbiri's avatar
sbiri committed
60 61 62
    Parameters
    ----------
    u10n : float
sbiri's avatar
sbiri committed
63
        neutral 10m wind speed [m/s]
sbiri's avatar
sbiri committed
64
    Ta   : float
sbiri's avatar
sbiri committed
65
        air temperature        [K]
sbiri's avatar
sbiri committed
66 67
    Tp   : float
        wave period
sbiri's avatar
sbiri committed
68
    lat : float                [degE]
69
        latitude
sbiri's avatar
sbiri committed
70 71
    meth : str

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


112
def cd_calc(cdn, hin, hout, psim):
sbiri's avatar
sbiri committed
113 114
    """
    Calculates drag coefficient at reference height
115 116 117 118 119

    Parameters
    ----------
    cdn : float
        neutral drag coefficient
120
    hin : float
121
        wind speed height       [m]
122
    hout : float
sbiri's avatar
sbiri committed
123
        reference height        [m]
124 125 126 127 128 129 130
    psim : float
        momentum stability function

    Returns
    -------
    cd : float
    """
131
    cd = (cdn/np.power(1+(np.sqrt(cdn)*(np.log(hin/hout)-psim))/kappa, 2))
132
    return cd
sbiri's avatar
sbiri committed
133
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
134

sbiri's avatar
sbiri committed
135

sbiri's avatar
sbiri committed
136
def ctcqn_calc(zol, cdn, u10n, zo, Ta, meth="S80"):
sbiri's avatar
sbiri committed
137
    """
sbiri's avatar
sbiri committed
138
    Calculates neutral heat and moisture exchange coefficients
sbiri's avatar
sbiri committed
139

sbiri's avatar
sbiri committed
140 141 142 143 144
    Parameters
    ----------
    zol  : float
        height over MO length
    cdn  : float
145
        neutral drag coefficient
sbiri's avatar
sbiri committed
146
    u10n : float
sbiri's avatar
sbiri committed
147
        neutral 10m wind speed  [m/s]
sbiri's avatar
sbiri committed
148
    zo   : float
sbiri's avatar
sbiri committed
149
        surface roughness       [m]
sbiri's avatar
sbiri committed
150
    Ta   : float
sbiri's avatar
sbiri committed
151
        air temperature         [K]
sbiri's avatar
sbiri committed
152 153
    meth : str

sbiri's avatar
sbiri committed
154 155 156 157 158 159 160
    Returns
    -------
    ctn : float
        neutral heat exchange coefficient
    cqn : float
        neutral moisture exchange coefficient
    """
sbiri's avatar
sbiri committed
161
    if (meth == "S80" or meth == "S88" or meth == "YT96"):
sbiri's avatar
sbiri committed
162
        cqn = np.ones(Ta.shape)*1.20*0.001  # from S88
sbiri's avatar
sbiri committed
163
        ctn = np.ones(Ta.shape)*1.00*0.001
sbiri's avatar
sbiri committed
164
    elif (meth == "LP82"):
sbiri's avatar
sbiri committed
165 166
        cqn = np.where((zol <= 0), 1.15*0.001, 1*0.001)
        ctn = np.where((zol <= 0), 1.13*0.001, 0.66*0.001)
sbiri's avatar
sbiri committed
167 168 169 170 171
    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
172
        # Zeng et al. 1998 (25)
sbiri's avatar
sbiri committed
173 174
        re=usr*zo/visc_air(Ta)
        zoq = zo/np.exp(2.67*np.power(re, 1/4)-2.57)
175 176 177
        zot = np.copy(zoq)
        cqn = np.power(kappa, 2)/(np.log(10/zo)*np.log(10/zoq))
        ctn = np.power(kappa, 2)/(np.log(10/zo)*np.log(10/zoq))
sbiri's avatar
sbiri committed
178 179 180
    elif (meth == "C30"):
        usr = np.sqrt(cdn*np.power(u10n, 2))
        rr = zo*usr/visc_air(Ta)
181 182
        zoq = np.minimum(5e-5/np.power(rr, 0.6), 1.15e-4)  # moisture roughness
        zot = np.copy(zoq)  # temperature roughness
sbiri's avatar
sbiri committed
183 184 185 186 187
        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)
188 189
        zoq = np.minimum(5.8e-5/np.power(rr, 0.72), 1.6e-4)  # moisture roughness
        zot = np.copy(zoq)  # temperature roughness
sbiri's avatar
sbiri committed
190 191
        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
192
    elif (meth == "ecmwf" or meth == "Beljaars"):
193
        # eq. (3.26) p.38 over sea IFS Documentation cy46r1
sbiri's avatar
sbiri committed
194 195 196 197 198
        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
199
    else:
sbiri's avatar
sbiri committed
200
        print("unknown method ctcqn: "+meth)
sbiri's avatar
sbiri committed
201
    return ctn, cqn
sbiri's avatar
sbiri committed
202 203 204
# ---------------------------------------------------------------------


205
def ctcq_calc(cdn, cd, ctn, cqn, ht, hq, hout, psit, psiq):
sbiri's avatar
sbiri committed
206 207
    """
    Calculates heat and moisture exchange coefficients at reference height
sbiri's avatar
sbiri committed
208

sbiri's avatar
sbiri committed
209 210 211 212 213 214 215 216 217 218
    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
sbiri's avatar
sbiri committed
219
    ht : float
sbiri's avatar
sbiri committed
220
        original temperature sensor height [m]
sbiri's avatar
sbiri committed
221
    hq : float
sbiri's avatar
sbiri committed
222
        original moisture sensor height    [m]
223
    hout : float
sbiri's avatar
sbiri committed
224
        reference height                   [m]
sbiri's avatar
sbiri committed
225 226 227 228
    psit : float
        heat stability function
    psiq : float
        moisture stability function
sbiri's avatar
sbiri committed
229

sbiri's avatar
sbiri committed
230 231 232
    Returns
    -------
    ct : float
233
       heat exchange coefficient
sbiri's avatar
sbiri committed
234
    cq : float
235
       moisture exchange coefficient
sbiri's avatar
sbiri committed
236
    """
237
    ct = (ctn*np.sqrt(cd/cdn) /
238
          (1+ctn*((np.log(ht/hout)-psit)/(kappa*np.sqrt(cdn)))))
239
    cq = (cqn*np.sqrt(cd/cdn) /
240
          (1+cqn*((np.log(hq/hout)-psiq)/(kappa*np.sqrt(cdn)))))
sbiri's avatar
sbiri committed
241
    return ct, cq
sbiri's avatar
sbiri committed
242 243 244
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
245
def get_stabco(meth="S80"):
sbiri's avatar
sbiri committed
246 247
    """
    Gives the coefficients \\alpha, \\beta, \\gamma for stability functions
sbiri's avatar
sbiri committed
248 249 250 251 252 253 254 255 256 257 258

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

    Returns
    -------
    coeffs : float
    """
    alpha, beta, gamma = 0, 0, 0
    if (meth == "S80" or meth == "S88" or meth == "LY04" or
sbiri's avatar
sbiri committed
259
        meth == "UA" or meth == "ecmwf" or meth == "C30" or
sbiri's avatar
sbiri committed
260
        meth == "C35" or meth == "Beljaars"):
sbiri's avatar
sbiri committed
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275
        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
276
def psim_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
277 278
    """
    Calculates momentum stability function
sbiri's avatar
sbiri committed
279

sbiri's avatar
sbiri committed
280 281 282 283
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
284 285
    meth : str

sbiri's avatar
sbiri committed
286 287 288 289
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
290 291
    if (meth == "ecmwf"):
        psim = psim_ecmwf(zol)
sbiri's avatar
sbiri committed
292
    elif (meth == "C30" or meth == "C35"):  # or meth == "C40"
sbiri's avatar
sbiri committed
293
        psim = psiu_26(zol, meth)
294 295
    elif (meth == "Beljaars"): # Beljaars (1997) eq. 16, 17
        psim = np.where(zol < 0, psim_conv(zol, meth), psi_Bel(zol))
sbiri's avatar
sbiri committed
296
    else:
297 298
        psim = np.where(zol < 0, psim_conv(zol, meth),
                        psim_stab(zol, meth))
sbiri's avatar
sbiri committed
299
    return psim
sbiri's avatar
sbiri committed
300 301 302
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
303
def psit_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
304 305
    """
    Calculates heat stability function
sbiri's avatar
sbiri committed
306

sbiri's avatar
sbiri committed
307 308 309 310
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
311
    meth : str
312
        parameterisation method
sbiri's avatar
sbiri committed
313

sbiri's avatar
sbiri committed
314 315 316 317
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
318
    if (meth == "ecmwf"):
319
        psit = np.where(zol < 0, psi_conv(zol, meth),
sbiri's avatar
sbiri committed
320
                        psi_ecmwf(zol))
sbiri's avatar
sbiri committed
321
    elif (meth == "C30" or meth == "C35"):
sbiri's avatar
sbiri committed
322
        psit = psit_26(zol)
323 324
    elif (meth == "Beljaars"): # Beljaars (1997) eq. 16, 17
        psit = np.where(zol < 0, psi_conv(zol, meth), psi_Bel(zol))
sbiri's avatar
sbiri committed
325
    else:
326 327
        psit = np.where(zol < 0, psi_conv(zol, meth),
                        psi_stab(zol, meth))
sbiri's avatar
sbiri committed
328
    return psit
sbiri's avatar
sbiri committed
329 330 331
# ---------------------------------------------------------------------


332
def psi_Bel(zol):
sbiri's avatar
sbiri committed
333 334
    """
    Calculates momentum/heat stability function
335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352

    Parameters
    ----------
    zol : float
        height over MO length
    meth : str
        parameterisation method

    Returns
    -------
    psit : float
    """
    a, b, c, d = 0.7, 0.75, 5, 0.35
    psi = -(a*zol+b*(zol-c/d)*np.exp(-d*zol)+b*c/d)
    return psi
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
353 354 355 356
def psi_ecmwf(zol):
    """
    Calculates heat stability function for stable conditions
    for method ecmwf
sbiri's avatar
sbiri committed
357

sbiri's avatar
sbiri committed
358 359 360 361
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
362

sbiri's avatar
sbiri committed
363 364 365 366
    Returns
    -------
    psit : float
    """
367
    # eq (3.22) p. 37 IFS Documentation cy46r1
sbiri's avatar
sbiri committed
368 369
    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
370
    return psit
sbiri's avatar
sbiri committed
371 372
# ---------------------------------------------------------------------

sbiri's avatar
sbiri committed
373 374

def psit_26(zol):
sbiri's avatar
sbiri committed
375 376
    """
    Computes temperature structure function as in C35
sbiri's avatar
sbiri committed
377 378 379 380 381 382 383 384 385 386 387

    Parameters
    ----------
    zol : float
        height over MO length

    Returns
    -------
    psi : float
    """
    b, d = 2/3, 0.35
388 389 390 391 392 393 394 395
    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)
396 397
    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
398 399 400 401
    return psi
# ---------------------------------------------------------------------


402
def psi_conv(zol, meth):
sbiri's avatar
sbiri committed
403 404
    """
    Calculates heat stability function for unstable conditions
sbiri's avatar
sbiri committed
405

sbiri's avatar
sbiri committed
406 407 408 409
    Parameters
    ----------
    zol : float
        height over MO length
410 411
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
412

sbiri's avatar
sbiri committed
413 414 415 416
    Returns
    -------
    psit : float
    """
417 418
    coeffs = get_stabco(meth)
    alpha, beta = coeffs[0], coeffs[1]
419 420
    xtmp = np.power(1-alpha*zol, beta)
    psit = 2*np.log((1+np.power(xtmp, 2))*0.5)
sbiri's avatar
sbiri committed
421
    return psit
sbiri's avatar
sbiri committed
422 423 424
# ---------------------------------------------------------------------


425
def psi_stab(zol, meth):
sbiri's avatar
sbiri committed
426 427
    """
    Calculates heat stability function for stable conditions
sbiri's avatar
sbiri committed
428

sbiri's avatar
sbiri committed
429 430 431 432
    Parameters
    ----------
    zol : float
        height over MO length
433 434
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
435

sbiri's avatar
sbiri committed
436 437 438 439
    Returns
    -------
    psit : float
    """
440 441
    coeffs = get_stabco(meth)
    gamma = coeffs[2]
sbiri's avatar
sbiri committed
442
    psit = -gamma*zol
sbiri's avatar
sbiri committed
443
    return psit
sbiri's avatar
sbiri committed
444 445 446
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
447 448 449
def psim_ecmwf(zol):
    """
    Calculates momentum stability function for method ecmwf
sbiri's avatar
sbiri committed
450

sbiri's avatar
sbiri committed
451 452 453 454
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
455

sbiri's avatar
sbiri committed
456 457 458 459
    Returns
    -------
    psim : float
    """
460
    # eq (3.20, 3.22) p. 37 IFS Documentation cy46r1
sbiri's avatar
sbiri committed
461
    coeffs = get_stabco("ecmwf")
462 463
    alpha, beta = coeffs[0], coeffs[1]
    xtmp = np.power(1-alpha*zol, beta)
sbiri's avatar
sbiri committed
464
    a, b, c, d = 1, 2/3, 5, 0.35
465 466 467
    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
468 469 470 471
    return psim
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
472
def psiu_26(zol, meth):
sbiri's avatar
sbiri committed
473 474
    """
    Computes velocity structure function C35
sbiri's avatar
sbiri committed
475 476 477 478 479 480 481 482 483 484 485

    Parameters
    ----------
    zol : float
        height over MO length

    Returns
    -------
    psi : float
    """
    if (meth == "C30"):
486 487
        dzol = np.minimum(0.35*zol, 50) # stable
        psi = np.where(zol >= 0, -((1+zol)+0.6667*(zol-14.28)*np.exp(-dzol) +
sbiri's avatar
sbiri committed
488 489 490 491 492 493 494 495 496 497
                                  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)
498 499
    elif (meth == "C35"): #  or meth == "C40"
        dzol = np.minimum(0.35*zol, 50)  # stable
sbiri's avatar
sbiri committed
500
        a, b, c, d = 0.7, 3/4, 5, 0.35
501
        psi = np.where(zol >= 0, -(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d),
sbiri's avatar
sbiri committed
502 503 504 505 506 507 508 509 510 511 512
                       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)
    return psi
513 514
#------------------------------------------------------------------------------

sbiri's avatar
sbiri committed
515 516


517
def psim_conv(zol, meth):
sbiri's avatar
sbiri committed
518 519
    """
    Calculates momentum stability function for unstable conditions
sbiri's avatar
sbiri committed
520

sbiri's avatar
sbiri committed
521 522 523 524
    Parameters
    ----------
    zol : float
        height over MO length
525 526
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
527

sbiri's avatar
sbiri committed
528 529 530 531
    Returns
    -------
    psim : float
    """
532 533
    coeffs = get_stabco(meth)
    alpha, beta = coeffs[0], coeffs[1]
534 535
    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
536
            2*np.arctan(xtmp)+np.pi/2)
sbiri's avatar
sbiri committed
537
    return psim
sbiri's avatar
sbiri committed
538 539 540
# ---------------------------------------------------------------------


541
def psim_stab(zol, meth):
sbiri's avatar
sbiri committed
542 543
    """
    Calculates momentum stability function for stable conditions
sbiri's avatar
sbiri committed
544

sbiri's avatar
sbiri committed
545 546 547 548
    Parameters
    ----------
    zol : float
        height over MO length
549 550
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
551

sbiri's avatar
sbiri committed
552 553 554 555
    Returns
    -------
    psim : float
    """
556 557
    coeffs = get_stabco(meth)
    gamma = coeffs[2]
sbiri's avatar
sbiri committed
558 559 560 561 562
    psim = -gamma*zol
    return psim
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
563 564 565 566
def cs_C35(sst, qsea, rho, Rs, Rnl, cp, lv, delta, usr, tsr, qsr, lat):
    """
    Computes cool skin
    following COARE3.5 (Fairall et al. 1996, Edson et al. 2013)
sbiri's avatar
sbiri committed
567

sbiri's avatar
sbiri committed
568 569 570
    Parameters
    ----------
    sst : float
sbiri's avatar
sbiri committed
571
        sea surface temperature      [K]
sbiri's avatar
sbiri committed
572
    qsea : float
sbiri's avatar
sbiri committed
573
        specific humidity over sea   [g/kg]
sbiri's avatar
sbiri committed
574
    rho : float
sbiri's avatar
sbiri committed
575
        density of air               [kg/m^3]
sbiri's avatar
sbiri committed
576
    Rs : float
sbiri's avatar
sbiri committed
577
        downward shortwave radiation [Wm-2]
sbiri's avatar
sbiri committed
578
    Rnl : float
sbiri's avatar
sbiri committed
579
        upwelling IR radiation       [Wm-2]
sbiri's avatar
sbiri committed
580
    cp : float
sbiri's avatar
sbiri committed
581
       specific heat of air at constant pressure [J/K/kg]
sbiri's avatar
sbiri committed
582
    lv : float
sbiri's avatar
sbiri committed
583 584 585
       latent heat of vaporization   [J/kg]
    delta : float
       cool skin thickness           [m]
sbiri's avatar
sbiri committed
586
    usr : float
sbiri's avatar
sbiri committed
587
       friction velocity             [m/s]
sbiri's avatar
sbiri committed
588
    tsr : float
sbiri's avatar
sbiri committed
589
       star temperature              [K]
sbiri's avatar
sbiri committed
590
    qsr : float
sbiri's avatar
sbiri committed
591
       star humidity                 [g/kg]
sbiri's avatar
sbiri committed
592
    lat : float
sbiri's avatar
sbiri committed
593
       latitude                      [degE]
sbiri's avatar
sbiri committed
594

sbiri's avatar
sbiri committed
595 596 597
    Returns
    -------
    dter : float
sbiri's avatar
sbiri committed
598
        cool skin correction         [K]
sbiri's avatar
sbiri committed
599
    dqer : float
sbiri's avatar
sbiri committed
600 601 602
       humidity corrction            [g/kg]
    delta : float
       cool skin thickness           [m]
sbiri's avatar
sbiri committed
603
    """
sbiri's avatar
sbiri committed
604 605
    # coded following Saunders (1967) with lambda = 6
    g = gc(lat, None)
sbiri's avatar
sbiri committed
606 607
    if (np.nanmin(sst) > 200):  # if sst in Kelvin convert to Celsius
        sst = sst-CtoK
sbiri's avatar
sbiri committed
608 609 610 611
    # ************  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
612
    aw = 2.1e-5*np.power(np.maximum(sst+3.2, 0), 0.79)
sbiri's avatar
sbiri committed
613 614
    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
615
    Rns = 0.945*Rs  # albedo correction
sbiri's avatar
sbiri committed
616 617 618 619 620 621
    shf = -rho*cp*usr*tsr
    lhf = -rho*lv*usr*qsr
    Qnsol = Rnl+shf+lhf
    fs = 0.065+11*delta-6.6e-5/delta*(1-np.exp(-delta/8.0e-4))
    qcol = Qnsol-Rns*fs
    alq = aw*qcol+0.026*lhf*cpw/lv
sbiri's avatar
sbiri committed
622
    xlamx = 6*np.ones(sst.shape)
sbiri's avatar
sbiri committed
623
    xlamx = np.where(alq > 0, 6/(1+(bigc*alq/usr**4)**0.75)**0.333, 6)
sbiri's avatar
sbiri committed
624
    delta = np.where(alq > 0, xlamx*visw/(np.sqrt(rho/rhow)*usr),
sbiri's avatar
sbiri committed
625 626
                   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
627
    dter = qcol*delta/tcw
sbiri's avatar
sbiri committed
628
    dqer = wetc*dter
sbiri's avatar
sbiri committed
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 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 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717
    return dter, dqer, delta
# ---------------------------------------------------------------------


def delta(aw, Qnsol, usr, lat):
    """
    Computes the thickness (m) of the viscous skin layer.
    Based on Fairall et al., 1996 and cited in IFS Documentation Cy46r1
    eq. 8.155 p. 164

    Parameters
    ----------
    aw : float
        thermal expansion coefficient of sea-water  [1/K]
    Qnsol : float
        part of the net heat flux actually absorbed in the warm layer [W/m^2]
    usr : float
        friction velocity in the air (u*) [m/s]
    lat : float
       latitude                      [degE]

    Returns
    -------
    delta : float
        the thickness (m) of the viscous skin layer

    """
    rhow, visw, tcw = 1025, 1e-6, 0.6
    # u* in the water
    usr_w = np.maximum(usr, 1e-4)*np.sqrt(1.2/rhow) # rhoa=1.2
    rcst_cs = 16*gc(lat)*np.power(visw, 3)/np.power(tcw, 2)
    lm = 6/np.power(1+np.power(np.maximum(Qnsol*aw*rcst_cs /
                                          np.power(usr_w, 4), 0), 3/4),
                    1/3)
    ztmp = visw/usr_w
    delta = np.where(Qnsol > 0, np.minimum(6*ztmp, 0.007), lm*ztmp)
    return delta
# ---------------------------------------------------------------------


def cs_ecmwf(rho, Rs, Rnl, cp, lv, usr, tsr, qsr, sst, lat):
    """
    cool skin adjustment based on IFS Documentation cy46r1

    Parameters
    ----------
    rho : float
        density of air               [kg/m^3]
    Rs : float
        downward solar radiation [Wm-2]
    Rnl : float
        net thermal radiaion     [Wm-2]
    cp : float
       specific heat of air at constant pressure [J/K/kg]
    lv : float
       latent heat of vaporization   [J/kg]
    usr : float
       friction velocity         [m/s]
    tsr : float
       star temperature              [K]
    qsr : float
       star humidity                 [g/kg]
    sst : float
        sea surface temperature  [K]
    lat : float
       latitude                  [degE]

    Returns
    -------
    dtc : float
        cool skin temperature correction [K]

    """
    if (np.nanmin(sst) < 200):  # if sst in Celsius convert to Kelvin
        sst = sst+CtoK
    aw = np.maximum(1e-5, 1e-5*(sst-CtoK))
    Rns = 0.945*Rs  # (1-0.066)*Rs net solar radiation (albedo correction)
    shf = -rho*cp*usr*tsr
    lhf = -rho*lv*usr*qsr
    Qnsol = shf+lhf+Rnl  # eq. 8.152
    d = delta(aw, Qnsol, usr, lat)
    for jc in range(10): # because implicit in terms of delta...
        # # fraction of the solar radiation absorbed in layer delta eq. 8.153
        # and Eq.(5) Zeng & Beljaars, 2005
        fs = 0.065+11*d-6.6e-5/d*(1-np.exp(-d/8e-4))
        Q = Qnsol-fs*Rns
        d = delta(aw, Q, usr, lat)
    dtc = Q*d/0.6  # (rhow*cw*kw)eq. 8.151
    return dtc
sbiri's avatar
sbiri committed
718 719 720
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
721 722 723 724 725 726 727 728 729 730 731
def wl_ecmwf(rho, Rs, Rnl, cp, lv, usr, tsr, qsr, sst, skt, dtc, lat):
    """
    warm layer correction following IFS Documentation cy46r1
    and aerobulk (Brodeau et al., 2016)

    Parameters
    ----------
    rho : float
        density of air               [kg/m^3]
    Rs : float
        downward solar radiation    [Wm-2]
sbiri's avatar
sbiri committed
732
    Rnl : float
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
        net thermal radiation  [Wm-2]
    cp : float
       specific heat of air at constant pressure [J/K/kg]
    lv : float
       latent heat of vaporization   [J/kg]
    usr : float
        friction velocity           [m/s]
    tsr : float
       star temperature              [K]
    qsr : float
       star humidity                 [g/kg]
    sst : float
        bulk sst                    [K]
    skt : float
        skin sst from previous step [K]
    dtc : float
        cool skin correction        [K]
    lat : float
        latitude                    [degE]

    Returns
    -------
    dtwl : float
        warm layer correction       [K]

    """
    if (np.nanmin(sst) < 200):  # if sst in Celsius convert to Kelvin
        sst = sst+CtoK
    rhow, cpw, visw, rd0 = 1025, 4190, 1e-6, 3
    Rns = 0.945*Rs
    ## Previous value of dT / warm-layer, adapted to depth:
sbiri's avatar
sbiri committed
764
    aw = 2.1e-5*np.power(np.maximum(sst-CtoK+3.2, 0), 0.79) # thermal expansion coefficient of sea-water (SST accurate enough#)
sbiri's avatar
sbiri committed
765 766 767 768 769 770 771 772 773 774 775
    # *** Rd = Fraction of solar radiation absorbed in warm layer (-)
    a1, a2, a3 = 0.28, 0.27, 0.45
    b1, b2, b3 = -71.5, -2.8, -0.06 # [m-1]
    Rd = 1-(a1*np.exp(b1*rd0)+a2*np.exp(b2*rd0)+a3*np.exp(b3*rd0))
    shf = -rho*cp*usr*tsr
    lhf = -rho*lv*usr*qsr
    Qnsol = shf+lhf+Rnl
    usrw  = np.maximum(usr, 1e-4 )*np.sqrt(1.2/rhow)   # u* in the water
    zc3 = rd0*kappa*gc(lat)/np.power(1.2/rhow, 3/2)
    zc4 = (0.3+1)*kappa/rd0
    zc5 = (0.3+1)/(0.3*rd0)
sbiri's avatar
sbiri committed
776
    for jwl in range(10): # itteration to solve implicitely equation for warm layer
sbiri's avatar
sbiri committed
777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 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
        dsst = skt-sst+dtc
        ## Buoyancy flux and stability parameter (zdl = -z/L) in water
        ZSRD = (Qnsol-Rns*Rd)/(rhow*cpw)
        ztmp = np.maximum(dsst, 0)
        zdl = np.where(ZSRD > 0, 2*(np.power(usrw, 2) *
                                    np.sqrt(ztmp/(5*rd0*gc(lat)*aw/visw)))+ZSRD,
                       np.power(usrw, 2)*np.sqrt(ztmp/(5*rd0*gc(lat)*aw/visw)))
        usr = np.maximum(usr, 1e-4 )
        zdL = zc3*aw*zdl/np.power(usr, 3)
        ## Stability function Phi_t(-z/L) (zdL is -z/L) :
        zphi = np.where(zdL > 0, (1+(5*zdL+4*np.power(zdL, 2)) /
                                  (1+3*zdL+0.25*np.power(zdL, 2)) +
                                  2/np.sqrt(1-16*(-np.abs(zdL)))),
                        1/np.sqrt(1-16*(-np.abs(zdL))))
        zz = zc4*(usrw)/zphi
        zz = np.maximum(np.abs(zz) , 1e-4)*np.sign(zz)
        dtwl = np.maximum(0, (zc5*ZSRD)/zz)
    return dtwl
#----------------------------------------------------------------------


def cs_Beljaars(rho, Rs, Rnl, cp, lv, usr, tsr, qsr, lat, Qs):
    """
    cool skin adjustment based on Beljaars (1997)
    air-sea interaction in the ECMWF model

    Parameters
    ----------
    rho : float
        density of air           [kg/m^3]
    Rs : float
        downward solar radiation [Wm-2]
    Rnl : float
        net thermal radiaion     [Wm-2]
    cp : float
       specific heat of air at constant pressure [J/K/kg]
    lv : float
       latent heat of vaporization   [J/kg]
    usr : float
       friction velocity         [m/s]
    tsr : float
       star temperature              [K]
    qsr : float
       star humidity                 [g/kg]
    lat : float
       latitude                  [degE]
    Qs : float
      radiation balance

    Returns
    -------
    Qs : float
      radiation balance
    dtc : float
        cool skin temperature correction [K]

    """
    g = gc(lat, None)
    tcw = 0.6       # thermal conductivity of water (at 20C) [W/m/K]
    visw = 1e-6     # kinetic viscosity of water [m^2/s]
    rhow = 1025     # Density of sea-water [kg/m^3]
    cpw = 4190      # specific heat capacity of water
    aw = 3e-4       # thermal expansion coefficient [K-1]
    Rns = 0.945*Rs  # net solar radiation (albedo correction)
    shf = -rho*cp*usr*tsr
    lhf = -rho*lv*usr*qsr
    Q = Rnl+shf+lhf+Qs
    xt = (16*Q*g*aw*cpw*np.power(rhow*visw, 3))/(np.power(usr, 4) *
                                                np.power(rho*tcw, 2))
    xt1 = 1+np.power(xt, 3/4)
    # Saunders const  eq. 22
    ls = np.where(Q > 0, 6/np.power(xt1, 1/3), 6)
    delta = np.where(Q > 0 , (ls*visw)/(np.sqrt(rho/rhow)*usr),
                     np.where((ls*visw)/(np.sqrt(rho/rhow)*usr) > 0.01, 0.01,
                              (ls*visw)/(np.sqrt(rho/rhow)*usr)))  # eq. 21
    # fraction of the solar radiation absorbed in layer delta
    fc = 0.065+11*delta-6.6e-5*(1-np.exp(-delta/0.0008))/delta
    Qs = fc*Rns
    Q = Rnl+shf+lhf+Qs
    dtc = Q*delta/tcw
    return Qs, dtc
#----------------------------------------------------------------------


sbiri's avatar
sbiri committed
861
def get_gust(beta, Ta, usr, tsrv, zi, lat):
sbiri's avatar
sbiri committed
862 863
    """
    Computes gustiness
sbiri's avatar
sbiri committed
864

sbiri's avatar
sbiri committed
865 866 867 868 869
    Parameters
    ----------
    beta : float
        constant
    Ta : float
sbiri's avatar
sbiri committed
870
        air temperature   [K]
sbiri's avatar
sbiri committed
871
    usr : float
sbiri's avatar
sbiri committed
872
        friction velocity [m/s]
sbiri's avatar
sbiri committed
873
    tsrv : float
sbiri's avatar
sbiri committed
874
        star virtual temperature of air [K]
sbiri's avatar
sbiri committed
875
    zi : int
sbiri's avatar
sbiri committed
876
        scale height of the boundary layer depth [m]
sbiri's avatar
sbiri committed
877 878
    lat : float
        latitude
sbiri's avatar
sbiri committed
879

sbiri's avatar
sbiri committed
880 881
    Returns
    -------
sbiri's avatar
sbiri committed
882
    ug : float        [m/s]
sbiri's avatar
sbiri committed
883
    """
884
    if (np.nanmax(Ta) < 200):  # convert to K if in Celsius
sbiri's avatar
sbiri committed
885 886
        Ta = Ta+273.16
    g = gc(lat, None)
sbiri's avatar
sbiri committed
887
    Bf = (-g/Ta)*usr*tsrv
sbiri's avatar
sbiri committed
888 889 890 891 892 893
    ug = np.ones(np.shape(Ta))*0.2
    ug = np.where(Bf > 0, beta*np.power(Bf*zi, 1/3), 0.2)
    return ug
# ---------------------------------------------------------------------


894 895
def get_L(L, lat, usr, tsr, qsr, hin, Ta, sst, qair, qsea, wind, monob, psim,
          meth):
sbiri's avatar
sbiri committed
896 897 898 899 900 901 902
    """
    calculates Monin-Obukhov length and virtual star temperature

    Parameters
    ----------
    L : int
        Monin-Obukhov length definition options
sbiri's avatar
sbiri committed
903 904
        "S80"  : default for S80, S88, LP82, YT96 and LY04
        "ecmwf" : following ecmwf (IFS Documentation cy46r1), default for ecmwf
sbiri's avatar
sbiri committed
905 906 907 908 909 910 911 912 913 914
    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)
sbiri's avatar
sbiri committed
915
    hin : float
sbiri's avatar
sbiri committed
916 917 918 919 920
        sensor heights (m)
    Ta : float
        air temperature (K)
    sst : float
        sea surface temperature (K)
sbiri's avatar
sbiri committed
921 922 923
    qair : float
        air specific humidity (g/kg)
    qsea : float
sbiri's avatar
sbiri committed
924
        specific humidity at sea surface (g/kg)
sbiri's avatar
sbiri committed
925 926
    q10n : float
        neutral specific humidity at 10m (g/kg)
sbiri's avatar
sbiri committed
927 928 929 930 931
    wind : float
        wind speed (m/s)
    monob : float
        Monin-Obukhov length from previous iteration step (m)
    meth : str
932
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96",
sbiri's avatar
sbiri committed
933
        "UA", "LY04", "C30", "C35", "ecmwf", "Beljaars"
sbiri's avatar
sbiri committed
934 935 936

    Returns
    -------
937 938 939 940
    tsrv : float
        virtual star temperature (K)
    monob : float
        M-O length (m)
sbiri's avatar
sbiri committed
941 942

    """
sbiri's avatar
sbiri committed
943
    g = gc(lat)
944
    Rb = np.empty(sst.shape)
945 946 947 948 949 950 951 952 953 954
    # as in NCAR, LY04
    tsrv = tsr*(1+0.6077*qair)+0.6077*Ta*qsr
    # from eq. 3.24 ifs Cy46r1 pp. 37
    thvs = sst*(1+0.6077*qsea) # virtual SST
    dthv = (Ta-sst)*(1+0.6077*qair)+0.6077*Ta*(qair-qsea)
    tv = 0.5*(thvs+Ta*(1+0.6077*qair)) # estimate tv within surface layer
    # adjust wind to T sensor's height
    uz = (wind-usr/kappa*(np.log(hin[0]/hin[1])-psim +
                            psim_calc(hin[1]/monob, meth)))
    Rb = g*dthv*hin[1]/(tv*uz*uz)
955
    if (L == "S80"):
sbiri's avatar
sbiri committed
956 957 958
        temp = (g*kappa*tsrv /
                np.maximum(np.power(usr, 2)*Ta*(1+0.6077*qair), 1e-9))
        temp = np.minimum(np.abs(temp), 200)*np.sign(temp)
sbiri's avatar
sbiri committed
959
        temp = np.minimum(np.abs(temp), 10/hin[0])*np.sign(temp)
sbiri's avatar
sbiri committed
960 961
        monob = 1/np.copy(temp)
    elif (L == "ecmwf"):
sbiri's avatar
sbiri committed
962 963
        zo = (0.11*visc_air(Ta)/usr+0.018*np.power(usr, 2)/g)
        zot = 0.40*visc_air(Ta)/usr
964
        zol = (Rb*(np.power(np.log((hin[1]+zo)/zo)-psim_calc((hin[1]+zo) /
sbiri's avatar
sbiri committed
965 966
                                                              monob, meth) +
                            psim_calc(zo/monob, meth), 2) /
967 968
                   (np.log((hin[1]+zo)/zot) -
                    psit_calc((hin[1]+zo)/monob, meth) +
sbiri's avatar
sbiri committed
969
                    psit_calc(zot/monob, meth))))
sbiri's avatar
sbiri committed
970
        monob = hin[1]/zol
971
    return tsrv, monob, Rb
sbiri's avatar
sbiri committed
972 973 974
#------------------------------------------------------------------------------


sbiri's avatar
sbiri committed
975
def get_strs(hin, monob, wind, zo, zot, zoq, dt, dq, dter, dqer, dtwl, ct, cq,
sbiri's avatar
sbiri committed
976
             cskin, wl, meth):
977 978 979 980 981
    """
    calculates star wind speed, temperature and specific humidity

    Parameters
    ----------
sbiri's avatar
sbiri committed
982
    hin : float
sbiri's avatar
sbiri committed
983
        sensor heights [m]
984
    monob : float
sbiri's avatar
sbiri committed
985
        M-O length     [m]
986
    wind : float
sbiri's avatar
sbiri committed
987
        wind speed     [m/s]
988
    zo : float
sbiri's avatar
sbiri committed
989
        momentum roughness length    [m]
990
    zot : float
sbiri's avatar
sbiri committed
991
        temperature roughness length [m]
992
    zoq : float
sbiri's avatar
sbiri committed
993
        moisture roughness length    [m]
994
    dt : float
sbiri's avatar
sbiri committed
995
        temperature difference       [K]
996
    dq : float
sbiri's avatar
sbiri committed
997
        specific humidity difference [g/kg]
998
    dter : float
sbiri's avatar
sbiri committed
999
        cskin temperature adjustment [K]
1000
    dqer : float
sbiri's avatar
sbiri committed
1001 1002 1003
        cskin q adjustment           [g/kg]
    dtwl : float
        warm layer temperature adjustment [K]
1004 1005 1006 1007 1008 1009
    ct : float
        temperature exchange coefficient
    cq : float
        moisture exchange coefficient
    cskin : int
        cool skin adjustment switch
sbiri's avatar
sbiri committed
1010 1011
    wl : int
        warm layer correction switch
1012 1013
    meth : str
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96", "UA",
sbiri's avatar
sbiri committed
1014
        "LY04", "C30", "C35", "ecmwf", "Beljaars"
1015 1016 1017 1018

    Returns
    -------
    usr : float
sbiri's avatar
sbiri committed
1019
        friction wind speed [m/s]
1020
    tsr : float
sbiri's avatar
sbiri committed
1021
        star temperature    [K]
1022
    qsr : float
sbiri's avatar
sbiri committed
1023
        star specific humidity [g/kg]
1024 1025 1026

    """
    if (meth == "UA"):
sbiri's avatar
sbiri committed
1027
        usr = np.where(hin[0]/monob <= -1.574, kappa*wind /
1028 1029
                       (np.log(-1.574*monob/zo)-psim_calc(-1.574, meth) +
                        psim_calc(zo/monob, meth) +
sbiri's avatar
sbiri committed
1030
                        1.14*(np.power(-hin[0]/monob, 1/3) -
1031
                        np.power(1.574, 1/3))),
sbiri's avatar
sbiri committed
1032 1033 1034
                       np.where(hin[0]/monob < 0, kappa*wind /
                                (np.log(hin[0]/zo) -
                                 psim_calc(hin[0]/monob, meth) +
1035
                                 psim_calc(zo/monob, meth)),
sbiri's avatar
sbiri committed
1036 1037 1038
                                np.where(hin[0]/monob <= 1, kappa*wind /
                                         (np.log(hin[0]/zo) +
                                          5*hin[0]/monob-5*zo/monob),
1039 1040
                                         kappa*wind/(np.log(monob/zo)+5 -
                                                     5*zo/monob +
sbiri's avatar
sbiri committed
1041 1042
                                                     5*np.log(hin[0]/monob) +
                                                     hin[0]/monob-1))))
1043
                                # Zeng et al. 1998 (7-10)
sbiri's avatar
sbiri committed
1044
        tsr = np.where(hin[1]/monob < -0.465, kappa*(dt+dter*cskin-dtwl*wl) /
1045 1046
                       (np.log((-0.465*monob)/zot) -
                        psit_calc(-0.465, meth)+0.8*(np.power(0.465, -1/3) -
sbiri's avatar
sbiri committed
1047 1048 1049 1050
                        np.power(-hin[1]/monob, -1/3))),
                       np.where(hin[1]/monob < 0, kappa*(dt+dter*cskin-dtwl*wl) /
                                (np.log(hin[1]/zot) -
                                 psit_calc(hin[1]/monob, meth) +
1051
                                 psit_calc(zot/monob, meth)),
sbiri's avatar
sbiri committed
1052
                                np.where(hin[1]/monob <= 1,
sbiri's avatar
sbiri committed
1053
                                         kappa*(dt+dter*cskin-dtwl*wl) /
sbiri's avatar
sbiri committed
1054 1055
                                         (np.log(hin[1]/zot) +
                                          5*hin[1]/monob-5*zot/monob),
sbiri's avatar
sbiri committed
1056
                                         kappa*(dt+dter*cskin-dtwl*wl) /
1057
                                         (np.log(monob/zot)+5 -
sbiri's avatar
sbiri committed
1058 1059
                                          5*zot/monob+5*np.log(hin[1]/monob) +
                                          hin[1]/monob-1))))
1060
                                # Zeng et al. 1998 (11-14)
sbiri's avatar
sbiri committed
1061
        qsr = np.where(hin[2]/monob < -0.465, kappa*(dq+dqer*cskin) /
1062 1063 1064
                       (np.log((-0.465*monob)/zoq) -
                        psit_calc(-0.465, meth)+psit_calc(zoq/monob, meth) +
                        0.8*(np.power(0.465, -1/3) -
sbiri's avatar
sbiri committed
1065 1066 1067 1068
                             np.power(-hin[2]/monob, -1/3))),
                       np.where(hin[2]/monob < 0,
                                kappa*(dq+dqer*cskin)/(np.log(hin[1]/zot) -
                                psit_calc(hin[2]/monob, meth) +
1069
                                psit_calc(zoq/monob, meth)),
sbiri's avatar
sbiri committed
1070
                                np.where(hin[2]/monob <= 1,
1071
                                         kappa*(dq+dqer*cskin) /
sbiri's avatar
sbiri committed
1072
                                         (np.log(hin[1]/zoq)+5*hin[2]/monob -
1073 1074 1075
                                          5*zoq/monob),
                                         kappa*(dq+dqer*cskin)/
                                         (np.log(monob/zoq)+5-5*zoq/monob +
sbiri's avatar
sbiri committed
1076 1077
                                          5*np.log(hin[2]/monob) +
                                          hin[2]/monob-1))))
sbiri's avatar
sbiri committed
1078
    elif (meth == "C30" or meth == "C35"):
sbiri's avatar
sbiri committed
1079 1080 1081 1082 1083
        usr = (wind*kappa/(np.log(hin[0]/zo)-psiu_26(hin[0]/monob, meth)))
        tsr = ((dt+dter*cskin-dtwl*wl)*(kappa/(np.log(hin[1]/zot) -
                                       psit_26(hin[1]/monob))))
        qsr = ((dq+dqer*cskin)*(kappa/(np.log(hin[2]/zoq) -
                                       psit_26(hin[2]/monob))))
1084
    else:
sbiri's avatar
sbiri committed
1085
        usr = (wind*kappa/(np.log(hin[0]/zo)-psim_calc(hin[0]/monob, meth)))
sbiri's avatar
sbiri committed
1086
        tsr = ct*wind*(dt+dter*cskin-dtwl*wl)/usr
1087 1088
        qsr = cq*wind*(dq+dqer*cskin)/usr
    return usr, tsr, qsr
sbiri's avatar
sbiri committed
1089
# ---------------------------------------------------------------------