flux_subs.py 34.1 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

7
def cdn_calc(u10n, usr, Ta, 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]
15 16
    usr : float
        friction velocity      [m/s]
sbiri's avatar
sbiri committed
17
    Ta   : float
sbiri's avatar
sbiri committed
18
        air temperature        [K]
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 36
          meth == "C35" or meth == "Beljaars"): #  or meth == "C40"
        cdn = cdn_from_roughness(u10n, usr, Ta, lat, meth)
sbiri's avatar
sbiri committed
37
    elif (meth == "YT96"):
38 39 40
        # convert usr in eq. 21 to cdn to expand for low wind speeds
        cdn = np.power((0.10038+u10n*2.17e-3+np.power(u10n, 2)*2.78e-3 -
                        np.power(u10n, 3)*4.4e-5)/u10n, 2)
sbiri's avatar
sbiri committed
41
    elif (meth == "LY04"):
sbiri's avatar
sbiri committed
42
        cdn = np.where(u10n > 0.5, (0.142+2.7/u10n+u10n/13.09 -
sbiri's avatar
sbiri committed
43
                                    3.14807e-10*np.power(u10n, 6))*1e-3,
sbiri's avatar
sbiri committed
44 45
                       (0.142+2.7/0.5+0.5/13.09 -
                        3.14807e-10*np.power(0.5, 6))*1e-3)
sbiri's avatar
sbiri committed
46 47
        cdn = np.where(u10n > 33, 2.34e-3, np.copy(cdn))
        cdn = np.maximum(np.copy(cdn), 0.1e-3)
sbiri's avatar
sbiri committed
48
    else:
sbiri's avatar
sbiri committed
49
        print("unknown method cdn: "+meth)
sbiri's avatar
sbiri committed
50
    return cdn
sbiri's avatar
sbiri committed
51 52 53
# ---------------------------------------------------------------------


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

sbiri's avatar
sbiri committed
58 59 60
    Parameters
    ----------
    u10n : float
sbiri's avatar
sbiri committed
61
        neutral 10m wind speed [m/s]
62 63
    usr : float
        friction velocity      [m/s]
sbiri's avatar
sbiri committed
64
    Ta   : float
sbiri's avatar
sbiri committed
65 66
        air temperature        [K]
    lat : float                [degE]
67
        latitude
sbiri's avatar
sbiri committed
68 69
    meth : str

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


106
def cd_calc(cdn, hin, hout, psim):
sbiri's avatar
sbiri committed
107 108
    """
    Calculates drag coefficient at reference height
109 110 111 112 113

    Parameters
    ----------
    cdn : float
        neutral drag coefficient
114
    hin : float
115
        wind speed height       [m]
116
    hout : float
sbiri's avatar
sbiri committed
117
        reference height        [m]
118 119 120 121 122 123 124
    psim : float
        momentum stability function

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

sbiri's avatar
sbiri committed
129

130
def ctcqn_calc(zol, cdn, usr, zo, Ta, meth="S80"):
sbiri's avatar
sbiri committed
131
    """
sbiri's avatar
sbiri committed
132
    Calculates neutral heat and moisture exchange coefficients
sbiri's avatar
sbiri committed
133

sbiri's avatar
sbiri committed
134 135 136 137 138
    Parameters
    ----------
    zol  : float
        height over MO length
    cdn  : float
139
        neutral drag coefficient
140 141
    usr : float
        friction velocity      [m/s]
sbiri's avatar
sbiri committed
142
    zo   : float
sbiri's avatar
sbiri committed
143
        surface roughness       [m]
sbiri's avatar
sbiri committed
144
    Ta   : float
sbiri's avatar
sbiri committed
145
        air temperature         [K]
sbiri's avatar
sbiri committed
146 147
    meth : str

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


