Completed
Pull Request — master (#494)
by
unknown
56s
created

most_unstable_cape_cin()   B

Complexity

Conditions 1

Size

Total Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 39
rs 8.8571
1
# Copyright (c) 2008-2015 MetPy Developers.
2
# Distributed under the terms of the BSD 3-Clause License.
3
# SPDX-License-Identifier: BSD-3-Clause
4
"""Contains a collection of thermodynamic calculations."""
5
6
from __future__ import division
7
8
import warnings
9
10
import numpy as np
11
import scipy.integrate as si
12
import scipy.optimize as so
13
14
from .tools import (_greater_or_close, _less_or_close, broadcast_indices, find_intersections,
15
                    get_layer, interp)
16
from ..constants import Cp_d, epsilon, kappa, Lv, P0, Rd
17
from ..package_tools import Exporter
18
from ..units import atleast_1d, check_units, concatenate, units
19
20
exporter = Exporter(globals())
21
22
sat_pressure_0c = 6.112 * units.millibar
23
24
25
@exporter.export
26
@check_units('[pressure]', '[temperature]')
27
def potential_temperature(pressure, temperature):
28
    r"""Calculate the potential temperature.
29
30
    Uses the Poisson equation to calculation the potential temperature
31
    given `pressure` and `temperature`.
32
33
    Parameters
34
    ----------
35
    pressure : `pint.Quantity`
36
        The total atmospheric pressure
37
    temperature : `pint.Quantity`
38
        The temperature
39
40
    Returns
41
    -------
42
    `pint.Quantity`
43
        The potential temperature corresponding to the the temperature and
44
        pressure.
45
46
    See Also
47
    --------
48
    dry_lapse
49
50
    Notes
51
    -----
52
    Formula:
53
54
    .. math:: \Theta = T (P_0 / P)^\kappa
55
56
    Examples
57
    --------
58
    >>> from metpy.units import units
59
    >>> metpy.calc.potential_temperature(800. * units.mbar, 273. * units.kelvin)
60
    <Quantity(290.96653180346203, 'kelvin')>
61
62
    """
63
    return temperature * (P0 / pressure).to('dimensionless')**kappa
64
65
66
@exporter.export
67
@check_units('[pressure]', '[temperature]')
68
def dry_lapse(pressure, temperature):
69
    r"""Calculate the temperature at a level assuming only dry processes.
70
71
    This function lifts a parcel starting at `temperature`, conserving
72
    potential temperature. The starting pressure should be the first item in
73
    the `pressure` array.
74
75
    Parameters
76
    ----------
77
    pressure : `pint.Quantity`
78
        The atmospheric pressure level(s) of interest
79
    temperature : `pint.Quantity`
80
        The starting temperature
81
82
    Returns
83
    -------
84
    `pint.Quantity`
85
       The resulting parcel temperature at levels given by `pressure`
86
87
    See Also
88
    --------
89
    moist_lapse : Calculate parcel temperature assuming liquid saturation
90
                  processes
91
    parcel_profile : Calculate complete parcel profile
92
    potential_temperature
93
94
    """
95
    return temperature * (pressure / pressure[0])**kappa
96
97
98
@exporter.export
99
@check_units('[pressure]', '[temperature]')
100
def moist_lapse(pressure, temperature):
101
    r"""Calculate the temperature at a level assuming liquid saturation processes.
102
103
    This function lifts a parcel starting at `temperature`. The starting
104
    pressure should be the first item in the `pressure` array. Essentially,
105
    this function is calculating moist pseudo-adiabats.
106
107
    Parameters
108
    ----------
109
    pressure : `pint.Quantity`
110
        The atmospheric pressure level(s) of interest
111
    temperature : `pint.Quantity`
112
        The starting temperature
113
114
    Returns
115
    -------
116
    `pint.Quantity`
117
       The temperature corresponding to the the starting temperature and
118
       pressure levels.
119
120
    See Also
121
    --------
122
    dry_lapse : Calculate parcel temperature assuming dry adiabatic processes
123
    parcel_profile : Calculate complete parcel profile
124
125
    Notes
126
    -----
127
    This function is implemented by integrating the following differential
128
    equation:
129
130
    .. math:: \frac{dT}{dP} = \frac{1}{P} \frac{R_d T + L_v r_s}
131
                                {C_{pd} + \frac{L_v^2 r_s \epsilon}{R_d T^2}}
132
133
    This equation comes from [Bakhshaii2013]_.
134
135
    """
136
    def dt(t, p):
137
        t = units.Quantity(t, temperature.units)
138
        p = units.Quantity(p, pressure.units)
139
        rs = saturation_mixing_ratio(p, t)
140
        frac = ((Rd * t + Lv * rs) /
141
                (Cp_d + (Lv * Lv * rs * epsilon / (Rd * t * t)))).to('kelvin')
142
        return frac / p
143
    return units.Quantity(si.odeint(dt, atleast_1d(temperature).squeeze(),
144
                                    pressure.squeeze()).T.squeeze(), temperature.units)
145
146
147
@exporter.export
148
@check_units('[pressure]', '[temperature]', '[temperature]')
149
def lcl(pressure, temperature, dewpt, max_iters=50, eps=1e-5):
150
    r"""Calculate the lifted condensation level (LCL) using from the starting point.
151
152
    The starting state for the parcel is defined by `temperature`, `dewpt`,
153
    and `pressure`.
154
155
    Parameters
156
    ----------
157
    pressure : `pint.Quantity`
158
        The starting atmospheric pressure
159
    temperature : `pint.Quantity`
160
        The starting temperature
161
    dewpt : `pint.Quantity`
162
        The starting dew point
163
164
    Returns
165
    -------
166
    `(pint.Quantity, pint.Quantity)`
167
        The LCL pressure and temperature
168
169
    Other Parameters
170
    ----------------
171
    max_iters : int, optional
172
        The maximum number of iterations to use in calculation, defaults to 50.
173
    eps : float, optional
174
        The desired relative error in the calculated value, defaults to 1e-5.
175
176
    See Also
177
    --------
178
    parcel_profile
179
180
    Notes
181
    -----
182
    This function is implemented using an iterative approach to solve for the
183
    LCL. The basic algorithm is:
184
185
    1. Find the dew point from the LCL pressure and starting mixing ratio
186
    2. Find the LCL pressure from the starting temperature and dewpoint
187
    3. Iterate until convergence
188
189
    The function is guaranteed to finish by virtue of the `max_iters` counter.
190
191
    """
192
    def _lcl_iter(p, p0, w, t):
193
        td = dewpoint(vapor_pressure(units.Quantity(p, pressure.units), w))
194
        return (p0 * (td / t) ** (1. / kappa)).m
195
196
    w = mixing_ratio(saturation_vapor_pressure(dewpt), pressure)
197
    fp = so.fixed_point(_lcl_iter, pressure.m, args=(pressure.m, w, temperature),
198
                        xtol=eps, maxiter=max_iters)
199
    lcl_p = units.Quantity(fp, pressure.units)
200
    return lcl_p, dewpoint(vapor_pressure(lcl_p, w))
201
202
203
@exporter.export
204
@check_units('[pressure]', '[temperature]', '[temperature]')
205
def lfc(pressure, temperature, dewpt):
206
    r"""Calculate the level of free convection (LFC).
207
208
    This works by finding the first intersection of the ideal parcel path and
209
    the measured parcel temperature.
210
211
    Parameters
212
    ----------
213
    pressure : `pint.Quantity`
214
        The atmospheric pressure
215
    temperature : `pint.Quantity`
216
        The temperature at the levels given by `pressure`
217
    dewpt : `pint.Quantity`
218
        The dew point at the levels given by `pressure`
219
220
    Returns
221
    -------
222
    `pint.Quantity`
223
        The LFC pressure and temperature
224
225
    See Also
226
    --------
227
    parcel_profile
228
229
    """
230
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
231
232
    # The parcel profile and data have the same first data point, so we ignore
233
    # that point to get the real first intersection for the LFC calculation.
234
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:],
235
                              direction='increasing')
