Completed
Pull Request — master (#492)
by
unknown
01:24
created

sbcape_cin()   B

Complexity

Conditions 2

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
c 1
b 0
f 0
dl 0
loc 38
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 numpy as np
9
import scipy.integrate as si
10
import scipy.optimize as so
11
12
from .tools import _greater_or_close, _less_or_close, find_intersections, get_layer
13
from ..constants import Cp_d, epsilon, kappa, Lv, P0, Rd
14
from ..package_tools import Exporter
15
from ..units import atleast_1d, check_units, concatenate, units
16
17
exporter = Exporter(globals())
18
19
sat_pressure_0c = 6.112 * units.millibar
20
21
22
@exporter.export
23
@check_units('[pressure]', '[temperature]')
24
def potential_temperature(pressure, temperature):
25
    r"""Calculate the potential temperature.
26
27
    Uses the Poisson equation to calculation the potential temperature
28
    given `pressure` and `temperature`.
29
30
    Parameters
31
    ----------
32
    pressure : `pint.Quantity`
33
        The total atmospheric pressure
34
    temperature : `pint.Quantity`
35
        The temperature
36
37
    Returns
38
    -------
39
    `pint.Quantity`
40
        The potential temperature corresponding to the the temperature and
41
        pressure.
42
43
    See Also
44
    --------
45
    dry_lapse
46
47
    Notes
48
    -----
49
    Formula:
50
51
    .. math:: \Theta = T (P_0 / P)^\kappa
52
53
    Examples
54
    --------
55
    >>> from metpy.units import units
56
    >>> metpy.calc.potential_temperature(800. * units.mbar, 273. * units.kelvin)
57
    <Quantity(290.96653180346203, 'kelvin')>
58
59
    """
60
    return temperature * (P0 / pressure).to('dimensionless')**kappa
61
62
63
@exporter.export
64
@check_units('[pressure]', '[temperature]')
65
def dry_lapse(pressure, temperature):
66
    r"""Calculate the temperature at a level assuming only dry processes.
67
68
    This function lifts a parcel starting at `temperature`, conserving
69
    potential temperature. The starting pressure should be the first item in
70
    the `pressure` array.
71
72
    Parameters
73
    ----------
74
    pressure : `pint.Quantity`
75
        The atmospheric pressure level(s) of interest
76
    temperature : `pint.Quantity`
77
        The starting temperature
78
79
    Returns
80
    -------
81
    `pint.Quantity`
82
       The resulting parcel temperature at levels given by `pressure`
83
84
    See Also
85
    --------
86
    moist_lapse : Calculate parcel temperature assuming liquid saturation
87
                  processes
88
    parcel_profile : Calculate complete parcel profile
89
    potential_temperature
90
91
    """
92
    return temperature * (pressure / pressure[0])**kappa
93
94
95
@exporter.export
96
@check_units('[pressure]', '[temperature]')
97
def moist_lapse(pressure, temperature):
98
    r"""Calculate the temperature at a level assuming liquid saturation processes.
99
100
    This function lifts a parcel starting at `temperature`. The starting
101
    pressure should be the first item in the `pressure` array. Essentially,
102
    this function is calculating moist pseudo-adiabats.
103
104
    Parameters
105
    ----------
106
    pressure : `pint.Quantity`
107
        The atmospheric pressure level(s) of interest
108
    temperature : `pint.Quantity`
109
        The starting temperature
110
111
    Returns
112
    -------
113
    `pint.Quantity`
114
       The temperature corresponding to the the starting temperature and
115
       pressure levels.
116
117
    See Also
118
    --------
119
    dry_lapse : Calculate parcel temperature assuming dry adiabatic processes
120
    parcel_profile : Calculate complete parcel profile
121
122
    Notes
123
    -----
124
    This function is implemented by integrating the following differential
125
    equation:
126
127
    .. math:: \frac{dT}{dP} = \frac{1}{P} \frac{R_d T + L_v r_s}
128
                                {C_{pd} + \frac{L_v^2 r_s \epsilon}{R_d T^2}}
129
130
    This equation comes from [Bakhshaii2013]_.
131
132
    """
133
    def dt(t, p):
134
        t = units.Quantity(t, temperature.units)
135
        p = units.Quantity(p, pressure.units)
136
        rs = saturation_mixing_ratio(p, t)
137
        frac = ((Rd * t + Lv * rs) /
138
                (Cp_d + (Lv * Lv * rs * epsilon / (Rd * t * t)))).to('kelvin')
139
        return frac / p
140
    return units.Quantity(si.odeint(dt, atleast_1d(temperature).squeeze(),
141
                                    pressure.squeeze()).T.squeeze(), temperature.units)
142
143
144
@exporter.export
145
@check_units('[pressure]', '[temperature]', '[temperature]')
146
def lcl(pressure, temperature, dewpt, max_iters=50, eps=1e-5):
147
    r"""Calculate the lifted condensation level (LCL) using from the starting point.
148
149
    The starting state for the parcel is defined by `temperature`, `dewpt`,
150
    and `pressure`.
151
152
    Parameters
153
    ----------
154
    pressure : `pint.Quantity`
155
        The starting atmospheric pressure
156
    temperature : `pint.Quantity`
157
        The starting temperature
158
    dewpt : `pint.Quantity`
159
        The starting dew point
160
161
    Returns
162
    -------
163
    `(pint.Quantity, pint.Quantity)`
164
        The LCL pressure and temperature
165
166
    Other Parameters
167
    ----------------
168
    max_iters : int, optional
169
        The maximum number of iterations to use in calculation, defaults to 50.
170
    eps : float, optional
171
        The desired relative error in the calculated value, defaults to 1e-5.
172
173
    See Also
174
    --------
175
    parcel_profile
176
177
    Notes
178
    -----
179
    This function is implemented using an iterative approach to solve for the
180
    LCL. The basic algorithm is:
181
182
    1. Find the dew point from the LCL pressure and starting mixing ratio
183
    2. Find the LCL pressure from the starting temperature and dewpoint
184
    3. Iterate until convergence
185
186
    The function is guaranteed to finish by virtue of the `max_iters` counter.
187
188
    """
189
    def _lcl_iter(p, p0, w, t):
190
        td = dewpoint(vapor_pressure(units.Quantity(p, pressure.units), w))
191
        return (p0 * (td / t) ** (1. / kappa)).m
192
193
    w = mixing_ratio(saturation_vapor_pressure(dewpt), pressure)
194
    fp = so.fixed_point(_lcl_iter, pressure.m, args=(pressure.m, w, temperature),
195
                        xtol=eps, maxiter=max_iters)
196
    lcl_p = units.Quantity(fp, pressure.units)
197
    return lcl_p, dewpoint(vapor_pressure(lcl_p, w))
198
199
200
@exporter.export
201
@check_units('[pressure]', '[temperature]', '[temperature]')
202
def lfc(pressure, temperature, dewpt):
203
    r"""Calculate the level of free convection (LFC).
204
205
    This works by finding the first intersection of the ideal parcel path and
206
    the measured parcel temperature.
207
208
    Parameters
209
    ----------
210
    pressure : `pint.Quantity`
211
        The atmospheric pressure
212
    temperature : `pint.Quantity`
213
        The temperature at the levels given by `pressure`
214
    dewpt : `pint.Quantity`
215
        The dew point at the levels given by `pressure`
216
217
    Returns
218
    -------
219
    `pint.Quantity`
220
        The LFC pressure and temperature
221
222
    See Also
223
    --------
224
    parcel_profile
225
226
    """
227
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
228
229
    # The parcel profile and data have the same first data point, so we ignore
230
    # that point to get the real first intersection for the LFC calculation.
231
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:],
232
                              direction='increasing')