196
def ctcq_calc(cdn, cd, ctn, cqn, hin, hout, psit, psiq):
sbiri's avatar
sbiri committed
197 198
    """
    Calculates heat and moisture exchange coefficients at reference height
sbiri's avatar
sbiri committed
199

sbiri's avatar
sbiri committed
200 201 202 203 204 205 206 207 208 209
    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
210 211
    hin : float
        original temperature/humidity sensor height [m]
212
    hout : float
sbiri's avatar
sbiri committed
213
        reference height                   [m]
sbiri's avatar
sbiri committed
214 215 216 217
    psit : float
        heat stability function
    psiq : float
        moisture stability function
sbiri's avatar
sbiri committed
218

sbiri's avatar
sbiri committed
219 220 221
    Returns
    -------
    ct : float
222
       heat exchange coefficient
sbiri's avatar
sbiri committed
223
    cq : float
224
       moisture exchange coefficient
sbiri's avatar
sbiri committed
225
    """
226
    ct = (ctn*np.sqrt(cd/cdn) /
227
          (1+ctn*((np.log(hin[1]/hout[1])-psit)/(kappa*np.sqrt(cdn)))))
228
    cq = (cqn*np.sqrt(cd/cdn) /
229
          (1+cqn*((np.log(hin[2]/hout[2])-psiq)/(kappa*np.sqrt(cdn)))))
sbiri's avatar
sbiri committed
230
    return ct, cq
sbiri's avatar
sbiri committed
231 232 233
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
234
def get_stabco(meth="S80"):
sbiri's avatar
sbiri committed
235 236
    """
    Gives the coefficients \\alpha, \\beta, \\gamma for stability functions
sbiri's avatar
sbiri committed
237 238 239 240 241 242 243 244 245 246 247

    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
248
        meth == "UA" or meth == "ecmwf" or meth == "C30" or
sbiri's avatar
sbiri committed
249
        meth == "C35" or meth == "Beljaars"):
sbiri's avatar
sbiri committed
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264
        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
265
def psim_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
266 267
    """
    Calculates momentum stability function
sbiri's avatar
sbiri committed
268

sbiri's avatar
sbiri committed
269 270 271 272
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
273 274
    meth : str

sbiri's avatar
sbiri committed
275 276 277 278
    Returns
    -------
    psim : float
    """
sbiri's avatar
sbiri committed
279 280
    if (meth == "ecmwf"):
        psim = psim_ecmwf(zol)
sbiri's avatar
sbiri committed
281
    elif (meth == "C30" or meth == "C35"):
sbiri's avatar
sbiri committed
282
        psim = psiu_26(zol, meth)
283 284
    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
285
    else:
286 287
        psim = np.where(zol < 0, psim_conv(zol, meth),
                        psim_stab(zol, meth))
sbiri's avatar
sbiri committed
288
    return psim
sbiri's avatar
sbiri committed
289 290 291
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
292
def psit_calc(zol, meth="S80"):
sbiri's avatar
sbiri committed
293 294
    """
    Calculates heat stability function
sbiri's avatar
sbiri committed
295

sbiri's avatar
sbiri committed
296 297 298 299
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
300
    meth : str
301
        parameterisation method
sbiri's avatar
sbiri committed
302

sbiri's avatar
sbiri committed
303 304 305 306
    Returns
    -------
    psit : float
    """
sbiri's avatar
sbiri committed
307
    if (meth == "ecmwf"):
308
        psit = np.where(zol < 0, psi_conv(zol, meth),
sbiri's avatar
sbiri committed
309
                        psi_ecmwf(zol))
sbiri's avatar
sbiri committed
310
    elif (meth == "C30" or meth == "C35"):
sbiri's avatar
sbiri committed
311
        psit = psit_26(zol)
312 313
    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
314
    else:
315 316
        psit = np.where(zol < 0, psi_conv(zol, meth),
                        psi_stab(zol, meth))
sbiri's avatar
sbiri committed
317
    return psit
sbiri's avatar
sbiri committed
318 319 320
# ---------------------------------------------------------------------


321
def psi_Bel(zol):
sbiri's avatar
sbiri committed
322 323
    """
    Calculates momentum/heat stability function
324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341

    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
342 343 344 345
def psi_ecmwf(zol):
    """
    Calculates heat stability function for stable conditions
    for method ecmwf
sbiri's avatar
sbiri committed
346

sbiri's avatar
sbiri committed
347 348 349 350
    Parameters
    ----------
    zol : float
        height over MO length
sbiri's avatar
sbiri committed
351

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

sbiri's avatar
sbiri committed
362 363

def psit_26(zol):
sbiri's avatar
sbiri committed
364 365
    """
    Computes temperature structure function as in C35
