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

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

sbiri's avatar
sbiri committed
6

sbiri's avatar
sbiri committed
7
def cdn_calc(u10n, usr, Ta, grav, meth):
sbiri's avatar
sbiri committed
8
    """
sbiri's avatar
sbiri committed
9
    Calculate 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]
sbiri's avatar
sbiri committed
19 20
    grav : float
        gravity               [m/s^2]
sbiri's avatar
sbiri committed
21 22
    meth : str

sbiri's avatar
sbiri committed
23 24 25
    Returns
    -------
    cdn : float
sbiri's avatar
sbiri committed
26
    zo  : float
sbiri's avatar
sbiri committed
27
    """
28
    cdn = np.zeros(Ta.shape)*np.nan
sbiri's avatar
sbiri committed
29 30 31
    if meth == "S80":  # eq. 14 Smith 1980
        cdn = np.maximum((0.61+0.063*u10n)*0.001, (0.61+0.063*6)*0.001)
    elif meth == "LP82":
sbiri's avatar
sbiri committed
32 33
        #  Large & Pond 1981 u10n <11m/s & eq. 21 Large & Pond 1982
        cdn = np.where(u10n < 11, 1.2*0.001, (0.49+0.065*u10n)*0.001)
sbiri's avatar
sbiri committed
34
    elif meth in ["S88", "UA", "ecmwf", "C30", "C35", "Beljaars"]:
sbiri's avatar
sbiri committed
35
        cdn = cdn_from_roughness(u10n, usr, Ta, grav, meth)
sbiri's avatar
sbiri committed
36
    elif meth == "YT96":
37 38 39
        # 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
40
    elif meth == "NCAR":  # eq. 11 Large and Yeager 2009