233
    # Two possible cases here: LFC = LCL, or LFC doesn't exist
234
    if len(x) == 0:
235
        if np.any(ideal_profile > temperature):
236
            # LFC = LCL
237
            x, y = lcl(pressure[0], temperature[0], dewpt[0])
238
            return x, y
239
        # LFC doesn't exist
240
        else:
241
            return np.nan * pressure.units, np.nan * temperature.units
242
    else:
243
        return x[0], y[0]
244
245
246
@exporter.export
247
@check_units('[pressure]', '[temperature]', '[temperature]')
248
def el(pressure, temperature, dewpt):
249
    r"""Calculate the equilibrium level.
250
251
    This works by finding the last intersection of the ideal parcel path and
252
    the measured environmental temperature. If there is one or fewer intersections, there is
253
    no equilibrium level.
254
255
    Parameters
256
    ----------
257
    pressure : `pint.Quantity`
258
        The atmospheric pressure
259
    temperature : `pint.Quantity`
260
        The temperature at the levels given by `pressure`
261
    dewpt : `pint.Quantity`
262
        The dew point at the levels given by `pressure`
263
264
    Returns
265
    -------
266
    `pint.Quantity, pint.Quantity`
267
        The EL pressure and temperature
268
269
    See Also
270
    --------
271
    parcel_profile
272
273
    """
274
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
275
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:])
276
    # Find LFC to make sure that the function isn't passing off the LFC as the EL
277
    lfc_pressure, _ = lfc(pressure, temperature, dewpt)
278
279
    # If there is only one intersection, there are two possibilities:
280
    # the dataset does not contain the EL, or the LFC = LCL.
281
    if len(x) <= 1:
282
        if (ideal_profile[-1] < temperature[-1]) and (len(x) == 1):