sbiri's avatar
sbiri committed
366 367 368 369 370 371 372 373 374 375 376

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

    Returns
    -------
    psi : float
    """
    b, d = 2/3, 0.35
377 378 379 380 381 382 383 384 385 386
    dzol = np.minimum(d*zol, 50)
    psi = -1*((1+b*zol)**1.5+b*(zol-14.28)*np.exp(-dzol)+8.525)
    k = np.where(zol < 0)
    x = np.sqrt(1-15*zol[k])
    psik = 2*np.log((1+x)/2)
    x = np.power(1-34.15*zol[k], 1/3)
    psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) *
            np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3))
    f = np.power(zol[k], 2)/(1+np.power(zol[k], 2))
    psi[k] = (1-f)*psik+f*psic
sbiri's avatar
sbiri committed
387 388 389 390
    return psi
# ---------------------------------------------------------------------


391
def psi_conv(zol, meth):
sbiri's avatar
sbiri committed
392 393
    """
    Calculates heat stability function for unstable conditions
sbiri's avatar
sbiri committed
394

sbiri's avatar
sbiri committed
395 396 397 398
    Parameters
    ----------
    zol : float
        height over MO length
399 400
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
401

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


414
def psi_stab(zol, meth):
sbiri's avatar
sbiri committed
415 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
# ---------------------------------------------------------------------


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

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

sbiri's avatar
sbiri committed
445 446 447 448
    Returns
    -------
    psim : float
    """
449
    # eq (3.20, 3.22) p. 37 IFS Documentation cy46r1
sbiri's avatar
sbiri committed
450
    coeffs = get_stabco("ecmwf")
451 452
    alpha, beta = coeffs[0], coeffs[1]
    xtmp = np.power(1-alpha*zol, beta)
sbiri's avatar
sbiri committed
453
    a, b, c, d = 1, 2/3, 5, 0.35
454 455 456
    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
457 458 459 460
    return psim
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
461
def psiu_26(zol, meth):
sbiri's avatar
sbiri committed
462 463
    """
    Computes velocity structure function C35
sbiri's avatar
sbiri committed
464 465 466 467 468 469 470 471 472 473 474

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

    Returns
    -------
    psi : float
    """
    if (meth == "C30"):
475
        dzol = np.minimum(0.35*zol, 50) # stable
476 477 478 479 480 481 482 483 484 485 486
        psi = -1*((1+zol)+0.6667*(zol-14.28)*np.exp(-dzol)+8.525)
        k = np.where(zol < 0) # unstable
        x = (1-15*zol[k])**0.25
        psik = (2*np.log((1+x)/2)+np.log((1+x*x)/2)-2*np.arctan(x) +
                2*np.arctan(1))
        x = (1-10.15*zol[k])**(1/3)
        psic = (1.5*np.log((1+x+x*x)/3) -
                np.sqrt(3)*np.arctan((1+2*x)/np.sqrt(3)) +
                4*np.arctan(1)/np.sqrt(3))
        f = zol[k]**2/(1+zol[k]**2)
        psi[k] = (1-f)*psik+f*psic
487
    elif (meth == "C35"): #  or meth == "C40"
488
        dzol = np.minimum(50, 0.35*zol)  # stable
sbiri's avatar
sbiri committed
489
        a, b, c, d = 0.7, 3/4, 5, 0.35
490 491 492 493 494 495 496 497 498
        psi = -1*(a*zol+b*(zol-c/d)*np.exp(-dzol)+b*c/d)
        k = np.where(zol < 0)  # unstable
        x = np.power(1-15*zol[k], 1/4)
        psik = 2*np.log((1+x)/2)+np.log((1+x*x)/2)-2*np.arctan(x)+2*np.arctan(1)
        x = np.power(1-10.15*zol[k], 1/3)
        psic = (1.5*np.log((1+x+np.power(x, 2))/3)-np.sqrt(3) *
                np.arctan((1+2*x)/np.sqrt(3))+4*np.arctan(1)/np.sqrt(3))
        f = np.power(zol[k], 2)/(1+np.power(zol[k], 2))
        psi[k] = (1-f)*psik+f*psic
sbiri's avatar
sbiri committed
499
    return psi
500 501
#------------------------------------------------------------------------------

sbiri's avatar
sbiri committed
502 503


504
def psim_conv(zol, meth):
sbiri's avatar
sbiri committed
505 506
    """
    Calculates momentum stability function for unstable conditions