236
    # Two possible cases here: LFC = LCL, or LFC doesn't exist
237
    if len(x) == 0:
238
        if np.all(_less_or_close(ideal_profile, temperature)):
239
            # LFC doesn't exist
240
            return np.nan * pressure.units, np.nan * temperature.units
241
        else:  # LFC = LCL
242
            x, y = lcl(pressure[0], temperature[0], dewpt[0])
243
            return x, y
244
    else:
245
        return x[0], y[0]
246
247
248
@exporter.export
249
@check_units('[pressure]', '[temperature]', '[temperature]')
250
def el(pressure, temperature, dewpt):
251
    r"""Calculate the equilibrium level.
252
253
    This works by finding the last intersection of the ideal parcel path and
254
    the measured environmental temperature. If there is one or fewer intersections, there is
255
    no equilibrium level.
256
257
    Parameters
258
    ----------
259
    pressure : `pint.Quantity`
260
        The atmospheric pressure
261
    temperature : `pint.Quantity`
262
        The temperature at the levels given by `pressure`
263
    dewpt : `pint.Quantity`
264
        The dew point at the levels given by `pressure`
265
266
    Returns
267
    -------
268
    `pint.Quantity, pint.Quantity`
269
        The EL pressure and temperature
270
271
    See Also
272
    --------
273
    parcel_profile
274
275
    """
276
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
277
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:])
278
    # Find LFC to make sure that the function isn't passing off the LFC as the EL
279
    lfc_pressure, _ = lfc(pressure, temperature, dewpt)
280
281
    # If there is only one intersection, there are two possibilities:
282
    # the dataset does not contain the EL, or the LFC = LCL.
283
    if len(x) <= 1:
284
        if (ideal_profile[-1] < temperature[-1]) and (len(x) == 1):
285
            # Profile top colder than environment with one
286
            # intersection, EL exists and LFC = LCL
287
            return x[-1], y[-1]
288
        else:
289
            # The EL does not exist, either due to incomplete data
290
            # or no intersection occurring.
291
            return np.nan * pressure.units, np.nan * temperature.units
292
    else:
293
        # Checking to make sure the LFC is not returned as the EL
294
        # when data is truncated below the EL
295
        if np.isclose(x[-1], lfc_pressure, atol=1e-6):
296
            return np.nan * pressure.units, np.nan * temperature.units
297
        else:
298
            return x[-1], y[-1]
299
300
301
@exporter.export
302
@check_units('[pressure]', '[temperature]', '[temperature]')
303
def parcel_profile(pressure, temperature, dewpt):
304
    r"""Calculate the profile a parcel takes through the atmosphere.
305
306
    The parcel starts at `temperature`, and `dewpt`, lifted up
307
    dry adiabatically to the LCL, and then moist adiabatically from there.
308
    `pressure` specifies the pressure levels for the profile.
309
310
    Parameters
311
    ----------
312
    pressure : `pint.Quantity`
313
        The atmospheric pressure level(s) of interest. The first entry should be the starting
314
        point pressure.
315
    temperature : `pint.Quantity`
316
        The starting temperature
317
    dewpt : `pint.Quantity`
318
        The starting dew point
319
320
    Returns
321
    -------
322
    `pint.Quantity`
323
        The parcel temperatures at the specified pressure levels.
324
325
    See Also
326
    --------
327
    lcl, moist_lapse, dry_lapse
328
329
    """
330
    # Find the LCL
331
    l = lcl(pressure[0], temperature, dewpt)[0].to(pressure.units)
332
333
    # Find the dry adiabatic profile, *including* the LCL. We need >= the LCL in case the
334
    # LCL is included in the levels. It's slightly redundant in that case, but simplifies
335
    # the logic for removing it later.
336
    press_lower = concatenate((pressure[pressure >= l], l))
337
    t1 = dry_lapse(press_lower, temperature)
338
339
    # Find moist pseudo-adiabatic profile starting at the LCL
340
    press_upper = concatenate((l, pressure[pressure < l]))
341
    t2 = moist_lapse(press_upper, t1[-1]).to(t1.units)
342
343
    # Return LCL *without* the LCL point
344
    return concatenate((t1[:-1], t2[1:]))