283
            # Profile top colder than environment with one
284
            # intersection, EL exists and LFC = LCL
285
            return x[-1], y[-1]
286
        else:
287
            # The EL does not exist, either due to incomplete data
288
            # or no intersection occurring.
289
            return np.nan * pressure.units, np.nan * temperature.units
290
    else:
291
        # Checking to make sure the LFC is not returned as the EL
292
        # when data is truncated below the EL
293
        if np.isclose(x[-1], lfc_pressure, atol=1e-6):
294
            return np.nan * pressure.units, np.nan * temperature.units
295
        else:
296
            return x[-1], y[-1]
297
298
299
@exporter.export
300
@check_units('[pressure]', '[temperature]', '[temperature]')
301
def parcel_profile(pressure, temperature, dewpt):
302
    r"""Calculate the profile a parcel takes through the atmosphere.
303
304
    The parcel starts at `temperature`, and `dewpt`, lifted up
305
    dry adiabatically to the LCL, and then moist adiabatically from there.
306
    `pressure` specifies the pressure levels for the profile.
307
308
    Parameters
309
    ----------
310
    pressure : `pint.Quantity`
311
        The atmospheric pressure level(s) of interest. The first entry should be the starting
312
        point pressure.
313
    temperature : `pint.Quantity`
314
        The starting temperature
315
    dewpt : `pint.Quantity`
316
        The starting dew point
317
318
    Returns
319
    -------
320
    `pint.Quantity`
321
        The parcel temperatures at the specified pressure levels.
322
323
    See Also
324
    --------
325
    lcl, moist_lapse, dry_lapse
326
327
    """
328
    # Find the LCL
329
    l = lcl(pressure[0], temperature, dewpt)[0].to(pressure.units)
330
331
    # Find the dry adiabatic profile, *including* the LCL. We need >= the LCL in case the
332
    # LCL is included in the levels. It's slightly redundant in that case, but simplifies
333
    # the logic for removing it later.
334
    press_lower = concatenate((pressure[pressure >= l], l))
335
    t1 = dry_lapse(press_lower, temperature)
336
337
    # Find moist pseudo-adiabatic profile starting at the LCL
338
    press_upper = concatenate((l, pressure[pressure < l]))
339
    t2 = moist_lapse(press_upper, t1[-1]).to(t1.units)
340
341
    # Return LCL *without* the LCL point
342
    return concatenate((t1[:-1], t2[1:]))
343
344
345
@exporter.export
346
@check_units('[pressure]', '[dimensionless]')
347
def vapor_pressure(pressure, mixing):
348
    r"""Calculate water vapor (partial) pressure.
349
350
    Given total `pressure` and water vapor `mixing` ratio, calculates the
351
    partial pressure of water vapor.
352
353
    Parameters
354
    ----------
355
    pressure : `pint.Quantity`
356
        total atmospheric pressure
357
    mixing : `pint.Quantity`
358
        dimensionless mass mixing ratio
359
360
    Returns
361
    -------
362
    `pint.Quantity`
363
        The ambient water vapor (partial) pressure in the same units as
364
        `pressure`.
365
366
    Notes
367
    -----
368
    This function is a straightforward implementation of the equation given in many places,
369
    such as [Hobbs1977]_ pg.71:
370
371
    .. math:: e = p \frac{r}{r + \epsilon}
372
373
    See Also
374
    --------
375
    saturation_vapor_pressure, dewpoint
376
377
    """
378
    return pressure * mixing / (epsilon + mixing)
379
380
381
@exporter.export
382
@check_units('[temperature]')
383
def saturation_vapor_pressure(temperature):
384
    r"""Calculate the saturation water vapor (partial) pressure.
385
386
    Parameters
387
    ----------
388
    temperature : `pint.Quantity`
389
        The temperature
390
391
    Returns
392
    -------
393
    `pint.Quantity`
394
        The saturation water vapor (partial) pressure
395
396
    See Also
397
    --------
398
    vapor_pressure, dewpoint
399
400
    Notes
401
    -----
402
    Instead of temperature, dewpoint may be used in order to calculate
403
    the actual (ambient) water vapor (partial) pressure.
404
405
    The formula used is that from [Bolton1980]_ for T in degrees Celsius:
406
407
    .. math:: 6.112 e^\frac{17.67T}{T + 243.5}
408
409
    """
410
    # Converted from original in terms of C to use kelvin. Using raw absolute values of C in
411
    # a formula plays havoc with units support.
412
    return sat_pressure_0c * np.exp(17.67 * (temperature - 273.15 * units.kelvin) /
413
                                    (temperature - 29.65 * units.kelvin))