sbiri's avatar
sbiri committed
507

sbiri's avatar
sbiri committed
508 509 510 511
    Parameters
    ----------
    zol : float
        height over MO length
512 513
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
514

sbiri's avatar
sbiri committed
515 516 517 518
    Returns
    -------
    psim : float
    """
519 520
    coeffs = get_stabco(meth)
    alpha, beta = coeffs[0], coeffs[1]
521 522
    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
523
            2*np.arctan(xtmp)+np.pi/2)
sbiri's avatar
sbiri committed
524
    return psim
sbiri's avatar
sbiri committed
525 526 527
# ---------------------------------------------------------------------


528
def psim_stab(zol, meth):
sbiri's avatar
sbiri committed
529 530
    """
    Calculates momentum stability function for stable conditions
sbiri's avatar
sbiri committed
531

sbiri's avatar
sbiri committed
532 533 534 535
    Parameters
    ----------
    zol : float
        height over MO length
536 537
    meth : str
        parameterisation method
sbiri's avatar
sbiri committed
538

sbiri's avatar
sbiri committed
539 540 541 542
    Returns
    -------
    psim : float
    """
543 544
    coeffs = get_stabco(meth)
    gamma = coeffs[2]
sbiri's avatar
sbiri committed
545 546 547 548 549
    psim = -gamma*zol
    return psim
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
550 551 552 553
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
554

sbiri's avatar
sbiri committed
555 556 557
    Parameters
    ----------
    sst : float
sbiri's avatar
sbiri committed
558
        sea surface temperature      [K]
sbiri's avatar
sbiri committed
559
    qsea : float
sbiri's avatar
sbiri committed
560
        specific humidity over sea   [g/kg]
sbiri's avatar
sbiri committed
561
    rho : float
sbiri's avatar
sbiri committed
562
        density of air               [kg/m^3]
sbiri's avatar
sbiri committed
563
    Rs : float
sbiri's avatar
sbiri committed
564
        downward shortwave radiation [Wm-2]
sbiri's avatar
sbiri committed
565
    Rnl : float
sbiri's avatar
sbiri committed
566
        upwelling IR radiation       [Wm-2]
sbiri's avatar
sbiri committed
567
    cp : float
sbiri's avatar
sbiri committed
568
       specific heat of air at constant pressure [J/K/kg]
sbiri's avatar
sbiri committed
569
    lv : float
sbiri's avatar
sbiri committed
570 571 572
       latent heat of vaporization   [J/kg]
    delta : float
       cool skin thickness           [m]
sbiri's avatar
sbiri committed
573
    usr : float
sbiri's avatar
sbiri committed
574
       friction velocity             [m/s]
sbiri's avatar
sbiri committed
575
    tsr : float
sbiri's avatar
sbiri committed
576
       star temperature              [K]
sbiri's avatar
sbiri committed
577
    qsr : float
sbiri's avatar
sbiri committed
578
       star humidity                 [g/kg]
sbiri's avatar
sbiri committed
579
    lat : float
sbiri's avatar
sbiri committed
580
       latitude                      [degE]
sbiri's avatar
sbiri committed
581

sbiri's avatar
sbiri committed
582 583 584
    Returns
    -------
    dter : float
sbiri's avatar
sbiri committed
585
        cool skin correction         [K]
sbiri's avatar
sbiri committed
586
    dqer : float
sbiri's avatar
sbiri committed
587 588 589
       humidity corrction            [g/kg]
    delta : float
       cool skin thickness           [m]
sbiri's avatar
sbiri committed
590
    """
sbiri's avatar
sbiri committed
591 592
    # coded following Saunders (1967) with lambda = 6
    g = gc(lat, None)
sbiri's avatar
sbiri committed
593 594
    if (np.nanmin(sst) > 200):  # if sst in Kelvin convert to Celsius
        sst = sst-CtoK
sbiri's avatar
sbiri committed
595 596 597 598
    # ************  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
599
    aw = 2.1e-5*np.power(np.maximum(sst+3.2, 0), 0.79)