345
346
347
@exporter.export
348
@check_units('[pressure]', '[dimensionless]')
349
def vapor_pressure(pressure, mixing):
350
    r"""Calculate water vapor (partial) pressure.
351
352
    Given total `pressure` and water vapor `mixing` ratio, calculates the
353
    partial pressure of water vapor.
354
355
    Parameters
356
    ----------
357
    pressure : `pint.Quantity`
358
        total atmospheric pressure
359
    mixing : `pint.Quantity`
360
        dimensionless mass mixing ratio
361
362
    Returns
363
    -------
364
    `pint.Quantity`
365
        The ambient water vapor (partial) pressure in the same units as
366
        `pressure`.
367
368
    Notes
369
    -----
370
    This function is a straightforward implementation of the equation given in many places,
371
    such as [Hobbs1977]_ pg.71:
372
373
    .. math:: e = p \frac{r}{r + \epsilon}
374
375
    See Also
376
    --------
377
    saturation_vapor_pressure, dewpoint
378
379
    """
380
    return pressure * mixing / (epsilon + mixing)
381
382
383
@exporter.export
384
@check_units('[temperature]')
385
def saturation_vapor_pressure(temperature):
386
    r"""Calculate the saturation water vapor (partial) pressure.
387
388
    Parameters
389
    ----------
390
    temperature : `pint.Quantity`
391
        The temperature
392
393
    Returns
394
    -------
395
    `pint.Quantity`
396
        The saturation water vapor (partial) pressure
397
398
    See Also
399
    --------
400
    vapor_pressure, dewpoint
401
402
    Notes
403
    -----
404
    Instead of temperature, dewpoint may be used in order to calculate
405
    the actual (ambient) water vapor (partial) pressure.
406
407
    The formula used is that from [Bolton1980]_ for T in degrees Celsius:
408
409
    .. math:: 6.112 e^\frac{17.67T}{T + 243.5}
410
411
    """
412
    # Converted from original in terms of C to use kelvin. Using raw absolute values of C in
413
    # a formula plays havoc with units support.
414
    return sat_pressure_0c * np.exp(17.67 * (temperature - 273.15 * units.kelvin) /
415
                                    (temperature - 29.65 * units.kelvin))
416
417
418
@exporter.export
419
@check_units('[temperature]', '[dimensionless]')
420
def dewpoint_rh(temperature, rh):
421
    r"""Calculate the ambient dewpoint given air temperature and relative humidity.
422
423
    Parameters
424
    ----------
425
    temperature : `pint.Quantity`
426
        Air temperature
427
    rh : `pint.Quantity`
428
        Relative humidity expressed as a ratio in the range (0, 1]
429
430
    Returns
431
    -------
432
    `pint.Quantity`
433
        The dew point temperature
434
435
    See Also
436
    --------
437
    dewpoint, saturation_vapor_pressure
438
439
    """
440
    if np.any(rh > 1.2):
441
        warnings.warn('Relative humidity >120%, ensure proper units.')
442
    return dewpoint(rh * saturation_vapor_pressure(temperature))
443
444
445
@exporter.export
446
@check_units('[pressure]')
447
def dewpoint(e):
448
    r"""Calculate the ambient dewpoint given the vapor pressure.
449
450
    Parameters
451
    ----------
452
    e : `pint.Quantity`
453
        Water vapor partial pressure
454
455
    Returns
456
    -------
457
    `pint.Quantity`
458
        Dew point temperature
459
460
    See Also
461
    --------
462
    dewpoint_rh, saturation_vapor_pressure, vapor_pressure
463
464
    Notes
465
    -----
466
    This function inverts the [Bolton1980]_ formula for saturation vapor
467
    pressure to instead calculate the temperature. This yield the following
468
    formula for dewpoint in degrees Celsius:
469
470
    .. math:: T = \frac{243.5 log(e / 6.112)}{17.67 - log(e / 6.112)}
471
472
    """
473
    val = np.log(e / sat_pressure_0c)
474
    return 0. * units.degC + 243.5 * units.delta_degC * val / (17.67 - val)
475
476
477
@exporter.export
478
@check_units('[pressure]', '[pressure]', '[dimensionless]')
479
def mixing_ratio(part_press, tot_press, molecular_weight_ratio=epsilon):
480
    r"""Calculate the mixing ratio of a gas.
481
482
    This calculates mixing ratio given its partial pressure and the total pressure of
483
    the air. There are no required units for the input arrays, other than that
484
    they have the same units.
485
486
    Parameters
487
    ----------
488
    part_press : `pint.Quantity`
489
        Partial pressure of the constituent gas
490
    tot_press : `pint.Quantity`
491
        Total air pressure
492
    molecular_weight_ratio : `pint.Quantity` or float, optional
493
        The ratio of the molecular weight of the constituent gas to that assumed
494
        for air. Defaults to the ratio for water vapor to dry air
495
        (:math:`\epsilon\approx0.622`).
496
497
    Returns
498
    -------
499
    `pint.Quantity`
500
        The (mass) mixing ratio, dimensionless (e.g. Kg/Kg or g/g)
501
502
    Notes
503
    -----
504
    This function is a straightforward implementation of the equation given in many places,
505
    such as [Hobbs1977]_ pg.73:
506
507
    .. math:: r = \epsilon \frac{e}{p - e}
508
509
    See Also
510
    --------
511
    saturation_mixing_ratio, vapor_pressure
512
513
    """
514
    return molecular_weight_ratio * part_press / (tot_press - part_press)
515
516
517
@exporter.export
518
@check_units('[pressure]', '[temperature]')
519
def saturation_mixing_ratio(tot_press, temperature):
520
    r"""Calculate the saturation mixing ratio of water vapor.
521
522
    This calculation is given total pressure and the temperature. The implementation
523
    uses the formula outlined in [Hobbs1977]_ pg.73.
524
525
    Parameters
526
    ----------
527
    tot_press: `pint.Quantity`
528
        Total atmospheric pressure
529
    temperature: `pint.Quantity`
530
        The temperature
531
532
    Returns
533
    -------
534
    `pint.Quantity`
535
        The saturation mixing ratio, dimensionless
536
537
    """