414
415
416
@exporter.export
417
@check_units('[temperature]', '[dimensionless]')
418
def dewpoint_rh(temperature, rh):
419
    r"""Calculate the ambient dewpoint given air temperature and relative humidity.
420
421
    Parameters
422
    ----------
423
    temperature : `pint.Quantity`
424
        Air temperature
425
    rh : `pint.Quantity`
426
        Relative humidity expressed as a ratio in the range [0, 1]
427
428
    Returns
429
    -------
430
    `pint.Quantity`
431
        The dew point temperature
432
433
    See Also
434
    --------
435
    dewpoint, saturation_vapor_pressure
436
437
    """
438
    return dewpoint(rh * saturation_vapor_pressure(temperature))
439
440
441
@exporter.export
442
@check_units('[pressure]')
443
def dewpoint(e):
444
    r"""Calculate the ambient dewpoint given the vapor pressure.
445
446
    Parameters
447
    ----------
448
    e : `pint.Quantity`
449
        Water vapor partial pressure
450
451
    Returns
452
    -------
453
    `pint.Quantity`
454
        Dew point temperature
455
456
    See Also
457
    --------
458
    dewpoint_rh, saturation_vapor_pressure, vapor_pressure
459
460
    Notes
461
    -----
462
    This function inverts the [Bolton1980]_ formula for saturation vapor
463
    pressure to instead calculate the temperature. This yield the following
464
    formula for dewpoint in degrees Celsius:
465
466
    .. math:: T = \frac{243.5 log(e / 6.112)}{17.67 - log(e / 6.112)}
467
468
    """
469
    val = np.log(e / sat_pressure_0c)
470
    return 0. * units.degC + 243.5 * units.delta_degC * val / (17.67 - val)
471
472
473
@exporter.export
474
@check_units('[pressure]', '[pressure]', '[dimensionless]')
475
def mixing_ratio(part_press, tot_press, molecular_weight_ratio=epsilon):
476
    r"""Calculate the mixing ratio of a gas.
477
478
    This calculates mixing ratio given its partial pressure and the total pressure of
479
    the air. There are no required units for the input arrays, other than that
480
    they have the same units.
481
482
    Parameters
483
    ----------
484
    part_press : `pint.Quantity`
485
        Partial pressure of the constituent gas
486
    tot_press : `pint.Quantity`
487
        Total air pressure
488
    molecular_weight_ratio : `pint.Quantity` or float, optional
489
        The ratio of the molecular weight of the constituent gas to that assumed
490
        for air. Defaults to the ratio for water vapor to dry air
491
        (:math:`\epsilon\approx0.622`).
492
493
    Returns
494
    -------
495
    `pint.Quantity`
496
        The (mass) mixing ratio, dimensionless (e.g. Kg/Kg or g/g)
497
498
    Notes
499
    -----
500
    This function is a straightforward implementation of the equation given in many places,
501
    such as [Hobbs1977]_ pg.73:
502
503
    .. math:: r = \epsilon \frac{e}{p - e}
504
505
    See Also
506
    --------
507
    saturation_mixing_ratio, vapor_pressure
508
509
    """
510
    return molecular_weight_ratio * part_press / (tot_press - part_press)
511
512
513
@exporter.export
514
@check_units('[pressure]', '[temperature]')
515
def saturation_mixing_ratio(tot_press, temperature):
516
    r"""Calculate the saturation mixing ratio of water vapor.
517
518
    This calculation is given total pressure and the temperature. The implementation
519
    uses the formula outlined in [Hobbs1977]_ pg.73.
520
521
    Parameters
522
    ----------
523
    tot_press: `pint.Quantity`
524
        Total atmospheric pressure
525
    temperature: `pint.Quantity`
526
        The temperature
527
528
    Returns
529
    -------
530
    `pint.Quantity`
531
        The saturation mixing ratio, dimensionless
532
533
    """
534
    return mixing_ratio(saturation_vapor_pressure(temperature), tot_press)
535
536
537
@exporter.export
538
@check_units('[pressure]', '[temperature]')
539
def equivalent_potential_temperature(pressure, temperature):
540
    r"""Calculate equivalent potential temperature.
541
542
    This calculation must be given an air parcel's pressure and temperature.
543
    The implementation uses the formula outlined in [Hobbs1977]_ pg.78-79.
544
545
    Parameters
546
    ----------
547
    pressure: `pint.Quantity`
548
        Total atmospheric pressure
549
    temperature: `pint.Quantity`
550
        The temperature
551
552
    Returns
553
    -------
554
    `pint.Quantity`
555
        The corresponding equivalent potential temperature of the parcel
556
557
    Notes
558
    -----
559
    .. math:: \Theta_e = \Theta e^\frac{L_v r_s}{C_{pd} T}
560
561
    """
562
    pottemp = potential_temperature(pressure, temperature)
563
    smixr = saturation_mixing_ratio(pressure, temperature)
564
    return pottemp * np.exp(Lv * smixr / (Cp_d * temperature))