sbiri's avatar
sbiri committed
41
        cdn = np.where(u10n > 0.5, (0.142+2.7/u10n+u10n/13.09 -
sbiri's avatar
sbiri committed
42
                                    3.14807e-10*np.power(u10n, 6))*1e-3,
sbiri's avatar
sbiri committed
43 44
                       (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
45 46
        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
47
    else:
sbiri's avatar
sbiri committed
48
        raise ValueError("Unknown method cdn: "+meth)
sbiri's avatar
sbiri committed
49 50 51

    zo = 10/np.exp(kappa/np.sqrt(cdn))
    return cdn, zo
sbiri's avatar
sbiri committed
52 53 54
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
55
def cdn_from_roughness(u10n, usr, Ta, grav, meth):
sbiri's avatar
sbiri committed
56
    """
sbiri's avatar
sbiri committed
57
    Calculate neutral drag coefficient from roughness length.
sbiri's avatar
sbiri committed
58

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

sbiri's avatar
sbiri committed
71 72 73 74
    Returns
    -------
    cdn : float
    """
sbiri's avatar
sbiri committed
75
    #  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)/grav
81
            #  smooth surface roughness length (eq. 6 in Smith 88)
sbiri's avatar
sbiri committed
82
            zs = 0.11*visc_air(Ta)/usr
sbiri's avatar
sbiri committed
83 84
            zo = zc + zs  # eq. 7 & 8 in Smith 88
        elif meth == "UA":
sbiri's avatar
sbiri committed
85
            # valid for 0<u<18m/s # Zeng et al. 1998 (24)
sbiri's avatar
sbiri committed
86
            zo = 0.013*np.power(usr, 2)/grav+0.11*visc_air(Ta)/usr
sbiri's avatar
sbiri committed
87
        elif meth == "C30":  # eq. 25 Fairall et al. 1996a
sbiri's avatar
sbiri committed
88
            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
                         np.where(u10n > 18, 0.018, a))
sbiri's avatar
sbiri committed
91
            zo = a*np.power(usr, 2)/grav+0.11*visc_air(Ta)/usr
sbiri's avatar
sbiri committed
92
        elif meth == "C35":  # eq.6-11 Edson et al. (2013)
93 94
            zo = (0.11*visc_air(Ta)/usr +
                  np.minimum(0.0017*19-0.0050, 0.0017*u10n-0.0050) *
sbiri's avatar
sbiri committed
95
                  np.power(usr, 2)/grav)
sbiri's avatar
sbiri committed
96
        elif meth in ["ecmwf", "Beljaars"]:
97
            # eq. (3.26) p.38 over sea IFS Documentation cy46r1
sbiri's avatar
sbiri committed
98
            zo = 0.018*np.power(usr, 2)/grav+0.11*visc_air(Ta)/usr
sbiri's avatar
sbiri committed
99
        else:
sbiri's avatar
sbiri committed
100 101
            raise ValueError("Unknown method for cdn_from_roughness "+meth)

102
        cdn = np.power(kappa/np.log(10/zo), 2)
103 104 105 106
    return cdn
# ---------------------------------------------------------------------


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

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

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

sbiri's avatar
sbiri committed
130

sbiri's avatar
sbiri committed
131
def ctqn_calc(corq, zol, cdn, usr, zo, Ta, meth):
sbiri's avatar
sbiri committed
132
    """
sbiri's avatar
sbiri committed
133
    Calculate neutral heat and moisture exchange coefficients.
sbiri's avatar
sbiri committed
134

sbiri's avatar
sbiri committed
135 136
    Parameters
    ----------
sbiri's avatar
sbiri committed
137 138
    corq : flag to select
           "ct" or "cq"
sbiri's avatar
sbiri committed
139 140 141
    zol  : float
        height over MO length
    cdn  : float
142
        neutral drag coefficient
143 144
    usr : float
        friction velocity      [m/s]
sbiri's avatar
sbiri committed
145
    zo   : float
sbiri's avatar
sbiri committed
146
        surface roughness       [m]
sbiri's avatar
sbiri committed
147
    Ta   : float
sbiri's avatar
sbiri committed
148
        air temperature         [K]
sbiri's avatar
sbiri committed
149 150
    meth : str

sbiri's avatar
sbiri committed
151 152
    Returns
    -------
sbiri's avatar
sbiri committed
153
    ctqn : float
sbiri's avatar
sbiri committed
154
        neutral heat exchange coefficient
sbiri's avatar
sbiri committed
155 156
    zotq : float
        roughness length for t or q
sbiri's avatar
sbiri committed
157
    """
sbiri's avatar
sbiri committed
158
    if meth in ["S80", "S88", "YT96"]:
sbiri's avatar
sbiri committed
159
        cqn = np.ones(Ta.shape)*1.20*0.001  # from S88
sbiri's avatar
sbiri committed
160
        ctn = np.ones(Ta.shape)*1.00*0.001
sbiri's avatar
sbiri committed
161 162
        zot = 10/(np.exp(np.power(kappa, 2) / (ctn*np.log(10/zo))))
        zoq = 10/(np.exp(np.power(kappa, 2) / (cqn*np.log(10/zo))))
sbiri's avatar
sbiri committed
163
    elif meth == "LP82":
sbiri's avatar
sbiri committed
164 165
        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
166 167
        zot = 10/(np.exp(np.power(kappa, 2) / (ctn*np.log(10/zo))))
        zoq = 10/(np.exp(np.power(kappa, 2) / (cqn*np.log(10/zo))))
sbiri's avatar
sbiri committed
168
    elif meth == "NCAR":
sbiri's avatar
sbiri committed
169 170 171
        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
172 173
        zot = 10/(np.exp(np.power(kappa, 2) / (ctn*np.log(10/zo))))
        zoq = 10/(np.exp(np.power(kappa, 2) / (cqn*np.log(10/zo))))
sbiri's avatar
sbiri committed
174
    elif meth == "UA":
sbiri's avatar
sbiri committed
175
        # Zeng et al. 1998 (25)
176 177
        rr = usr*zo/visc_air(Ta)
        zoq = zo/np.exp(2.67*np.power(rr, 1/4)-2.57)
178 179 180
        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
181
    elif meth == "C30":
sbiri's avatar
sbiri committed
182
        rr = zo*usr/visc_air(Ta)
183 184
        zoq = np.minimum(5e-5/np.power(rr, 0.6), 1.15e-4)  # moisture roughness
        zot = np.copy(zoq)  # temperature roughness
185 186
        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
187
    elif meth == "C35":
sbiri's avatar
sbiri committed
188
        rr = zo*usr/visc_air(Ta)
sbiri's avatar
sbiri committed
189
        zoq = np.minimum(5.8e-5/np.power(rr, 0.72), 1.6e-4)  # moisture rough.
190
        zot = np.copy(zoq)  # temperature roughness
191 192
        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
193
    elif meth in ["ecmwf", "Beljaars"]:
194
        # eq. (3.26) p.38 over sea IFS Documentation cy46r1
sbiri's avatar
sbiri committed
195 196
        zot = 0.40*visc_air(Ta)/usr
        zoq = 0.62*visc_air(Ta)/usr
197 198
        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
199
    else:
sbiri's avatar
sbiri committed
200
        raise ValueError("Unknown method ctqn: "+meth)
sbiri's avatar
sbiri committed
201 202 203 204 205 206 207 208

    if corq == "ct":
        ctqn = ctn
        zotq = zot
    elif corq == "cq":
        ctqn = cqn
        zotq = zoq
    else:
sbiri's avatar
sbiri committed
209 210
        raise ValueError("Unknown flag - should be ct or cq: "+corq)

sbiri's avatar
sbiri committed
211 212
    return ctqn, zotq
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
213

sbiri's avatar
sbiri committed
214

sbiri's avatar
sbiri committed
215
def ctq_calc(cdn, cd, ctqn, hin, hout, psitq):
sbiri's avatar
sbiri committed
216
    """
sbiri's avatar
sbiri committed
217
    Calculate heat and moisture exchange coefficients at reference height.
sbiri's avatar
sbiri committed
218

sbiri's avatar
sbiri committed
219 220 221 222 223 224
    Parameters
    ----------
    cdn : float
        neutral drag coefficient
    cd  : float
        drag coefficient at reference height
sbiri's avatar
sbiri committed
225 226
    ctqn : float
        neutral heat or moisture exchange coefficient
227
    hin : float
sbiri's avatar
sbiri committed
228
        original temperature or humidity sensor height [m]
229
    hout : float
sbiri's avatar
sbiri committed
230
        reference height                   [m]
sbiri's avatar
sbiri committed
231 232
    psitq : float
        heat or moisture stability function
sbiri's avatar
sbiri committed
233

sbiri's avatar
sbiri committed
234 235
    Returns
    -------
sbiri's avatar
sbiri committed
236 237
    ctq : float
       heat or moisture exchange coefficient
sbiri's avatar
sbiri committed
238
    """
sbiri's avatar
sbiri committed
239
    ctq = (ctqn*np.sqrt(cd/cdn) /
sbiri's avatar
sbiri committed
240
           (1+ctqn*((np.log(hin/hout)-psitq)/(kappa*np.sqrt(cdn)))))
sbiri's avatar
sbiri committed
241

sbiri's avatar
sbiri committed
242 243
    return ctq
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
244

sbiri's avatar
sbiri committed
245

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

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

    Returns
    -------
    coeffs : float
    """
    alpha, beta, gamma = 0, 0, 0
sbiri's avatar
sbiri committed
259
    if meth in ["S80", "S88", "NCAR", "UA", "ecmwf", "C30", "C35", "Beljaars"]:
sbiri's avatar
sbiri committed
260
        alpha, beta, gamma = 16, 0.25, 5  # Smith 1980, from Dyer (1974)
sbiri's avatar
sbiri committed
261
    elif meth == "LP82":
sbiri's avatar
sbiri committed
262
        alpha, beta, gamma = 16, 0.25, 7
sbiri's avatar
sbiri committed
263
    elif meth == "YT96":
sbiri's avatar
sbiri committed
264 265
        alpha, beta, gamma = 20, 0.25, 5
    else:
sbiri's avatar
sbiri committed
266
        raise ValueError("Unknown method stabco: "+meth)
sbiri's avatar
sbiri committed
267 268 269 270 271 272 273 274
    coeffs = np.zeros(3)
    coeffs[0] = alpha
    coeffs[1] = beta
    coeffs[2] = gamma
    return coeffs
# ---------------------------------------------------------------------


sbiri's avatar
sbiri committed
275
def psim_calc(zol, meth):
sbiri's avatar
sbiri committed
276
    """
sbiri's avatar
sbiri committed
277
    Calculate momentum stability function.
sbiri's avatar
sbiri committed
278

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

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


sbiri's avatar
sbiri committed
302
def psit_calc(zol, meth):
sbiri's avatar
sbiri committed
303
    """
sbiri's avatar
sbiri committed
304
    Calculate heat stability function.
sbiri's avatar
sbiri committed
305

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

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


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

    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
352 353
def psi_ecmwf(zol):
    """
sbiri's avatar
sbiri committed
354 355 356
    Calculate heat stability function for stable conditions.

    For method ecmwf
sbiri's avatar
sbiri committed
357

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

sbiri's avatar
sbiri committed
363 364 365 366
    Returns
    -------
    psit : float
    """
367
    # eq (3.22) p. 37 IFS Documentation cy46r1
sbiri's avatar
sbiri committed
368 369
    a, b, c, d = 1, 2/3, 5, 0.35
    psit = -b*(zol-c/d)*np.exp(-d*zol)-np.power(1+(2/3)*a*zol, 1.5)-(b*c)/d+1
sbiri's avatar
sbiri committed
370
    return psit
sbiri's avatar
sbiri committed
371 372
# ---------------------------------------------------------------------

sbiri's avatar
sbiri committed
373 374

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

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

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


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

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

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


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

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

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


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

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

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


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

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

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

sbiri's avatar
sbiri committed
512 513
    return psi
# ----------------------------------------------------------------------------
sbiri's avatar
sbiri committed
514 515


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

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

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


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

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

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


sbiri's avatar
sbiri committed
562
def get_gust(beta, Ta, usr, tsrv, zi, grav):
sbiri's avatar
sbiri committed
563
    """
sbiri's avatar
sbiri committed
564
    Compute gustiness.
sbiri's avatar
sbiri committed
565

sbiri's avatar
sbiri committed
566 567 568 569 570
    Parameters
    ----------
    beta : float
        constant
    Ta : float
sbiri's avatar
sbiri committed
571
        air temperature   [K]
sbiri's avatar
sbiri committed
572
    usr : float
sbiri's avatar
sbiri committed
573
        friction velocity [m/s]
sbiri's avatar
sbiri committed
574
    tsrv : float
sbiri's avatar
sbiri committed
575
        star virtual temperature of air [K]
sbiri's avatar
sbiri committed
576
    zi : int
sbiri's avatar
sbiri committed
577
        scale height of the boundary layer depth [m]
sbiri's avatar
sbiri committed
578 579
    grav : float
        gravity
sbiri's avatar
sbiri committed
580

sbiri's avatar
sbiri committed
581 582
    Returns
    -------
sbiri's avatar
sbiri committed
583
    ug : float        [m/s]
sbiri's avatar
sbiri committed
584
    """
sbiri's avatar
sbiri committed
585
    if np.nanmax(Ta) < 200:  # convert to K if in Celsius
sbiri's avatar
sbiri committed
586
        Ta = Ta+273.16
sbiri's avatar
sbiri committed
587 588
    # minus sign to allow cube root
    Bf = (-grav/Ta)*usr*tsrv
sbiri's avatar
sbiri committed
589 590 591 592 593
    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
594

sbiri's avatar
sbiri committed
595
def get_strs(hin, monob, wind, zo, zot, zoq, dt, dq, cd, ct, cq, meth):
596
    """
sbiri's avatar
sbiri committed
597
    Calculate star wind speed, temperature and specific humidity.
598 599 600

    Parameters
    ----------
sbiri's avatar
sbiri committed
601
    hin : float
sbiri's avatar
sbiri committed
602
        sensor heights [m]
603
    monob : float
sbiri's avatar
sbiri committed
604
        M-O length     [m]
605
    wind : float
sbiri's avatar
sbiri committed
606
        wind speed     [m/s]
607
    zo : float
sbiri's avatar
sbiri committed
608
        momentum roughness length    [m]
609
    zot : float
sbiri's avatar
sbiri committed
610
        temperature roughness length [m]
611
    zoq : float
sbiri's avatar
sbiri committed
612
        moisture roughness length    [m]
613
    dt : float
sbiri's avatar
sbiri committed
614
        temperature difference       [K]
615
    dq : float
sbiri's avatar
sbiri committed
616
        specific humidity difference [g/kg]
617 618 619 620 621
    ct : float
        temperature exchange coefficient
    cq : float
        moisture exchange coefficient
    meth : str
sbiri's avatar
sbiri committed
622 623
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96",
        "UA", "NCAR", "C30", "C35", "ecmwf", "Beljaars"
624 625 626 627

    Returns
    -------
    usr : float
sbiri's avatar
sbiri committed
628
        friction wind speed [m/s]
629
    tsr : float
sbiri's avatar
sbiri committed
630
        star temperature    [K]
631
    qsr : float
sbiri's avatar
sbiri committed
632
        star specific humidity [g/kg]
633 634

    """
sbiri's avatar
sbiri committed
635
    if meth == "UA":
sbiri's avatar
sbiri committed
636 637
        # Zeng et al. 1998
        # away from extremes UA follows e.g. S80
sbiri's avatar
sbiri committed
638
        usr = wind*np.sqrt(cd)
sbiri's avatar
sbiri committed
639 640 641 642 643 644
        tsr = ct*wind*dt/usr
        qsr = cq*wind*dq/usr

        # momentum
        hol0 = hin[0]/np.copy(monob)
        # very unstable (Zeng et al. 1998 eq 7)
sbiri's avatar
sbiri committed
645 646 647 648 649 650
        usr = np.where(
            hol0 <= -1.574, wind*kappa/(np.log(-1.574*monob/zo) -
                                        psim_calc(-1.574, meth) +
                                        psim_calc(zo/monob, meth) +
                                        1.14*(np.power(-hin[0]/monob, 1/3) -
                                              np.power(1.574, 1/3))), usr)
sbiri's avatar
sbiri committed
651
        # very stable (Zeng et al. 1998 eq 10)
sbiri's avatar
sbiri committed
652 653 654
        usr = np.where(
            hol0 > 1, wind*kappa/(np.log(monob/zo)+5-5*zo/monob +
                                  5*np.log(hin[0]/monob)+hin[0]/monob-1), usr)
sbiri's avatar
sbiri committed
655 656 657 658

        # temperature
        hol1 = hin[1]/np.copy(monob)
        # very unstable (Zeng et al. 1998 eq 11)
sbiri's avatar
sbiri committed
659 660 661 662 663
        tsr = np.where(
            hol1 < -0.465, kappa*dt/(np.log((-0.465*monob)/zot) -
                                     psit_calc(-0.465, meth) +
                                     0.8*(np.power(0.465, -1/3) -
                                          np.power(-hin[1]/monob, -1/3))), tsr)
sbiri's avatar
sbiri committed
664
        # very stable (Zeng et al. 1998 eq 14)
sbiri's avatar
sbiri committed
665 666 667
        tsr = np.where(
            hol1 > 1, kappa*(dt)/(np.log(monob/zot)+5-5*zot/monob +
                                  5*np.log(hin[1]/monob)+hin[1]/monob-1), tsr)
sbiri's avatar
sbiri committed
668 669 670 671

        # humidity
        hol2 = hin[2]/monob
        # very unstable (Zeng et al. 1998 eq 11)
sbiri's avatar
sbiri committed
672 673 674 675 676 677
        qsr = np.where(
            hol2 < -0.465, kappa*dq/(np.log((-0.465*monob)/zoq) -
                                     psit_calc(-0.465, meth) +
                                     psit_calc(zoq/monob, meth) +
                                     0.8*(np.power(0.465, -1/3) -
                                          np.power(-hin[2]/monob, -1/3))), qsr)
sbiri's avatar
sbiri committed
678
        # very stable (Zeng et al. 1998 eq 14)
sbiri's avatar
sbiri committed
679 680 681
        qsr = np.where(hol2 > 1, kappa*dq/(np.log(monob/zoq)+5-5*zoq/monob +
                                           5*np.log(hin[2]/monob) +
                                           hin[2]/monob-1), qsr)
sbiri's avatar
sbiri committed
682

683
    else:
sbiri's avatar
sbiri committed
684
        usr = wind*np.sqrt(cd)
sbiri's avatar
sbiri committed
685 686
        tsr = ct*wind*dt/usr
        qsr = cq*wind*dq/usr
687
    return usr, tsr, qsr
sbiri's avatar
sbiri committed
688
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
689 690 691 692


def get_tsrv(tsr, qsr, Ta, qair):
    """
sbiri's avatar
sbiri committed
693 694
    Calculate virtual star temperature.

sbiri's avatar
sbiri committed
695 696 697 698 699 700 701 702 703 704
    Parameters
    ----------
    tsr : float
        star temperature (K)
    qsr : float
        star specific humidity (g/kg)
    Ta : float
        air temperature (K)
    qair : float
        air specific humidity (g/kg)
sbiri's avatar
sbiri committed
705

sbiri's avatar
sbiri committed
706 707 708 709
    Returns
    -------
    tsrv : float
        virtual star temperature (K)
sbiri's avatar
sbiri committed
710

sbiri's avatar
sbiri committed
711 712 713 714
    """
    # as in aerobulk One_on_L in mod_phymbl.f90
    tsrv = tsr*(1+0.6077*qair)+0.6077*Ta*qsr
    return tsrv
sbiri's avatar
sbiri committed
715

sbiri's avatar
sbiri committed
716
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
717 718


sbiri's avatar
sbiri committed
719 720
def get_Rb(grav, usr, hin_u, hin_t, tv, dtv, wind, monob, meth):
    """
sbiri's avatar
sbiri committed
721 722
    Calculate bulk Richardson number.

sbiri's avatar
sbiri committed
723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
    Parameters
    ----------
    grav : float
        acceleration due to gravity (m/s2)
    usr : float
        friction wind speed (m/s)
    hin_u : float
        u sensor height (m)
    hin_t : float
        t sensor height (m)
    tv : float
        virtual temperature (K)
    dtv : float
        virtual temperature difference, air and sea (K)
    wind : float
        wind speed (m/s)
    monob : float
        Monin-Obukhov length from previous iteration step (m)
    psim : float
        momentum stability function
    meth : str
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96",
        "UA", "NCAR", "C30", "C35", "ecmwf", "Beljaars"
sbiri's avatar
sbiri committed
746

sbiri's avatar
sbiri committed
747 748 749 750
    Returns
    -------
    Rb  : float
       Richardson number
sbiri's avatar
sbiri committed
751

sbiri's avatar
sbiri committed
752 753
    """
    # now input dtv
sbiri's avatar
sbiri committed
754 755
    # tvs = sst*(1+0.6077*qsea) # virtual SST
    # dtv = tv - tvs          # virtual air - sea temp. diff
sbiri's avatar
sbiri committed
756 757 758 759 760
    # adjust wind to t measurement height
    uz = (wind-usr/kappa*(np.log(hin_u/hin_t)-psim_calc(hin_u/monob, meth) +
                          psim_calc(hin_t/monob, meth)))
    Rb = grav*dtv*hin_t/(tv*uz*uz)
    return Rb
sbiri's avatar
sbiri committed
761

sbiri's avatar
sbiri committed
762
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
763 764


sbiri's avatar
sbiri committed
765 766
def get_LRb(Rb, hin_t, monob, zo, zot, meth):
    """
sbiri's avatar
sbiri committed
767 768
    Calculate Monin-Obukhov length following ecmwf (IFS Documentation cy46r1).

sbiri's avatar
sbiri committed
769
    default for methods ecmwf and Beljaars
sbiri's avatar
sbiri committed
770

sbiri's avatar
sbiri committed
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785
    Parameters
    ----------
    Rb  : float
       Richardson number
    hin_t : float
        t sensor height (m)
    monob : float
        Monin-Obukhov length from previous iteration step (m)
    zo   : float
        surface roughness       (m)
    zot   : float
        temperature roughness length       (m)
    meth : str
        bulk parameterisation method option: "S80", "S88", "LP82", "YT96",
        "UA", "NCAR", "C30", "C35", "ecmwf", "Beljaars"
sbiri's avatar
sbiri committed
786

sbiri's avatar
sbiri committed
787 788 789 790
    Returns
    -------
    monob : float
        M-O length (m)
sbiri's avatar
sbiri committed
791

sbiri's avatar
sbiri committed
792
    """
sbiri's avatar
sbiri committed
793 794 795 796 797
    zol = Rb*(np.power(
        np.log((hin_t+zo)/zo)-psim_calc((hin_t+zo)/monob, meth) +
        psim_calc(zo/monob, meth), 2)/(np.log((hin_t+zo)/zot) -
                                       psit_calc((hin_t+zo)/monob, meth) +
                                       psit_calc(zot/monob, meth)))
sbiri's avatar
sbiri committed
798 799
    monob = hin_t/zol
    return monob
sbiri's avatar
sbiri committed
800

sbiri's avatar
sbiri committed
801
# ---------------------------------------------------------------------
sbiri's avatar
sbiri committed
802 803


sbiri's avatar
sbiri committed
804 805
def get_Ltsrv(tsrv, grav, tv, usr):
    """
sbiri's avatar
sbiri committed
806 807
    Calculate Monin-Obukhov length from tsrv.

sbiri's avatar
sbiri committed
808 809 810 811 812 813 814 815 816 817
    Parameters
    ----------
    tsrv : float
        virtual star temperature (K)
    grav : float
        acceleration due to gravity (m/s2)
    tv : float
        virtual temperature (K)
    usr : float
        friction wind speed (m/s)
sbiri's avatar
sbiri committed
818

sbiri's avatar
sbiri committed
819 820 821 822
    Returns
    -------
    monob : float
        M-O length (m)
sbiri's avatar
sbiri committed
823

sbiri's avatar
sbiri committed
824
    """
sbiri's avatar
sbiri committed
825
    # tmp = (g*kappa*tsrv /
sbiri's avatar
sbiri committed
826
    #        np.maximum(np.power(usr, 2)*Ta*(1+0.6077*qair), 1e-9))
sbiri's avatar
sbiri committed
827 828
    # tmp = np.minimum(np.abs(tmp), 200)*np.sign(tmp)
    # monob = 1/np.copy(tmp)
sbiri's avatar
sbiri committed
829 830 831
    tsrv = np.maximum(np.abs(tsrv), 1e-9)*np.sign(tsrv)
    monob = (np.power(usr, 2)*tv)/(grav*kappa*tsrv)
    return monob
sbiri's avatar
sbiri committed
832

sbiri's avatar
sbiri committed
833
# ---------------------------------------------------------------------