538
    return mixing_ratio(saturation_vapor_pressure(temperature), tot_press)
539
540
541
@exporter.export
542
@check_units('[pressure]', '[temperature]', '[temperature]')
543
def equivalent_potential_temperature(pressure, temperature, dewpoint):
544
    r"""Calculate equivalent potential temperature.
545
546
    This calculation must be given an air parcel's pressure, temperature, and dewpoint.
547
    The implementation uses the formula outlined in [Bolton1980]_:
548
549
    First, the LCL temperature is calculated:
550
551
    .. math:: T_{L}=\frac{1}{\frac{1}{T_{D}-56}+\frac{(T_{K}/T_{D})}{800}}+56
552
553
    Which is then used to calculate the potential temperature at the LCL:
554
555
    .. math:: \theta_{DL}=T_{K}\left(\frac{1000}{p-e}\right)^k
556
              \left(\frac{T_{K}}{T_{L}}\right)^{.28r}
557
558
    Both of these are used to calculate the final equivalent potential temperature:
559
560
    .. math:: \theta_{E}=\theta_{DL}\exp[\left(\left\frac{3036.}{T_{L}}
561
                                        -1.78\right)*r(1+.448r)\right]
562
563
    Parameters
564
    ----------
565
    pressure: `pint.Quantity`
566
        Total atmospheric pressure
567
    temperature: `pint.Quantity`
568
        Temperature of parcel
569
    dewpoint: `pint.Quantity`
570
        Dewpoint of parcel
571
572
    Returns
573
    -------
574
    `pint.Quantity`
575
        The equivalent potential temperature of the parcel
576
577
    Notes
578
    -----
579
    [Bolton1980]_ formula for Theta-e is used, since according to
580
    [Davies-Jones2009]_ it is the most accurate non-iterative formulation
581
    available.
582
583
    """
584
    t = temperature.to('kelvin').magnitude
585
    td = dewpoint.to('kelvin').magnitude
586
    p = pressure.to('hPa').magnitude
587
    e = saturation_vapor_pressure(dewpoint).to('hPa').magnitude
588
    r = saturation_mixing_ratio(pressure, dewpoint).magnitude
589
590
    t_l = 56 + 1. / (1. / (td - 56) + np.log(t / td) / 800.)
591
    th_l = t * (1000 / (p - e)) ** kappa * (t / t_l) ** (0.28 * r)
592
    th_e = th_l * np.exp((3036. / t_l - 1.78) * r * (1 + 0.448 * r))
593
594
    return th_e * units.kelvin
595
596
597
@exporter.export
598
@check_units('[temperature]', '[dimensionless]', '[dimensionless]')
599
def virtual_temperature(temperature, mixing, molecular_weight_ratio=epsilon):
600
    r"""Calculate virtual temperature.
601
602
    This calculation must be given an air parcel's temperature and mixing ratio.
603
    The implementation uses the formula outlined in [Hobbs2006]_ pg.80.
604
605
    Parameters
606
    ----------
607
    temperature: `pint.Quantity`
608
        The temperature
609
    mixing : `pint.Quantity`
610
        dimensionless mass mixing ratio
611
    molecular_weight_ratio : `pint.Quantity` or float, optional
612
        The ratio of the molecular weight of the constituent gas to that assumed
613
        for air. Defaults to the ratio for water vapor to dry air.
614
        (:math:`\epsilon\approx0.622`).
615
616
    Returns
617
    -------
618
    `pint.Quantity`
619
        The corresponding virtual temperature of the parcel
620
621
    Notes
622
    -----
623
    .. math:: T_v = T \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
624
625
    """
626
    return temperature * ((mixing + molecular_weight_ratio) /
627
                          (molecular_weight_ratio * (1 + mixing)))
628
629
630
@exporter.export
631
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
632
def virtual_potential_temperature(pressure, temperature, mixing,
633
                                  molecular_weight_ratio=epsilon):
634
    r"""Calculate virtual potential temperature.
635
636
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
637
    The implementation uses the formula outlined in [Markowski2010]_ pg.13.
638
639
    Parameters
640
    ----------
641
    pressure: `pint.Quantity`
642
        Total atmospheric pressure
643
    temperature: `pint.Quantity`
644
        The temperature
645
    mixing : `pint.Quantity`
646
        dimensionless mass mixing ratio
647
    molecular_weight_ratio : `pint.Quantity` or float, optional
648
        The ratio of the molecular weight of the constituent gas to that assumed
649
        for air. Defaults to the ratio for water vapor to dry air.
650
        (:math:`\epsilon\approx0.622`).
651
652
    Returns
653
    -------
654
    `pint.Quantity`
655
        The corresponding virtual potential temperature of the parcel
656
657
    Notes
658
    -----
659
    .. math:: \Theta_v = \Theta \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
660
661
    """
662
    pottemp = potential_temperature(pressure, temperature)
663
    return virtual_temperature(pottemp, mixing, molecular_weight_ratio)
664
665
666
@exporter.export
667
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
668
def density(pressure, temperature, mixing, molecular_weight_ratio=epsilon):
669
    r"""Calculate density.
670
671
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
672
    The implementation uses the formula outlined in [Hobbs2006]_ pg.67.
673
674
    Parameters
675
    ----------
676
    temperature: `pint.Quantity`
677
        The temperature
678
    pressure: `pint.Quantity`
679
        Total atmospheric pressure
680
    mixing : `pint.Quantity`
681
        dimensionless mass mixing ratio
682
    molecular_weight_ratio : `pint.Quantity` or float, optional
683
        The ratio of the molecular weight of the constituent gas to that assumed
684
        for air. Defaults to the ratio for water vapor to dry air.
685
        (:math:`\epsilon\approx0.622`).
686
687
    Returns
688
    -------
689
    `pint.Quantity`
690
        The corresponding density of the parcel
691
692
    Notes
693
    -----
694
    .. math:: \rho = \frac{p}{R_dT_v}
695
696
    """
697
    virttemp = virtual_temperature(temperature, mixing, molecular_weight_ratio)
698
    return (pressure / (Rd * virttemp)).to(units.kilogram / units.meter ** 3)