565
566
567
@exporter.export
568
@check_units('[temperature]', '[dimensionless]', '[dimensionless]')
569
def virtual_temperature(temperature, mixing, molecular_weight_ratio=epsilon):
570
    r"""Calculate virtual temperature.
571
572
    This calculation must be given an air parcel's temperature and mixing ratio.
573
    The implementation uses the formula outlined in [Hobbs2006]_ pg.80.
574
575
    Parameters
576
    ----------
577
    temperature: `pint.Quantity`
578
        The temperature
579
    mixing : `pint.Quantity`
580
        dimensionless mass mixing ratio
581
    molecular_weight_ratio : `pint.Quantity` or float, optional
582
        The ratio of the molecular weight of the constituent gas to that assumed
583
        for air. Defaults to the ratio for water vapor to dry air
584
        (:math:`\epsilon\approx0.622`).
585
586
    Returns
587
    -------
588
    `pint.Quantity`
589
        The corresponding virtual temperature of the parcel
590
591
    Notes
592
    -----
593
    .. math:: T_v = T \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
594
595
    """
596
    return temperature * ((mixing + molecular_weight_ratio) /
597
                          (molecular_weight_ratio * (1 + mixing)))
598
599
600
@exporter.export
601
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
602
def virtual_potential_temperature(pressure, temperature, mixing,
603
                                  molecular_weight_ratio=epsilon):
604
    r"""Calculate virtual potential temperature.
605
606
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
607
    The implementation uses the formula outlined in [Markowski2010]_ pg.13.
608
609
    Parameters
610
    ----------
611
    pressure: `pint.Quantity`
612
        Total atmospheric pressure
613
    temperature: `pint.Quantity`
614
        The temperature
615
    mixing : `pint.Quantity`
616
        dimensionless mass mixing ratio
617
    molecular_weight_ratio : `pint.Quantity` or float, optional
618
        The ratio of the molecular weight of the constituent gas to that assumed
619
        for air. Defaults to the ratio for water vapor to dry air
620
        (:math:`\epsilon\approx0.622`).
621
622
    Returns
623
    -------
624
    `pint.Quantity`
625
        The corresponding virtual potential temperature of the parcel
626
627
    Notes
628
    -----
629
    .. math:: \Theta_v = \Theta \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
630
631
    """
632
    pottemp = potential_temperature(pressure, temperature)
633
    return virtual_temperature(pottemp, mixing, molecular_weight_ratio)
634
635
636
@exporter.export
637
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
638
def density(pressure, temperature, mixing, molecular_weight_ratio=epsilon):
639
    r"""Calculate density.
640
641
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
642
    The implementation uses the formula outlined in [Hobbs2006]_ pg.67.
643
644
    Parameters
645
    ----------
646
    temperature: `pint.Quantity`
647
        The temperature
648
    pressure: `pint.Quantity`
649
        Total atmospheric pressure
650
    mixing : `pint.Quantity`
651
        dimensionless mass mixing ratio
652
    molecular_weight_ratio : `pint.Quantity` or float, optional
653
        The ratio of the molecular weight of the constituent gas to that assumed
654
        for air. Defaults to the ratio for water vapor to dry air
655
        (:math:`\epsilon\approx0.622`).
656
657
    Returns
658
    -------
659
    `pint.Quantity`
660
        The corresponding density of the parcel
661
662
    Notes
663
    -----
664
    .. math:: \rho = \frac{p}{R_dT_v}
665
666
    """
667
    virttemp = virtual_temperature(temperature, mixing, molecular_weight_ratio)
668
    return (pressure / (Rd * virttemp)).to(units.kilogram / units.meter ** 3)
669
670
671
@exporter.export
672
@check_units('[temperature]', '[temperature]', '[pressure]')
673
def relative_humidity_wet_psychrometric(dry_bulb_temperature, web_bulb_temperature,
674
                                        pressure, **kwargs):
675
    r"""Calculate the relative humidity with wet bulb and dry bulb temperatures.
676
677
    This uses a psychrometric relationship as outlined in [WMO8-2008]_, with
678
    coefficients from [Fan1987]_.
679
680
    Parameters
681
    ----------
682
    dry_bulb_temperature: `pint.Quantity`
683
        Dry bulb temperature
684
    web_bulb_temperature: `pint.Quantity`
685
        Wet bulb temperature
686
    pressure: `pint.Quantity`
687
        Total atmospheric pressure
688
689
    Returns
690
    -------
691
    `pint.Quantity`
692
        Relative humidity
693
694
    Notes
695
    -----
696
    .. math:: RH = 100 \frac{e}{e_s}
697
698
    * :math:`RH` is relative humidity
699
    * :math:`e` is vapor pressure from the wet psychrometric calculation
700
    * :math:`e_s` is the saturation vapor pressure
701
702
    See Also
703
    --------
704
    psychrometric_vapor_pressure_wet, saturation_vapor_pressure
705
706
    """