sbiri's avatar
sbiri committed
600 601
    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
602
    Rns = 0.945*Rs  # albedo correction
sbiri's avatar
sbiri committed
603 604 605 606 607 608
    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
609
    xlamx = 6*np.ones(sst.shape)
sbiri's avatar
sbiri committed
610
    xlamx = np.where(alq > 0, 6/(1+(bigc*alq/usr**4)**0.75)**0.333, 6)
sbiri's avatar
sbiri committed
611
    delta = np.where(alq > 0, xlamx*visw/(np.sqrt(rho/rhow)*usr),
sbiri's avatar
sbiri committed
612 613
                   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
614
    dter = qcol*delta/tcw
sbiri's avatar
sbiri committed
615
    dqer = wetc*dter
sbiri's avatar
sbiri committed
616 617 618 619 620 621 622 623 624 625 626 627 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 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
    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
705 706 707
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
708 709 710 711 712 713 714 715 716 717 718
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
719
    Rnl : float
sbiri's avatar
sbiri committed
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750
        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
751
    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
752 753 754 755 756 757 758 759 760 761 762
    # *** 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
763
    for jwl in range(10): # itteration to solve implicitely equation for warm layer
sbiri's avatar
sbiri committed
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 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
        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
848
def get_gust(beta, Ta, usr, tsrv, zi, lat):
sbiri's avatar
sbiri committed
849 850
    """
    Computes gustiness
sbiri's avatar
sbiri committed
851

sbiri's avatar
sbiri committed
852 853 854 855 856
    Parameters
    ----------
    beta : float
        constant
    Ta : float
sbiri's avatar
sbiri committed
857
        air temperature   [K]
sbiri's avatar
sbiri committed
858
    usr : float
sbiri's avatar
sbiri committed
859
        friction velocity [m/s]
sbiri's avatar
sbiri committed
860
    tsrv : float
sbiri's avatar
sbiri committed
861
        star virtual temperature of air [K]
sbiri's avatar
sbiri committed
862
    zi : int
sbiri's avatar
sbiri committed
863
        scale height of the boundary layer depth [m]
sbiri's avatar
sbiri committed
864 865
    lat : float
        latitude
sbiri's avatar
sbiri committed
866

sbiri's avatar
sbiri committed
867 868
    Returns
    -------
sbiri's avatar
sbiri committed
869
    ug : float        [m/s]
sbiri's avatar
sbiri committed
870
    """
871
    if (np.nanmax(Ta) < 200):  # convert to K if in Celsius
sbiri's avatar
sbiri committed
872 873
        Ta = Ta+273.16
    g = gc(lat, None)
sbiri's avatar
sbiri committed
874
    Bf = (-g/Ta)*usr*tsrv
sbiri's avatar
sbiri committed
875 876 877 878 879 880
    ug = np.ones(np.shape(Ta))*0.2
    ug = np.where(Bf > 0, beta*np.power(Bf*zi, 1/3), 0.2)
    return ug
# ---------------------------------------------------------------------


881 882
def get_L(L, lat, usr, tsr, qsr, hin, Ta, sst, qair, qsea, wind, monob, psim,
          meth):
sbiri's avatar
sbiri committed
883 884 885 886 887 888 889
    """
    calculates Monin-Obukhov length and virtual star temperature

    Parameters
    ----------
    L : int
        Monin-Obukhov length definition options
sbiri's avatar
sbiri committed
890 891
        "S80"  : default for S80, S88, LP82, YT96 and LY04
        "ecmwf" : following ecmwf (IFS Documentation cy46r1), default for ecmwf
sbiri's avatar
sbiri committed
892 893 894 895 896 897 898 899 900 901
    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
902
    hin : float
sbiri's avatar
sbiri committed
903 904 905 906 907
        sensor heights (m)
    Ta : float
        air temperature (K)
    sst : float
        sea surface temperature (K)
sbiri's avatar
sbiri committed
908 909 910
    qair : float
        air specific humidity (g/kg)
    qsea : float
sbiri's avatar
sbiri committed
911
        specific humidity at sea surface (g/kg)
sbiri's avatar
sbiri committed
912 913
    q10n : float
        neutral specific humidity at 10m (g/kg)
sbiri's avatar
sbiri committed
914 915 916 917 918
    wind : float
        wind speed (m/s)
    monob : float
        Monin-Obukhov length from previous iteration step (m)
    meth : str
919
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96",
sbiri's avatar
sbiri committed
920
        "UA", "LY04", "C30", "C35", "ecmwf", "Beljaars"
sbiri's avatar
sbiri committed
921 922 923

    Returns
    -------
924 925 926 927
    tsrv : float
        virtual star temperature (K)
    monob : float
        M-O length (m)
sbiri's avatar
sbiri committed
928 929

    """
sbiri's avatar
sbiri committed
930
    g = gc(lat)
931
    Rb = np.empty(sst.shape)
932 933 934 935 936 937 938 939 940 941
    # 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)
942
    if (L == "S80"):
sbiri's avatar
sbiri committed
943 944 945
        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
946
        temp = np.minimum(np.abs(temp), 10/hin[0])*np.sign(temp)
sbiri's avatar
sbiri committed
947 948
        monob = 1/np.copy(temp)
    elif (L == "ecmwf"):
sbiri's avatar
sbiri committed
949 950
        zo = (0.11*visc_air(Ta)/usr+0.018*np.power(usr, 2)/g)
        zot = 0.40*visc_air(Ta)/usr
951
        zol = (Rb*(np.power(np.log((hin[1]+zo)/zo)-psim_calc((hin[1]+zo) /
sbiri's avatar
sbiri committed
952 953
                                                              monob, meth) +
                            psim_calc(zo/monob, meth), 2) /
954 955
                   (np.log((hin[1]+zo)/zot) -
                    psit_calc((hin[1]+zo)/monob, meth) +
sbiri's avatar
sbiri committed
956
                    psit_calc(zot/monob, meth))))
sbiri's avatar
sbiri committed
957
        monob = hin[1]/zol
958
    return tsrv, monob, Rb
sbiri's avatar
sbiri committed
959 960 961
#------------------------------------------------------------------------------


sbiri's avatar
sbiri committed
962
def get_strs(hin, monob, wind, zo, zot, zoq, dt, dq, dter, dqer, dtwl, ct, cq,
sbiri's avatar
sbiri committed
963
             cskin, wl, meth):
964 965 966 967 968
    """
    calculates star wind speed, temperature and specific humidity

    Parameters
    ----------
sbiri's avatar
sbiri committed
969
    hin : float
sbiri's avatar
sbiri committed
970
        sensor heights [m]
971
    monob : float
sbiri's avatar
sbiri committed
972
        M-O length     [m]
973
    wind : float
sbiri's avatar
sbiri committed
974
        wind speed     [m/s]
975
    zo : float
sbiri's avatar
sbiri committed
976
        momentum roughness length    [m]
977
    zot : float
sbiri's avatar
sbiri committed
978
        temperature roughness length [m]
979
    zoq : float
sbiri's avatar
sbiri committed
980
        moisture roughness length    [m]
981
    dt : float
sbiri's avatar
sbiri committed
982
        temperature difference       [K]
983
    dq : float
sbiri's avatar
sbiri committed
984
        specific humidity difference [g/kg]
985
    dter : float
sbiri's avatar
sbiri committed
986
        cskin temperature adjustment [K]
987
    dqer : float
sbiri's avatar
sbiri committed
988 989 990
        cskin q adjustment           [g/kg]
    dtwl : float
        warm layer temperature adjustment [K]
991 992 993 994 995 996
    ct : float
        temperature exchange coefficient
    cq : float
        moisture exchange coefficient
    cskin : int
        cool skin adjustment switch
sbiri's avatar
sbiri committed
997 998
    wl : int
        warm layer correction switch
999 1000
    meth : str
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96", "UA",
sbiri's avatar
sbiri committed
1001
        "LY04", "C30", "C35", "ecmwf", "Beljaars"
1002 1003 1004 1005

    Returns
    -------
    usr : float
sbiri's avatar
sbiri committed
1006
        friction wind speed [m/s]
1007
    tsr : float
sbiri's avatar
sbiri committed
1008
        star temperature    [K]
1009
    qsr : float
sbiri's avatar
sbiri committed
1010
        star specific humidity [g/kg]
1011 1012 1013

    """
    if (meth == "UA"):
sbiri's avatar
sbiri committed
1014
        usr = np.where(hin[0]/monob <= -1.574, kappa*wind /
1015 1016
                       (np.log(-1.574*monob/zo)-psim_calc(-1.574, meth) +
                        psim_calc(zo/monob, meth) +
sbiri's avatar
sbiri committed
1017
                        1.14*(np.power(-hin[0]/monob, 1/3) -
1018
                        np.power(1.574, 1/3))),
sbiri's avatar
sbiri committed
1019 1020 1021
                       np.where(hin[0]/monob < 0, kappa*wind /
                                (np.log(hin[0]/zo) -
                                 psim_calc(hin[0]/monob, meth) +
1022
                                 psim_calc(zo/monob, meth)),
sbiri's avatar
sbiri committed
1023 1024 1025
                                np.where(hin[0]/monob <= 1, kappa*wind /
                                         (np.log(hin[0]/zo) +
                                          5*hin[0]/monob-5*zo/monob),
1026 1027
                                         kappa*wind/(np.log(monob/zo)+5 -
                                                     5*zo/monob +
sbiri's avatar
sbiri committed
1028 1029
                                                     5*np.log(hin[0]/monob) +
                                                     hin[0]/monob-1))))
1030
                                # Zeng et al. 1998 (7-10)
sbiri's avatar
sbiri committed
1031
        tsr = np.where(hin[1]/monob < -0.465, kappa*(dt+dter*cskin-dtwl*wl) /
1032 1033
                       (np.log((-0.465*monob)/zot) -
                        psit_calc(-0.465, meth)+0.8*(np.power(0.465, -1/3) -
sbiri's avatar
sbiri committed
1034 1035 1036 1037
                        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) +
1038
                                 psit_calc(zot/monob, meth)),
sbiri's avatar
sbiri committed
1039
                                np.where(hin[1]/monob <= 1,
sbiri's avatar
sbiri committed
1040
                                         kappa*(dt+dter*cskin-dtwl*wl) /
sbiri's avatar
sbiri committed
1041 1042
                                         (np.log(hin[1]/zot) +
                                          5*hin[1]/monob-5*zot/monob),
sbiri's avatar
sbiri committed
1043
                                         kappa*(dt+dter*cskin-dtwl*wl) /
1044
                                         (np.log(monob/zot)+5 -
sbiri's avatar
sbiri committed
1045 1046
                                          5*zot/monob+5*np.log(hin[1]/monob) +
                                          hin[1]/monob-1))))