699
700
701
@exporter.export
702
@check_units('[temperature]', '[temperature]', '[pressure]')
703
def relative_humidity_wet_psychrometric(dry_bulb_temperature, web_bulb_temperature,
704
                                        pressure, **kwargs):
705
    r"""Calculate the relative humidity with wet bulb and dry bulb temperatures.
706
707
    This uses a psychrometric relationship as outlined in [WMO8-2014]_, with
708
    coefficients from [Fan1987]_.
709
710
    Parameters
711
    ----------
712
    dry_bulb_temperature: `pint.Quantity`
713
        Dry bulb temperature
714
    web_bulb_temperature: `pint.Quantity`
715
        Wet bulb temperature
716
    pressure: `pint.Quantity`
717
        Total atmospheric pressure
718
719
    Returns
720
    -------
721
    `pint.Quantity`
722
        Relative humidity
723
724
    Notes
725
    -----
726
    .. math:: RH = 100 \frac{e}{e_s}
727
728
    * :math:`RH` is relative humidity
729
    * :math:`e` is vapor pressure from the wet psychrometric calculation
730
    * :math:`e_s` is the saturation vapor pressure
731
732
    See Also
733
    --------
734
    psychrometric_vapor_pressure_wet, saturation_vapor_pressure
735
736
    """
737
    return (100 * units.percent * psychrometric_vapor_pressure_wet(dry_bulb_temperature,
738
            web_bulb_temperature, pressure, **kwargs) /
739
            saturation_vapor_pressure(dry_bulb_temperature))
740
741
742
@exporter.export
743
@check_units('[temperature]', '[temperature]', '[pressure]')
744
def psychrometric_vapor_pressure_wet(dry_bulb_temperature, wet_bulb_temperature, pressure,
745
                                     psychrometer_coefficient=6.21e-4 / units.kelvin):
746
    r"""Calculate the vapor pressure with wet bulb and dry bulb temperatures.
747
748
    This uses a psychrometric relationship as outlined in [WMO8-2014]_, with
749
    coefficients from [Fan1987]_.
750
751
    Parameters
752
    ----------
753
    dry_bulb_temperature: `pint.Quantity`
754
        Dry bulb temperature
755
    wet_bulb_temperature: `pint.Quantity`
756
        Wet bulb temperature
757
    pressure: `pint.Quantity`
758
        Total atmospheric pressure
759
    psychrometer_coefficient: `pint.Quantity`, optional
760
        Psychrometer coefficient. Defaults to 6.21e-4 K^-1.
761
762
    Returns
763
    -------
764
    `pint.Quantity`
765
        Vapor pressure
766
767
    Notes
768
    -----
769
    .. math:: e' = e'_w(T_w) - A p (T - T_w)
770
771
    * :math:`e'` is vapor pressure
772
    * :math:`e'_w(T_w)` is the saturation vapor pressure with respect to water at temperature
773
      :math:`T_w`
774
    * :math:`p` is the pressure of the wet bulb
775
    * :math:`T` is the temperature of the dry bulb
776
    * :math:`T_w` is the temperature of the wet bulb
777
    * :math:`A` is the psychrometer coefficient
778
779
    Psychrometer coefficient depends on the specific instrument being used and the ventilation
780
    of the instrument.
781
782
    See Also
783
    --------
784
    saturation_vapor_pressure
785
786
    """
787
    return (saturation_vapor_pressure(wet_bulb_temperature) - psychrometer_coefficient *
788
            pressure * (dry_bulb_temperature - wet_bulb_temperature).to('kelvin'))
789
790
791
@exporter.export
792
@check_units('[dimensionless]', '[temperature]', '[pressure]')
793
def relative_humidity_from_mixing_ratio(mixing_ratio, temperature, pressure):
794
    r"""Calculate the relative humidity from mixing ratio, temperature, and pressure.
795
796
    Parameters
797
    ----------
798
    mixing_ratio: `pint.Quantity`
799
        Dimensionless mass mixing ratio
800
    temperature: `pint.Quantity`
801
        Air temperature
802
    pressure: `pint.Quantity`
803
        Total atmospheric pressure
804
805
    Returns
806
    -------
807
    `pint.Quantity`
808
        Relative humidity
809
810
    Notes
811
    -----
812
    Formula from [Hobbs1977]_ pg. 74.
813
814
    .. math:: RH = 100 \frac{w}{w_s}
815
816
    * :math:`RH` is relative humidity
817
    * :math:`w` is mxing ratio
818
    * :math:`w_s` is the saturation mixing ratio
819
820
    See Also
821
    --------
822
    saturation_mixing_ratio
823
824
    """
825
    return (100 * units.percent *
826
            mixing_ratio / saturation_mixing_ratio(pressure, temperature))
827
828
829
@exporter.export
830
@check_units('[dimensionless]')
831
def mixing_ratio_from_specific_humidity(specific_humidity):
832
    r"""Calculate the mixing ratio from specific humidity.
833
834
    Parameters
835
    ----------
836
    specific_humidity: `pint.Quantity`
837
        Specific humidity of air
838
839
    Returns
840
    -------
841
    `pint.Quantity`
842
        Mixing ratio
843
844
    Notes
845
    -----
846
    Formula from [Salby1996]_ pg. 118.
847
848
    .. math:: w = \frac{q}{1-q}
849
850
    * :math:`w` is mxing ratio
851
    * :math:`q` is the specific humidity
852
853
    See Also
854
    --------
855
    mixing_ratio
856
857
    """
858
    return specific_humidity / (1 - specific_humidity)
859
860
861
@exporter.export
862
@check_units('[dimensionless]', '[temperature]', '[pressure]')
863
def relative_humidity_from_specific_humidity(specific_humidity, temperature, pressure):
864
    r"""Calculate the relative humidity from specific humidity, temperature, and pressure.
865
866
    Parameters
867
    ----------
868
    specific_humidity: `pint.Quantity`
869
        Specific humidity of air
870
    temperature: `pint.Quantity`
871
        Air temperature
872
    pressure: `pint.Quantity`
873
        Total atmospheric pressure
874
875
    Returns
876
    -------
877
    `pint.Quantity`
878
        Relative humidity
879
880
    Notes
881
    -----
882
    Formula from [Hobbs1977]_ pg. 74. and [Salby1996]_ pg. 118.
883
884
    .. math:: RH = 100 \frac{q}{(1-q)w_s}
885
886
    * :math:`RH` is relative humidity
887
    * :math:`q` is specific humidity
888
    * :math:`w_s` is the saturation mixing ratio
889
890
    See Also
891
    --------
892
    relative_humidity_from_mixing_ratio
893
894
    """
