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
599
    for i in range(4):
sbiri's avatar
sbiri committed
600 601 602 603 604 605 606 607 608 609 610 611 612 613
        aw = 2.1e-5*np.power(np.maximum(sst+3.2, 0), 0.79)
        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))
        Rns = 0.945*Rs  # albedo correction
        shf = -rho*cp*usr*tsr
        lhf = -rho*lv*usr*qsr
        Qnsol = shf+lhf+Rnl
        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
        xlamx = 6*np.ones(sst.shape)
        xlamx = np.where(alq > 0, 6/(1+(bigc*alq/usr**4)**0.75)**0.333, 6)
        delta =  np.minimum(xlamx*visw/(np.sqrt(rho/rhow)*usr), 0.01)
        delta = np.where(alq > 0, xlamx*visw/(np.sqrt(rho/rhow)*usr), delta)
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
    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
667
        net thermal radiation     [Wm-2]
sbiri's avatar
sbiri committed
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
    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)
697
    for jc in range(4): # because implicit in terms of delta...
sbiri's avatar
sbiri committed
698 699 700 701 702 703 704
        # # 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
# ---------------------------------------------------------------------


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

    Parameters
    ----------
888
    L : str
sbiri's avatar
sbiri committed
889
        Monin-Obukhov length definition options
890 891 892
        "tsrv"  : default for S80, S88, LP82, YT96, UA, C30, C35 and LY04
        "Rb" : following ecmwf (IFS Documentation cy46r1), default for ecmwf
               and Beljaars
sbiri's avatar
sbiri committed
893 894 895 896 897 898 899 900
    lat : float
        latitude
    usr : float
        friction wind speed (m/s)
    tsr : float
        star temperature (K)
    qsr : float
        star specific humidity (g/kg)
sbiri's avatar
sbiri committed
901
    hin : float
sbiri's avatar
sbiri committed
902 903 904 905 906
        sensor heights (m)
    Ta : float
        air temperature (K)
    sst : float
        sea surface temperature (K)
sbiri's avatar
sbiri committed
907 908 909
    qair : float
        air specific humidity (g/kg)
    qsea : float
sbiri's avatar
sbiri committed
910
        specific humidity at sea surface (g/kg)
sbiri's avatar
sbiri committed
911 912 913 914
    wind : float
        wind speed (m/s)
    monob : float
        Monin-Obukhov length from previous iteration step (m)
sbiri's avatar
sbiri committed
915 916 917 918
    zo   : float
        surface roughness       (m)
    zot   : float
        temperature roughness length       (m)
919 920
    psim : floast
        momentum stability function
sbiri's avatar
sbiri committed
921
    meth : str
922
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96",
sbiri's avatar
sbiri committed
923
        "UA", "LY04", "C30", "C35", "ecmwf", "Beljaars"
sbiri's avatar
sbiri committed
924 925 926

    Returns
    -------
927 928 929 930
    tsrv : float
        virtual star temperature (K)
    monob : float
        M-O length (m)
931
    Rb  : float
932
       Richardson number
sbiri's avatar
sbiri committed
933 934

    """
sbiri's avatar
sbiri committed
935
    g = gc(lat)
936
    Rb = np.empty(sst.shape)
sbiri's avatar
sbiri committed
937
    # as in aerobulk One_on_L in mod_phymbl.f90
938
    tsrv = tsr*(1+0.6077*qair)+0.6077*Ta*qsr
sbiri's avatar
sbiri committed
939
    # tsrv = tsr+0.6077*Ta*qsr
940 941 942 943 944 945
    # 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 +
sbiri's avatar
sbiri committed
946
                          psim_calc(hin[1]/monob, meth)))
947
    Rb = g*dthv*hin[1]/(tv*uz*uz)
sbiri's avatar
sbiri committed
948
    if (L == "tsrv"):
sbiri's avatar
sbiri committed
949 950 951 952
        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)
        monob = 1/np.copy(temp)
sbiri's avatar
sbiri committed
953
    elif (L == "Rb"):
954
        zol = (Rb*(np.power(np.log((hin[1]+zo)/zo)-psim_calc((hin[1]+zo) /
sbiri's avatar
sbiri committed
955 956
                                                              monob, meth) +
                            psim_calc(zo/monob, meth), 2) /
957 958
                   (np.log((hin[1]+zo)/zot) -
                    psit_calc((hin[1]+zo)/monob, meth) +
sbiri's avatar
sbiri committed
959
                    psit_calc(zot/monob, meth))))
sbiri's avatar
sbiri committed
960
        monob = hin[1]/zol
961
    return tsrv, monob, Rb
sbiri's avatar
sbiri committed
962 963 964
#------------------------------------------------------------------------------


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

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

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

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