707
    return (100 * units.percent * psychrometric_vapor_pressure_wet(dry_bulb_temperature,
708
            web_bulb_temperature, pressure, **kwargs) /
709
            saturation_vapor_pressure(dry_bulb_temperature))
710
711
712
@exporter.export
713
@check_units('[temperature]', '[temperature]', '[pressure]')
714
def psychrometric_vapor_pressure_wet(dry_bulb_temperature, wet_bulb_temperature, pressure,
715
                                     psychrometer_coefficient=6.21e-4 / units.kelvin):
716
    r"""Calculate the vapor pressure with wet bulb and dry bulb temperatures.
717
718
    This uses a psychrometric relationship as outlined in [WMO8-2008]_, with
719
    coefficients from [Fan1987]_.
720
721
    Parameters
722
    ----------
723
    dry_bulb_temperature: `pint.Quantity`
724
        Dry bulb temperature
725
    wet_bulb_temperature: `pint.Quantity`
726
        Wet bulb temperature
727
    pressure: `pint.Quantity`
728
        Total atmospheric pressure
729
    psychrometer_coefficient: `pint.Quantity`
730
        Psychrometer coefficient
731
732
    Returns
733
    -------
734
    `pint.Quantity`
735
        Vapor pressure
736
737
    Notes
738
    -----
739
    .. math:: e' = e'_w(T_w) - A p (T - T_w)
740
741
    * :math:`e'` is vapor pressure
742
    * :math:`e'_w(T_w)` is the saturation vapor pressure with respect to water at temperature
743
      :math:`T_w`
744
    * :math:`p` is the pressure of the wet bulb
745
    * :math:`T` is the temperature of the dry bulb
746
    * :math:`T_w` is the temperature of the wet bulb
747
    * :math:`A` is the psychrometer coefficient
748
749
    Psychrometer coefficient depends on the specific instrument being used and the ventilation
750
    of the instrument.
751
752
    See Also
753
    --------
754
    saturation_vapor_pressure
755
756
    """
757
    return (saturation_vapor_pressure(wet_bulb_temperature) - psychrometer_coefficient *
758
            pressure * (dry_bulb_temperature - wet_bulb_temperature).to('kelvin'))
759
760
761
@exporter.export
762
@check_units('[dimensionless]', '[temperature]', '[pressure]')
763
def relative_humidity_from_mixing_ratio(mixing_ratio, temperature, pressure):
764
    r"""Calculate the relative humidity from mixing ratio, temperature, and pressure.
765
766
    Parameters
767
    ----------
768
    mixing_ratio: `pint.Quantity`
769
        Dimensionless mass mixing ratio
770
    temperature: `pint.Quantity`
771
        Air temperature
772
    pressure: `pint.Quantity`
773
        Total atmospheric pressure
774
775
    Returns
776
    -------
777
    `pint.Quantity`
778
        Relative humidity
779
780
    Notes
781
    -----
782
    Formula from [Hobbs1977]_ pg. 74.
783
784
    .. math:: RH = 100 \frac{w}{w_s}
785
786
    * :math:`RH` is relative humidity
787
    * :math:`w` is mxing ratio
788
    * :math:`w_s` is the saturation mixing ratio
789
790
    See Also
791
    --------
792
    saturation_mixing_ratio
793
794
    """
795
    return (100 * units.percent *
796
            mixing_ratio / saturation_mixing_ratio(pressure, temperature))
797
798
799
@exporter.export
800
@check_units('[dimensionless]')
801
def mixing_ratio_from_specific_humidity(specific_humidity):
802
    r"""Calculate the mixing ratio from specific humidity.
803
804
    Parameters
805
    ----------
806
    specific_humidity: `pint.Quantity`
807
        Specific humidity of air
808
809
    Returns
810
    -------
811
    `pint.Quantity`
812
        Mixing ratio
813
814
    Notes
815
    -----
816
    Formula from [Salby1996]_ pg. 118.
817
818
    .. math:: w = \frac{q}{1-q}
819
820
    * :math:`w` is mxing ratio
821
    * :math:`q` is the specific humidity
822
823
    See Also
824
    --------
825
    mixing_ratio
826
827
    """
828
    return specific_humidity / (1 - specific_humidity)