895
    return (100 * units.percent *
896
            mixing_ratio_from_specific_humidity(specific_humidity) /
897
            saturation_mixing_ratio(pressure, temperature))
898
899
900
@exporter.export
901
@check_units('[pressure]', '[temperature]', '[temperature]', '[temperature]')
902
def cape_cin(pressure, temperature, dewpt, parcel_profile):
903
    r"""Calculate CAPE and CIN.
904
905
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
906
    of a given upper air profile and parcel path. CIN is integrated between the surface and
907
    LFC, CAPE is integrated between the LFC and EL (or top of sounding). Intersection points of
908
    the measured temperature profile and parcel profile are linearly interpolated.
909
910
    Parameters
911
    ----------
912
    pressure : `pint.Quantity`
913
        The atmospheric pressure level(s) of interest. The first entry should be the starting
914
        point pressure.
915
    temperature : `pint.Quantity`
916
        The atmospheric temperature corresponding to pressure.
917
    dewpt : `pint.Quantity`
918
        The atmospheric dew point corresponding to pressure.
919
    parcel_profile : `pint.Quantity`
920
        The temperature profile of the parcel
921
922
    Returns
923
    -------
924
    `pint.Quantity`
925
        Convective available potential energy (CAPE).
926
    `pint.Quantity`
927
        Convective inhibition (CIN).
928
929
    Notes
930
    -----
931
    Formula adopted from [Hobbs1977]_.
932
933
    .. math:: \text{CAPE} = -R_d \int_{LFC}^{EL} (T_{parcel} - T_{env}) d\text{ln}(p)
934
935
    .. math:: \text{CIN} = -R_d \int_{SFC}^{LFC} (T_{parcel} - T_{env}) d\text{ln}(p)
936
937
938
    * :math:`CAPE` Convective available potential energy
939
    * :math:`CIN` Convective inhibition
940
    * :math:`LFC` Pressure of the level of free convection
941
    * :math:`EL` Pressure of the equilibrium level
942
    * :math:`SFC` Level of the surface or beginning of parcel path
943
    * :math:`R_d` Gas constant
944
    * :math:`g` Gravitational acceleration
945
    * :math:`T_{parcel}` Parcel temperature
946
    * :math:`T_{env}` Environment temperature
947
    * :math:`p` Atmospheric pressure
948
949
    See Also
950
    --------
951
    lfc, el
952
953
    """
954
    # Calculate LFC limit of integration
955
    lfc_pressure = lfc(pressure, temperature, dewpt)[0]
956
957
    # If there is no LFC, no need to proceed.
958
    if np.isnan(lfc_pressure):
959
        return 0 * units('J/kg'), 0 * units('J/kg')
960
    else:
961
        lfc_pressure = lfc_pressure.magnitude
962
963
    # Calculate the EL limit of integration
964
    el_pressure = el(pressure, temperature, dewpt)[0]
965
966
    # No EL and we use the top reading of the sounding.
967
    if np.isnan(el_pressure):
968
        el_pressure = pressure[-1].magnitude
969
    else:
970
        el_pressure = el_pressure.magnitude
971
972
    # Difference between the parcel path and measured temperature profiles
973
    y = (parcel_profile - temperature).to(units.degK)
974
975
    # Estimate zero crossings
976
    x, y = _find_append_zero_crossings(np.copy(pressure), y)
977
978
    # CAPE (temperature parcel < temperature environment)
979
    # Only use data between the LFC and EL for calculation
980
    p_mask = _less_or_close(x, lfc_pressure) & _greater_or_close(x, el_pressure)
981
    x_clipped = x[p_mask]
982
    y_clipped = y[p_mask]
983
984
    y_clipped[_less_or_close(y_clipped, 0 * units.degK)] = 0 * units.degK
985
    cape = (Rd * (np.trapz(y_clipped, np.log(x_clipped)) * units.degK)).to(units('J/kg'))
986
987
    # CIN (temperature parcel < temperature environment)
988
    # Only use data between the surface and LFC for calculation
989
    p_mask = _greater_or_close(x, lfc_pressure)
990
    x_clipped = x[p_mask]
991
    y_clipped = y[p_mask]
992
993
    y_clipped[_greater_or_close(y_clipped, 0 * units.degK)] = 0 * units.degK
994
    cin = (Rd * (np.trapz(y_clipped, np.log(x_clipped)) * units.degK)).to(units('J/kg'))
995
996
    return cape, cin
997
998
999
def _find_append_zero_crossings(x, y):
1000
    r"""
1001
    Find and interpolate zero crossings.
1002
1003
    Estimate the zero crossings of an x,y series and add estimated crossings to series,
1004
    returning a sorted array with no duplicate values.
1005
1006
    Parameters
1007
    ----------
1008
    x : `pint.Quantity`
1009
        x values of data
1010
    y : `pint.Quantity`
1011
        y values of data
1012
1013
    Returns
1014
    -------
1015
    x : `pint.Quantity`
1016
        x values of data
1017
    y : `pint.Quantity`
1018
        y values of data
1019
1020
    """
1021
    # Find and append crossings to the data
1022
    crossings = find_intersections(x[1:], y[1:], np.zeros_like(y[1:]) * y.units)
1023
    x = concatenate((x, crossings[0]))
1024
    y = concatenate((y, crossings[1]))
1025
1026
    # Resort so that data are in order
1027
    sort_idx = np.argsort(x)
1028
    x = x[sort_idx]
1029
    y = y[sort_idx]
1030
1031
    # Remove duplicate data points if there are any
1032
    keep_idx = np.ediff1d(x, to_end=[1]) > 0