1047
                                # Zeng et al. 1998 (11-14)
sbiri's avatar
sbiri committed
1048
        qsr = np.where(hin[2]/monob < -0.465, kappa*(dq+dqer*cskin) /
1049 1050 1051
                       (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
1052 1053 1054 1055
                             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) +
1056
                                psit_calc(zoq/monob, meth)),
sbiri's avatar
sbiri committed
1057
                                np.where(hin[2]/monob <= 1,
1058
                                         kappa*(dq+dqer*cskin) /
sbiri's avatar
sbiri committed
1059
                                         (np.log(hin[1]/zoq)+5*hin[2]/monob -
1060 1061 1062
                                          5*zoq/monob),
                                         kappa*(dq+dqer*cskin)/
                                         (np.log(monob/zoq)+5-5*zoq/monob +
sbiri's avatar
sbiri committed
1063 1064
                                          5*np.log(hin[2]/monob) +
                                          hin[2]/monob-1))))
sbiri's avatar
sbiri committed
1065
    elif (meth == "C30" or meth == "C35"):
sbiri's avatar
sbiri committed
1066 1067 1068 1069 1070
        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))))
1071
    else:
sbiri's avatar
sbiri committed
1072
        usr = (wind*kappa/(np.log(hin[0]/zo)-psim_calc(hin[0]/monob, meth)))
sbiri's avatar
sbiri committed
1073
        tsr = ct*wind*(dt+dter*cskin-dtwl*wl)/usr
1074 1075
        qsr = cq*wind*(dq+dqer*cskin)/usr
    return usr, tsr, qsr
sbiri's avatar
sbiri committed
1076
# ---------------------------------------------------------------------