829
830
831
@exporter.export
832
@check_units('[dimensionless]', '[temperature]', '[pressure]')
833
def relative_humidity_from_specific_humidity(specific_humidity, temperature, pressure):
834
    r"""Calculate the relative humidity from specific humidity, temperature, and pressure.
835
836
    Parameters
837
    ----------
838
    specific_humidity: `pint.Quantity`
839
        Specific humidity of air
840
    temperature: `pint.Quantity`
841
        Air temperature
842
    pressure: `pint.Quantity`
843
        Total atmospheric pressure
844
845
    Returns
846
    -------
847
    `pint.Quantity`
848
        Relative humidity
849
850
    Notes
851
    -----
852
    Formula from [Hobbs1977]_ pg. 74. and [Salby1996]_ pg. 118.
853
854
    .. math:: RH = 100 \frac{q}{(1-q)w_s}
855
856
    * :math:`RH` is relative humidity
857
    * :math:`q` is specific humidity
858
    * :math:`w_s` is the saturation mixing ratio
859
860
    See Also
861
    --------
862
    relative_humidity_from_mixing_ratio
863
864
    """
865
    return (100 * units.percent *
866
            mixing_ratio_from_specific_humidity(specific_humidity) /
867
            saturation_mixing_ratio(pressure, temperature))
868
869
870
@exporter.export
871
@check_units('[pressure]', '[temperature]', '[temperature]', '[temperature]')
872
def cape_cin(pressure, temperature, dewpt, parcel_profile):
873
    r"""Calculate CAPE and CIN.
874
875
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
876
    of a given upper air profile and parcel path. CIN is integrated between the surface and
877
    LFC, CAPE is integrated between the LFC and EL (or top of sounding). Intersection points of
878
    the measured temperature profile and parcel profile are linearly interpolated.
879
880
    Parameters
881
    ----------
882
    pressure : `pint.Quantity`
883
        The atmospheric pressure level(s) of interest. The first entry should be the starting
884
        point pressure.
885
    temperature : `pint.Quantity`
886
        The starting temperature
887
    dewpt : `pint.Quantity`
888
        The starting dew point
889
    parcel_profile : `pint.Quantity`
890
        The temperature profile of the parcel
891
892
    Returns
893
    -------
894
    `pint.Quantity`
895
        Convective available potential energy (CAPE).
896
    `pint.Quantity`
897
        Convective inhibition (CIN).
898
899
    Notes
900
    -----
901
    Formula adopted from [Hobbs1977]_.
902
903
    .. math:: \text{CAPE} = -R_d \int_{LFC}^{EL} (T_{parcel} - T_{env}) d\text{ln}(p)
904
905
    .. math:: \text{CIN} = -R_d \int_{SFC}^{LFC} (T_{parcel} - T_{env}) d\text{ln}(p)
906
907
908
    * :math:`CAPE` Convective available potential energy
909
    * :math:`CIN` Convective inhibition
910
    * :math:`LFC` Pressure of the level of free convection
911
    * :math:`EL` Pressure of the equilibrium level
912
    * :math:`SFC` Level of the surface or beginning of parcel path
913
    * :math:`R_d` Gas constant
914
    * :math:`g` Gravitational acceleration
915
    * :math:`T_{parcel}` Parcel temperature
916
    * :math:`T_{env}` Environment temperature
917
    * :math:`p` Atmospheric pressure
918
919
    See Also
920
    --------
921
    lfc, el
922
923
    """
924
    # Calculate LFC limit of integration
925
    lfc_pressure = lfc(pressure, temperature, dewpt)[0]
926
927
    # If there is no LFC, no need to proceed.
928
    if np.isnan(lfc_pressure):
929
        return 0 * units('J/kg'), 0 * units('J/kg')
930
    else:
931
        lfc_pressure = lfc_pressure.magnitude
932
933
    # Calculate the EL limit of integration
934
    el_pressure = el(pressure, temperature, dewpt)[0]
935
936
    # No EL and we use the top reading of the sounding.
937
    if np.isnan(el_pressure):
938
        el_pressure = pressure[-1].magnitude
939
    else:
940
        el_pressure = el_pressure.magnitude
941
942
    # Difference between the parcel path and measured temperature profiles
943
    y = (parcel_profile - temperature).to(units.degK)
944
945
    # Estimate zero crossings
946
    x, y = _find_append_zero_crossings(np.copy(pressure), y)
947
948
    # CAPE (temperature parcel < temperature environment)
949
    # Only use data between the LFC and EL for calculation
950
    p_mask = _less_or_close(x, lfc_pressure) & _greater_or_close(x, el_pressure)
951
    x_clipped = x[p_mask]
952
    y_clipped = y[p_mask]
953
954
    y_clipped[_less_or_close(y_clipped, 0 * units.degK)] = 0 * units.degK
955
    cape = (Rd * (np.trapz(y_clipped, np.log(x_clipped)) * units.degK)).to(units('J/kg'))
956
957
    # CIN (temperature parcel < temperature environment)
958
    # Only use data between the surface and LFC for calculation
959
    p_mask = _greater_or_close(x, lfc_pressure)
960
    x_clipped = x[p_mask]