1033
    x = x[keep_idx]
1034
    y = y[keep_idx]
1035
    return x, y
1036
1037
1038
@exporter.export
1039
@check_units('[pressure]', '[temperature]', '[temperature]')
1040
def most_unstable_parcel(pressure, temperature, dewpoint, heights=None,
1041
                         bottom=None, depth=300 * units.hPa):
1042
    """
1043
    Determine the most unstable parcel in a layer.
1044
1045
    Determines the most unstable parcel of air by calculating the equivalent
1046
    potential temperature and finding its maximum in the specified layer.
1047
1048
    Parameters
1049
    ----------
1050
    pressure: `pint.Quantity`
1051
        Atmospheric pressure profile
1052
    temperature: `pint.Quantity`
1053
        Atmospheric temperature profile
1054
    dewpoint: `pint.Quantity`
1055
        Atmospheric dewpoint profile
1056
    heights: `pint.Quantity`, optional
1057
        Atmospheric height profile. Standard atmosphere assumed when None (the default).
1058
    bottom: `pint.Quantity`, optional
1059
        Bottom of the layer to consider for the calculation in pressure or height.
1060
        Defaults to using the bottom pressure or height.
1061
    depth: `pint.Quantity`, optional
1062
        Depth of the layer to consider for the calculation in pressure or height. Defaults
1063
        to 300 hPa.
1064
1065
    Returns
1066
    -------
1067
    `pint.Quantity`
1068
        Pressure, temperature, and dew point of most unstable parcel in the profile.
1069
    integer
1070
        Index of the most unstable parcel in the given profile
1071
1072
    See Also
1073
    --------
1074
    get_layer
1075
1076
    """
1077
    p_layer, T_layer, Td_layer = get_layer(pressure, temperature, dewpoint, bottom=bottom,
1078
                                           depth=depth, heights=heights, interpolate=False)
1079
    theta_e = equivalent_potential_temperature(p_layer, T_layer, Td_layer)
1080
    max_idx = np.argmax(theta_e)
1081
    return p_layer[max_idx], T_layer[max_idx], Td_layer[max_idx], max_idx
1082
1083
1084
@exporter.export
1085
@check_units('[temperature]', '[pressure]', '[temperature]')
1086
def isentropic_interpolation(theta_levels, pressure, temperature, *args, **kwargs):
1087
    r"""Interpolate data in isobaric coordinates to isentropic coordinates.
1088
1089
    Parameters
1090
    ----------
1091
    theta_levels : array
1092
        One-dimensional array of desired theta surfaces
1093
    pressure : array
1094
        One-dimensional array of pressure levels
1095
    temperature : array
1096
        Array of temperature
1097
    args : array, optional
1098
        Any additional variables will be interpolated to each isentropic level.
1099
1100
    Returns
1101
    -------
1102
    list
1103
        List with pressure at each isentropic level, followed by each additional
1104
        argument interpolated to isentropic coordinates.
1105
1106
    Other Parameters
1107
    ----------------
1108
    axis : int, optional
1109
        The axis corresponding to the vertical in the temperature array, defaults to 0.
1110
    tmpk_out : bool, optional
1111
        If true, will calculate temperature and output as the last item in the output list.
1112
        Defaults to False.
1113
    max_iters : int, optional
1114
        The maximum number of iterations to use in calculation, defaults to 50.
1115
    eps : float, optional
1116
        The desired absolute error in the calculated value, defaults to 1e-6.
1117
1118
    Notes
1119
    -----
1120
    Input variable arrays must have the same number of vertical levels as the pressure levels
1121
    array. Pressure is calculated on isentropic surfaces by assuming that temperature varies
1122
    linearly with the natural log of pressure. Linear interpolation is then used in the
1123
    vertical to find the pressure at each isentropic level. Interpolation method from
1124
    [Ziv1994]_. Any additional arguments are assumed to vary linearly with temperature and will
1125
    be linearly interpolated to the new isentropic levels.
1126
1127
    See Also
1128
    --------
1129
    potential_temperature
1130
1131
    """
1132
    # iteration function to be used later
1133
    # Calculates theta from linearly interpolated temperature and solves for pressure
1134
    def _isen_iter(iter_log_p, isentlevs_nd, ka, a, b, pok):
1135
        exner = pok * np.exp(-ka * iter_log_p)
1136
        t = a * iter_log_p + b
1137
        # Newton-Raphson iteration
1138
        f = isentlevs_nd - t * exner
1139
        fp = exner * (ka * t - a)
1140
        return iter_log_p - (f / fp)
1141
1142
    # Change when Python 2.7 no longer supported
1143
    # Pull out keyword arguments
1144
    tmpk_out = kwargs.pop('tmpk_out', False)
1145
    max_iters = kwargs.pop('max_iters', 50)
1146
    eps = kwargs.pop('eps', 1e-6)
1147
    axis = kwargs.pop('axis', 0)
1148
1149
    # Get dimensions in temperature
1150
    ndim = temperature.ndim
1151
1152
    # Convert units
1153
    pres = pressure.to('hPa')
1154
    temperature = temperature.to('kelvin')
1155
1156
    slices = [np.newaxis] * ndim
1157
    slices[axis] = slice(None)
1158
    pres = pres[slices]
1159
    pres = np.broadcast_to(pres, temperature.shape) * pres.units
1160
1161
    # Sort input data
1162
    sort_pres = np.argsort(pres.m, axis=axis)
1163
    sort_pres = np.swapaxes(np.swapaxes(sort_pres, 0, axis)[::-1], 0, axis)
1164
    sorter = broadcast_indices(pres, sort_pres, ndim, axis)
1165
    levs = pres[sorter]
1166
    theta_levels = np.asanyarray(theta_levels.to('kelvin')).reshape(-1)
1167
    sort_isentlevs = np.argsort(theta_levels)
1168
    tmpk = temperature[sorter]
1169
    isentlevels = theta_levels[sort_isentlevs]
1170
1171
    # Make the desired isentropic levels the same shape as temperature
1172
    isentlevs_nd = isentlevels
1173
    isentlevs_nd = isentlevs_nd[slices]
1174
    shape = list(temperature.shape)
1175
    shape[axis] = isentlevels.size
1176
    isentlevs_nd = np.broadcast_to(isentlevs_nd, shape)
1177
1178
    # exponent to Poisson's Equation, which is imported above
1179
    ka = kappa.to('dimensionless').m
1180
1181
    # calculate theta for each point
1182
    pres_theta = potential_temperature(levs, tmpk)
1183
1184
    # Raise error if input theta level is larger than pres_theta max
1185
    if np.max(pres_theta.m) < np.max(theta_levels):
1186
        raise ValueError('Input theta level out of data bounds')
1187
1188
    # Find log of pressure to implement assumption of linear temperature dependence on
1189
    # ln(p)
1190
    log_p = np.log(levs.m)
1191
1192
    # Calculations for interpolation routine
1193
    pok = P0 ** ka
1194
1195
    # index values for each point for the pressure level nearest to the desired theta level
1196
    minv = np.apply_along_axis(np.searchsorted, axis, pres_theta.m, theta_levels)
1197
1198
    # Create index values for broadcasting arrays
1199
    above = broadcast_indices(tmpk, minv, ndim, axis)
1200
    below = broadcast_indices(tmpk, minv - 1, ndim, axis)
1201
1202
    # calculate constants for the interpolation
1203
    a = (tmpk.m[above] - tmpk.m[below]) / (log_p[above] - log_p[below])
1204
    b = tmpk.m[above] - a * log_p[above]
1205
1206
    # calculate first guess for interpolation
1207
    first_guess = 0.5 * (log_p[above] + log_p[below])
1208
1209
    # iterative interpolation using scipy.optimize.fixed_point and _isen_iter defined above
1210
    log_p_solved = so.fixed_point(_isen_iter, first_guess, args=(isentlevs_nd, ka, a, b,
1211
                                                                 pok.m),
1212
                                  xtol=eps, maxiter=max_iters)
1213
1214
    # get back pressure and assign nan for values with pressure greater than 1000 hPa
1215
    isentprs = np.exp(log_p_solved)
1216
    isentprs[isentprs > np.max(pressure.m)] = np.nan
1217
1218
    # create list for storing output data
1219
    ret = []
1220
    ret.append(isentprs * units.hPa)
1221
1222
    # if tmpk_out = true, calculate temperature and output as last item in list
1223
    if tmpk_out:
1224
        ret.append((isentlevs_nd / ((P0.m / isentprs) ** ka)) * units.kelvin)
1225
1226
    # check to see if any additional arguments were given, if so, interpolate to
1227
    # new isentropic levels
1228
    try:
1229
        args[0]
1230
    except IndexError:
1231
        return ret
1232
    else:
1233
        # do an interpolation for each additional argument
1234
        for arr in args:
1235
            var = arr[sorter]
1236
            # interpolate to isentropic levels and add to temporary output array
1237
            arg_out = interp(isentlevels, pres_theta.m, var, axis=axis)
1238
            ret.append(arg_out)
1239
1240
    # output values as a list
1241
    return ret
1242
1243
1244
@exporter.export
1245
@check_units('[pressure]', '[temperature]', '[temperature]')
1246
def surface_based_cape_cin(pressure, temperature, dewpoint):
1247
    r"""Calculate surface-based CAPE and CIN.
1248
1249
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
1250
    of a given upper air profile for a surface-based parcel. CIN is integrated
1251
    between the surface and LFC, CAPE is integrated between the LFC and EL (or top of
1252
    sounding). Intersection points of the measured temperature profile and parcel profile are
1253
    linearly interpolated.
1254
1255
    Parameters
1256
    ----------
1257
    pressure : `pint.Quantity`
1258
        Atmospheric pressure profile. The first entry should be the starting
1259
        (surface) observation.
1260
    temperature : `pint.Quantity`
1261
        Temperature profile
1262
    dewpoint : `pint.Quantity`
1263
        Dewpoint profile
1264
1265
    Returns
1266
    -------
1267
    `pint.Quantity`
1268
        Surface based Convective Available Potential Energy (CAPE).
1269
    `pint.Quantity`
1270
        Surface based Convective INhibition (CIN).
1271
1272
    See Also
1273
    --------
1274
    cape_cin, parcel_profile
1275
1276
    """
1277
    profile = parcel_profile(pressure, temperature[0], dewpoint[0])
1278
    return cape_cin(pressure, temperature, dewpoint, profile)
1279
1280
1281
@exporter.export
1282
@check_units('[pressure]', '[temperature]', '[temperature]')
1283
def most_unstable_cape_cin(pressure, temperature, dewpoint, **kwargs):
1284
    r"""Calculate most unstable CAPE/CIN.
1285
1286
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
1287
    of a given upper air profile and most unstable parcel path. CIN is integrated between the
1288
    surface and LFC, CAPE is integrated between the LFC and EL (or top of sounding).
1289
    Intersection points of the measured temperature profile and parcel profile are linearly
1290
    interpolated.
1291
1292
    Parameters
1293
    ----------
1294
    pressure : `pint.Quantity`
1295
        Pressure profile
1296
    temperature : `pint.Quantity`
1297
        Temperature profile
1298
    dewpoint : `pint.Quantity`
1299
        Dewpoint profile
1300
1301
    Returns
1302
    -------
1303
    `pint.Quantity`
1304
        Most unstable Convective Available Potential Energy (CAPE).
1305
    `pint.Quantity`
1306
        Most unstable Convective INhibition (CIN).
1307
1308
    See Also
1309
    --------
1310
    cape_cin, most_unstable_parcel, parcel_profile
1311
1312
    """
1313
    _, parcel_temperature, parcel_dewpoint, parcel_idx = most_unstable_parcel(pressure,
1314
                                                                              temperature,
1315
                                                                              dewpoint,
1316
                                                                              **kwargs)
1317
    mu_profile = parcel_profile(pressure[parcel_idx:], parcel_temperature, parcel_dewpoint)
1318
    return cape_cin(pressure[parcel_idx:], temperature[parcel_idx:],
1319
                    dewpoint[parcel_idx:], mu_profile)
1320