961
    y_clipped = y[p_mask]
962
963
    y_clipped[_greater_or_close(y_clipped, 0 * units.degK)] = 0 * units.degK
964
    cin = (Rd * (np.trapz(y_clipped, np.log(x_clipped)) * units.degK)).to(units('J/kg'))
965
966
    return cape, cin
967
968
969
def _find_append_zero_crossings(x, y):
970
    r"""
971
    Find and interpolate zero crossings.
972
973
    Estimate the zero crossings of an x,y series and add estimated crossings to series,
974
    returning a sorted array with no duplicate values.
975
976
    Parameters
977
    ----------
978
    x : `pint.Quantity`
979
        x values of data
980
    y : `pint.Quantity`
981
        y values of data
982
983
    Returns
984
    -------
985
    x : `pint.Quantity`
986
        x values of data
987
    y : `pint.Quantity`
988
        y values of data
989
990
    """
991
    # Find and append crossings to the data
992
    crossings = find_intersections(x[1:], y[1:], np.zeros_like(y[1:]) * y.units)
993
    x = concatenate((x, crossings[0]))
994
    y = concatenate((y, crossings[1]))
995
996
    # Resort so that data are in order
997
    sort_idx = np.argsort(x)
998
    x = x[sort_idx]
999
    y = y[sort_idx]
1000
1001
    # Remove duplicate data points if there are any
1002
    keep_idx = np.ediff1d(x, to_end=[1]) > 0
1003
    x = x[keep_idx]
1004
    y = y[keep_idx]
1005
    return x, y
1006
1007
1008
@exporter.export
1009
@check_units('[pressure]', '[temperature]', '[temperature]')
1010
def most_unstable_parcel(p, temperature, dewpoint, heights=None,
1011
                         bottom=None, depth=300 * units.hPa):
1012
    """
1013
    Determine the most unstable parcel in a layer.
1014
1015
    Determines the most unstable parcel of air by calculating the equivalent
1016
    potential temperature and finding its maximum in the specified layer.
1017
1018
    Parameters
1019
    ----------
1020
    p: `pint.Quantity`
1021
        Atmospheric pressure profile
1022
    temperature: `pint.Quantity`
1023
        Atmospheric temperature profile
1024
    dewpoint: `pint.Quantity`
1025
        Atmospheric dewpoint profile
1026
    heights: `pint.Quantity`
1027
        Atmospheric height profile. Standard atmosphere assumed when None.
1028
    bottom: `pint.Quantity`
1029
        Bottom of the layer to consider for the calculation in pressure or height
1030
    depth: `pint.Quantity`
1031
        Depth of the layer to consider for the calculation in pressure or height
1032
1033
    Returns
1034
    -------
1035
    `pint.Quantity`
1036
        Pressure, temperature, and dew point of most unstable parcel in the profile.
1037
1038
    See Also
1039
    --------
1040
    get_layer
1041
1042
    """
1043
    p_layer, T_layer, Td_layer = get_layer(p, temperature, dewpoint, bottom=bottom,
1044
                                           depth=depth, heights=heights)
1045
    theta_e = equivalent_potential_temperature(p_layer, T_layer)
1046
    max_idx = np.argmax(theta_e)
1047
    return p_layer[max_idx], T_layer[max_idx], Td_layer[max_idx]
1048
1049
1050
@exporter.export
1051
@check_units('[pressure]', '[temperature]', '[temperature]', '[temperature]')
1052
def sbcape_cin(pressure, temperature, dewpt, profile=None):
1053
    r"""Calculate surface-based CAPE and CIN.
1054
1055
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
1056
    of a given upper air profile and parcel path for a surface-based parcel. CIN is integrated
1057
    between the surface and LFC, CAPE is integrated between the LFC and EL
1058
    (or top of sounding). Intersection points of the measured temperature profile and parcel
1059
    profile are linearly interpolated.
1060
1061
    Parameters
1062
    ----------
1063
    pressure : `pint.Quantity`
1064
        The atmospheric pressure level(s) of interest. The first entry should be the starting
1065
        point pressure.
1066
    temperature : `pint.Quantity`
1067
        The starting temperature
1068
    dewpt : `pint.Quantity`
1069
        The starting dew point
1070
    profile : `pint.Quantity`
1071
        The temperature profile of the parcel
1072
1073
    Returns
1074
    -------
1075
    `pint.Quantity`
1076
        Convective available potential energy (CAPE).
1077
    `pint.Quantity`
1078
        Convective inhibition (CIN).
1079
1080
    See Also
1081
    --------
1082
    cape_cin, lfc, el
1083
1084
    """
1085
    if profile is None:
1086
        profile = parcel_profile(pressure, temperature[0], dewpt[0])
1087
    return cape_cin(pressure, temperature, dewpt, profile)
1088