isentropic_interpolation()   C
last analyzed

Complexity

Conditions 7

Size

Total Lines 158

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
c 1
b 0
f 0
dl 0
loc 158
rs 5.3042

1 Method

Rating   Name   Duplication   Size   Complexity  
A _isen_iter() 0 7 1

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
# Copyright (c) 2008,2015,2016,2017 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
    # If the top of the sounding parcel is warmer than the environment, there is no EL
278
    if ideal_profile[-1] > temperature[-1]:
279
        return np.nan * pressure.units, np.nan * temperature.units
280
281
    # Otherwise the last intersection (as long as there is one) is the EL
282
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:])
283
    if len(x) > 0:
284
        return x[-1], y[-1]
285
    else:
286
        return np.nan * pressure.units, np.nan * temperature.units
287
288
289
@exporter.export
290
@check_units('[pressure]', '[temperature]', '[temperature]')
291
def parcel_profile(pressure, temperature, dewpt):
292
    r"""Calculate the profile a parcel takes through the atmosphere.
293
294
    The parcel starts at `temperature`, and `dewpt`, lifted up
295
    dry adiabatically to the LCL, and then moist adiabatically from there.
296
    `pressure` specifies the pressure levels for the profile.
297
298
    Parameters
299
    ----------
300
    pressure : `pint.Quantity`
301
        The atmospheric pressure level(s) of interest. The first entry should be the starting
302
        point pressure.
303
    temperature : `pint.Quantity`
304
        The starting temperature
305
    dewpt : `pint.Quantity`
306
        The starting dew point
307
308
    Returns
309
    -------
310
    `pint.Quantity`
311
        The parcel temperatures at the specified pressure levels.
312
313
    See Also
314
    --------
315
    lcl, moist_lapse, dry_lapse
316
317
    """
318
    # Find the LCL
319
    lcl_pressure = lcl(pressure[0], temperature, dewpt)[0].to(pressure.units)
320
321
    # Find the dry adiabatic profile, *including* the LCL. We need >= the LCL in case the
322
    # LCL is included in the levels. It's slightly redundant in that case, but simplifies
323
    # the logic for removing it later.
324
    press_lower = concatenate((pressure[pressure >= lcl_pressure], lcl_pressure))
325
    t1 = dry_lapse(press_lower, temperature)
326
327
    # Find moist pseudo-adiabatic profile starting at the LCL
328
    press_upper = concatenate((lcl_pressure, pressure[pressure < lcl_pressure]))
329
    t2 = moist_lapse(press_upper, t1[-1]).to(t1.units)
330
331
    # Return LCL *without* the LCL point
332
    return concatenate((t1[:-1], t2[1:]))
333
334
335
@exporter.export
336
@check_units('[pressure]', '[dimensionless]')
337
def vapor_pressure(pressure, mixing):
338
    r"""Calculate water vapor (partial) pressure.
339
340
    Given total `pressure` and water vapor `mixing` ratio, calculates the
341
    partial pressure of water vapor.
342
343
    Parameters
344
    ----------
345
    pressure : `pint.Quantity`
346
        total atmospheric pressure
347
    mixing : `pint.Quantity`
348
        dimensionless mass mixing ratio
349
350
    Returns
351
    -------
352
    `pint.Quantity`
353
        The ambient water vapor (partial) pressure in the same units as
354
        `pressure`.
355
356
    Notes
357
    -----
358
    This function is a straightforward implementation of the equation given in many places,
359
    such as [Hobbs1977]_ pg.71:
360
361
    .. math:: e = p \frac{r}{r + \epsilon}
362
363
    See Also
364
    --------
365
    saturation_vapor_pressure, dewpoint
366
367
    """
368
    return pressure * mixing / (epsilon + mixing)
369
370
371
@exporter.export
372
@check_units('[temperature]')
373
def saturation_vapor_pressure(temperature):
374
    r"""Calculate the saturation water vapor (partial) pressure.
375
376
    Parameters
377
    ----------
378
    temperature : `pint.Quantity`
379
        The temperature
380
381
    Returns
382
    -------
383
    `pint.Quantity`
384
        The saturation water vapor (partial) pressure
385
386
    See Also
387
    --------
388
    vapor_pressure, dewpoint
389
390
    Notes
391
    -----
392
    Instead of temperature, dewpoint may be used in order to calculate
393
    the actual (ambient) water vapor (partial) pressure.
394
395
    The formula used is that from [Bolton1980]_ for T in degrees Celsius:
396
397
    .. math:: 6.112 e^\frac{17.67T}{T + 243.5}
398
399
    """
400
    # Converted from original in terms of C to use kelvin. Using raw absolute values of C in
401
    # a formula plays havoc with units support.
402
    return sat_pressure_0c * np.exp(17.67 * (temperature - 273.15 * units.kelvin) /
403
                                    (temperature - 29.65 * units.kelvin))
404
405
406
@exporter.export
407
@check_units('[temperature]', '[dimensionless]')
408
def dewpoint_rh(temperature, rh):
409
    r"""Calculate the ambient dewpoint given air temperature and relative humidity.
410
411
    Parameters
412
    ----------
413
    temperature : `pint.Quantity`
414
        Air temperature
415
    rh : `pint.Quantity`
416
        Relative humidity expressed as a ratio in the range (0, 1]
417
418
    Returns
419
    -------
420
    `pint.Quantity`
421
        The dew point temperature
422
423
    See Also
424
    --------
425
    dewpoint, saturation_vapor_pressure
426
427
    """
428
    if np.any(rh > 1.2):
429
        warnings.warn('Relative humidity >120%, ensure proper units.')
430
    return dewpoint(rh * saturation_vapor_pressure(temperature))
431
432
433
@exporter.export
434
@check_units('[pressure]')
435
def dewpoint(e):
436
    r"""Calculate the ambient dewpoint given the vapor pressure.
437
438
    Parameters
439
    ----------
440
    e : `pint.Quantity`
441
        Water vapor partial pressure
442
443
    Returns
444
    -------
445
    `pint.Quantity`
446
        Dew point temperature
447
448
    See Also
449
    --------
450
    dewpoint_rh, saturation_vapor_pressure, vapor_pressure
451
452
    Notes
453
    -----
454
    This function inverts the [Bolton1980]_ formula for saturation vapor
455
    pressure to instead calculate the temperature. This yield the following
456
    formula for dewpoint in degrees Celsius:
457
458
    .. math:: T = \frac{243.5 log(e / 6.112)}{17.67 - log(e / 6.112)}
459
460
    """
461
    val = np.log(e / sat_pressure_0c)
462
    return 0. * units.degC + 243.5 * units.delta_degC * val / (17.67 - val)
463
464
465
@exporter.export
466
@check_units('[pressure]', '[pressure]', '[dimensionless]')
467
def mixing_ratio(part_press, tot_press, molecular_weight_ratio=epsilon):
468
    r"""Calculate the mixing ratio of a gas.
469
470
    This calculates mixing ratio given its partial pressure and the total pressure of
471
    the air. There are no required units for the input arrays, other than that
472
    they have the same units.
473
474
    Parameters
475
    ----------
476
    part_press : `pint.Quantity`
477
        Partial pressure of the constituent gas
478
    tot_press : `pint.Quantity`
479
        Total air pressure
480
    molecular_weight_ratio : `pint.Quantity` or float, optional
481
        The ratio of the molecular weight of the constituent gas to that assumed
482
        for air. Defaults to the ratio for water vapor to dry air
483
        (:math:`\epsilon\approx0.622`).
484
485
    Returns
486
    -------
487
    `pint.Quantity`
488
        The (mass) mixing ratio, dimensionless (e.g. Kg/Kg or g/g)
489
490
    Notes
491
    -----
492
    This function is a straightforward implementation of the equation given in many places,
493
    such as [Hobbs1977]_ pg.73:
494
495
    .. math:: r = \epsilon \frac{e}{p - e}
496
497
    See Also
498
    --------
499
    saturation_mixing_ratio, vapor_pressure
500
501
    """
502
    return molecular_weight_ratio * part_press / (tot_press - part_press)
503
504
505
@exporter.export
506
@check_units('[pressure]', '[temperature]')
507
def saturation_mixing_ratio(tot_press, temperature):
508
    r"""Calculate the saturation mixing ratio of water vapor.
509
510
    This calculation is given total pressure and the temperature. The implementation
511
    uses the formula outlined in [Hobbs1977]_ pg.73.
512
513
    Parameters
514
    ----------
515
    tot_press: `pint.Quantity`
516
        Total atmospheric pressure
517
    temperature: `pint.Quantity`
518
        The temperature
519
520
    Returns
521
    -------
522
    `pint.Quantity`
523
        The saturation mixing ratio, dimensionless
524
525
    """
526
    return mixing_ratio(saturation_vapor_pressure(temperature), tot_press)
527
528
529
@exporter.export
530
@check_units('[pressure]', '[temperature]', '[temperature]')
531
def equivalent_potential_temperature(pressure, temperature, dewpoint):
532
    r"""Calculate equivalent potential temperature.
533
534
    This calculation must be given an air parcel's pressure, temperature, and dewpoint.
535
    The implementation uses the formula outlined in [Bolton1980]_:
536
537
    First, the LCL temperature is calculated:
538
539
    .. math:: T_{L}=\frac{1}{\frac{1}{T_{D}-56}+\frac{(T_{K}/T_{D})}{800}}+56
540
541
    Which is then used to calculate the potential temperature at the LCL:
542
543
    .. math:: \theta_{DL}=T_{K}\left(\frac{1000}{p-e}\right)^k
544
              \left(\frac{T_{K}}{T_{L}}\right)^{.28r}
545
546
    Both of these are used to calculate the final equivalent potential temperature:
547
548
    .. math:: \theta_{E}=\theta_{DL}\exp[\left(\left\frac{3036.}{T_{L}}
549
                                        -1.78\right)*r(1+.448r)\right]
550
551
    Parameters
552
    ----------
553
    pressure: `pint.Quantity`
554
        Total atmospheric pressure
555
    temperature: `pint.Quantity`
556
        Temperature of parcel
557
    dewpoint: `pint.Quantity`
558
        Dewpoint of parcel
559
560
    Returns
561
    -------
562
    `pint.Quantity`
563
        The equivalent potential temperature of the parcel
564
565
    Notes
566
    -----
567
    [Bolton1980]_ formula for Theta-e is used, since according to
568
    [Davies-Jones2009]_ it is the most accurate non-iterative formulation
569
    available.
570
571
    """
572
    t = temperature.to('kelvin').magnitude
573
    td = dewpoint.to('kelvin').magnitude
574
    p = pressure.to('hPa').magnitude
575
    e = saturation_vapor_pressure(dewpoint).to('hPa').magnitude
576
    r = saturation_mixing_ratio(pressure, dewpoint).magnitude
577
578
    t_l = 56 + 1. / (1. / (td - 56) + np.log(t / td) / 800.)
579
    th_l = t * (1000 / (p - e)) ** kappa * (t / t_l) ** (0.28 * r)
580
    th_e = th_l * np.exp((3036. / t_l - 1.78) * r * (1 + 0.448 * r))
581
582
    return th_e * units.kelvin
583
584
585
@exporter.export
586
@check_units('[temperature]', '[dimensionless]', '[dimensionless]')
587
def virtual_temperature(temperature, mixing, molecular_weight_ratio=epsilon):
588
    r"""Calculate virtual temperature.
589
590
    This calculation must be given an air parcel's temperature and mixing ratio.
591
    The implementation uses the formula outlined in [Hobbs2006]_ pg.80.
592
593
    Parameters
594
    ----------
595
    temperature: `pint.Quantity`
596
        The temperature
597
    mixing : `pint.Quantity`
598
        dimensionless mass mixing ratio
599
    molecular_weight_ratio : `pint.Quantity` or float, optional
600
        The ratio of the molecular weight of the constituent gas to that assumed
601
        for air. Defaults to the ratio for water vapor to dry air.
602
        (:math:`\epsilon\approx0.622`).
603
604
    Returns
605
    -------
606
    `pint.Quantity`
607
        The corresponding virtual temperature of the parcel
608
609
    Notes
610
    -----
611
    .. math:: T_v = T \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
612
613
    """
614
    return temperature * ((mixing + molecular_weight_ratio) /
615
                          (molecular_weight_ratio * (1 + mixing)))
616
617
618
@exporter.export
619
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
620
def virtual_potential_temperature(pressure, temperature, mixing,
621
                                  molecular_weight_ratio=epsilon):
622
    r"""Calculate virtual potential temperature.
623
624
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
625
    The implementation uses the formula outlined in [Markowski2010]_ pg.13.
626
627
    Parameters
628
    ----------
629
    pressure: `pint.Quantity`
630
        Total atmospheric pressure
631
    temperature: `pint.Quantity`
632
        The temperature
633
    mixing : `pint.Quantity`
634
        dimensionless mass mixing ratio
635
    molecular_weight_ratio : `pint.Quantity` or float, optional
636
        The ratio of the molecular weight of the constituent gas to that assumed
637
        for air. Defaults to the ratio for water vapor to dry air.
638
        (:math:`\epsilon\approx0.622`).
639
640
    Returns
641
    -------
642
    `pint.Quantity`
643
        The corresponding virtual potential temperature of the parcel
644
645
    Notes
646
    -----
647
    .. math:: \Theta_v = \Theta \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
648
649
    """
650
    pottemp = potential_temperature(pressure, temperature)
651
    return virtual_temperature(pottemp, mixing, molecular_weight_ratio)
652
653
654
@exporter.export
655
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
656
def density(pressure, temperature, mixing, molecular_weight_ratio=epsilon):
657
    r"""Calculate density.
658
659
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
660
    The implementation uses the formula outlined in [Hobbs2006]_ pg.67.
661
662
    Parameters
663
    ----------
664
    temperature: `pint.Quantity`
665
        The temperature
666
    pressure: `pint.Quantity`
667
        Total atmospheric pressure
668
    mixing : `pint.Quantity`
669
        dimensionless mass mixing ratio
670
    molecular_weight_ratio : `pint.Quantity` or float, optional
671
        The ratio of the molecular weight of the constituent gas to that assumed
672
        for air. Defaults to the ratio for water vapor to dry air.
673
        (:math:`\epsilon\approx0.622`).
674
675
    Returns
676
    -------
677
    `pint.Quantity`
678
        The corresponding density of the parcel
679
680
    Notes
681
    -----
682
    .. math:: \rho = \frac{p}{R_dT_v}
683
684
    """
685
    virttemp = virtual_temperature(temperature, mixing, molecular_weight_ratio)
686
    return (pressure / (Rd * virttemp)).to(units.kilogram / units.meter ** 3)
687
688
689
@exporter.export
690
@check_units('[temperature]', '[temperature]', '[pressure]')
691
def relative_humidity_wet_psychrometric(dry_bulb_temperature, web_bulb_temperature,
692
                                        pressure, **kwargs):
693
    r"""Calculate the relative humidity with wet bulb and dry bulb temperatures.
694
695
    This uses a psychrometric relationship as outlined in [WMO8-2014]_, with
696
    coefficients from [Fan1987]_.
697
698
    Parameters
699
    ----------
700
    dry_bulb_temperature: `pint.Quantity`
701
        Dry bulb temperature
702
    web_bulb_temperature: `pint.Quantity`
703
        Wet bulb temperature
704
    pressure: `pint.Quantity`
705
        Total atmospheric pressure
706
707
    Returns
708
    -------
709
    `pint.Quantity`
710
        Relative humidity
711
712
    Notes
713
    -----
714
    .. math:: RH = 100 \frac{e}{e_s}
715
716
    * :math:`RH` is relative humidity
717
    * :math:`e` is vapor pressure from the wet psychrometric calculation
718
    * :math:`e_s` is the saturation vapor pressure
719
720
    See Also
721
    --------
722
    psychrometric_vapor_pressure_wet, saturation_vapor_pressure
723
724
    """
725
    return (100 * units.percent * psychrometric_vapor_pressure_wet(dry_bulb_temperature,
726
            web_bulb_temperature, pressure, **kwargs) /
727
            saturation_vapor_pressure(dry_bulb_temperature))
728
729
730
@exporter.export
731
@check_units('[temperature]', '[temperature]', '[pressure]')
732
def psychrometric_vapor_pressure_wet(dry_bulb_temperature, wet_bulb_temperature, pressure,
733
                                     psychrometer_coefficient=6.21e-4 / units.kelvin):
734
    r"""Calculate the vapor pressure with wet bulb and dry bulb temperatures.
735
736
    This uses a psychrometric relationship as outlined in [WMO8-2014]_, with
737
    coefficients from [Fan1987]_.
738
739
    Parameters
740
    ----------
741
    dry_bulb_temperature: `pint.Quantity`
742
        Dry bulb temperature
743
    wet_bulb_temperature: `pint.Quantity`
744
        Wet bulb temperature
745
    pressure: `pint.Quantity`
746
        Total atmospheric pressure
747
    psychrometer_coefficient: `pint.Quantity`, optional
748
        Psychrometer coefficient. Defaults to 6.21e-4 K^-1.
749
750
    Returns
751
    -------
752
    `pint.Quantity`
753
        Vapor pressure
754
755
    Notes
756
    -----
757
    .. math:: e' = e'_w(T_w) - A p (T - T_w)
758
759
    * :math:`e'` is vapor pressure
760
    * :math:`e'_w(T_w)` is the saturation vapor pressure with respect to water at temperature
761
      :math:`T_w`
762
    * :math:`p` is the pressure of the wet bulb
763
    * :math:`T` is the temperature of the dry bulb
764
    * :math:`T_w` is the temperature of the wet bulb
765
    * :math:`A` is the psychrometer coefficient
766
767
    Psychrometer coefficient depends on the specific instrument being used and the ventilation
768
    of the instrument.
769
770
    See Also
771
    --------
772
    saturation_vapor_pressure
773
774
    """
775
    return (saturation_vapor_pressure(wet_bulb_temperature) - psychrometer_coefficient *
776
            pressure * (dry_bulb_temperature - wet_bulb_temperature).to('kelvin'))
777
778
779
@exporter.export
780
@check_units('[dimensionless]', '[temperature]', '[pressure]')
781
def relative_humidity_from_mixing_ratio(mixing_ratio, temperature, pressure):
782
    r"""Calculate the relative humidity from mixing ratio, temperature, and pressure.
783
784
    Parameters
785
    ----------
786
    mixing_ratio: `pint.Quantity`
787
        Dimensionless mass mixing ratio
788
    temperature: `pint.Quantity`
789
        Air temperature
790
    pressure: `pint.Quantity`
791
        Total atmospheric pressure
792
793
    Returns
794
    -------
795
    `pint.Quantity`
796
        Relative humidity
797
798
    Notes
799
    -----
800
    Formula from [Hobbs1977]_ pg. 74.
801
802
    .. math:: RH = 100 \frac{w}{w_s}
803
804
    * :math:`RH` is relative humidity
805
    * :math:`w` is mxing ratio
806
    * :math:`w_s` is the saturation mixing ratio
807
808
    See Also
809
    --------
810
    saturation_mixing_ratio
811
812
    """
813
    return (100 * units.percent *
814
            mixing_ratio / saturation_mixing_ratio(pressure, temperature))
815
816
817
@exporter.export
818
@check_units('[dimensionless]')
819
def mixing_ratio_from_specific_humidity(specific_humidity):
820
    r"""Calculate the mixing ratio from specific humidity.
821
822
    Parameters
823
    ----------
824
    specific_humidity: `pint.Quantity`
825
        Specific humidity of air
826
827
    Returns
828
    -------
829
    `pint.Quantity`
830
        Mixing ratio
831
832
    Notes
833
    -----
834
    Formula from [Salby1996]_ pg. 118.
835
836
    .. math:: w = \frac{q}{1-q}
837
838
    * :math:`w` is mxing ratio
839
    * :math:`q` is the specific humidity
840
841
    See Also
842
    --------
843
    mixing_ratio
844
845
    """
846
    return specific_humidity / (1 - specific_humidity)
847
848
849
@exporter.export
850
@check_units('[dimensionless]', '[temperature]', '[pressure]')
851
def relative_humidity_from_specific_humidity(specific_humidity, temperature, pressure):
852
    r"""Calculate the relative humidity from specific humidity, temperature, and pressure.
853
854
    Parameters
855
    ----------
856
    specific_humidity: `pint.Quantity`
857
        Specific humidity of air
858
    temperature: `pint.Quantity`
859
        Air temperature
860
    pressure: `pint.Quantity`
861
        Total atmospheric pressure
862
863
    Returns
864
    -------
865
    `pint.Quantity`
866
        Relative humidity
867
868
    Notes
869
    -----
870
    Formula from [Hobbs1977]_ pg. 74. and [Salby1996]_ pg. 118.
871
872
    .. math:: RH = 100 \frac{q}{(1-q)w_s}
873
874
    * :math:`RH` is relative humidity
875
    * :math:`q` is specific humidity
876
    * :math:`w_s` is the saturation mixing ratio
877
878
    See Also
879
    --------
880
    relative_humidity_from_mixing_ratio
881
882
    """
883
    return (100 * units.percent *
884
            mixing_ratio_from_specific_humidity(specific_humidity) /
885
            saturation_mixing_ratio(pressure, temperature))
886
887
888
@exporter.export
889
@check_units('[pressure]', '[temperature]', '[temperature]', '[temperature]')
890
def cape_cin(pressure, temperature, dewpt, parcel_profile):
891
    r"""Calculate CAPE and CIN.
892
893
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
894
    of a given upper air profile and parcel path. CIN is integrated between the surface and
895
    LFC, CAPE is integrated between the LFC and EL (or top of sounding). Intersection points of
896
    the measured temperature profile and parcel profile are linearly interpolated.
897
898
    Parameters
899
    ----------
900
    pressure : `pint.Quantity`
901
        The atmospheric pressure level(s) of interest. The first entry should be the starting
902
        point pressure.
903
    temperature : `pint.Quantity`
904
        The atmospheric temperature corresponding to pressure.
905
    dewpt : `pint.Quantity`
906
        The atmospheric dew point corresponding to pressure.
907
    parcel_profile : `pint.Quantity`
908
        The temperature profile of the parcel
909
910
    Returns
911
    -------
912
    `pint.Quantity`
913
        Convective available potential energy (CAPE).
914
    `pint.Quantity`
915
        Convective inhibition (CIN).
916
917
    Notes
918
    -----
919
    Formula adopted from [Hobbs1977]_.
920
921
    .. math:: \text{CAPE} = -R_d \int_{LFC}^{EL} (T_{parcel} - T_{env}) d\text{ln}(p)
922
923
    .. math:: \text{CIN} = -R_d \int_{SFC}^{LFC} (T_{parcel} - T_{env}) d\text{ln}(p)
924
925
926
    * :math:`CAPE` Convective available potential energy
927
    * :math:`CIN` Convective inhibition
928
    * :math:`LFC` Pressure of the level of free convection
929
    * :math:`EL` Pressure of the equilibrium level
930
    * :math:`SFC` Level of the surface or beginning of parcel path
931
    * :math:`R_d` Gas constant
932
    * :math:`g` Gravitational acceleration
933
    * :math:`T_{parcel}` Parcel temperature
934
    * :math:`T_{env}` Environment temperature
935
    * :math:`p` Atmospheric pressure
936
937
    See Also
938
    --------
939
    lfc, el
940
941
    """
942
    # Calculate LFC limit of integration
943
    lfc_pressure = lfc(pressure, temperature, dewpt)[0]
944
945
    # If there is no LFC, no need to proceed.
946
    if np.isnan(lfc_pressure):
947
        return 0 * units('J/kg'), 0 * units('J/kg')
948
    else:
949
        lfc_pressure = lfc_pressure.magnitude
950
951
    # Calculate the EL limit of integration
952
    el_pressure = el(pressure, temperature, dewpt)[0]
953
954
    # No EL and we use the top reading of the sounding.
955
    if np.isnan(el_pressure):
956
        el_pressure = pressure[-1].magnitude
957
    else:
958
        el_pressure = el_pressure.magnitude
959
960
    # Difference between the parcel path and measured temperature profiles
961
    y = (parcel_profile - temperature).to(units.degK)
962
963
    # Estimate zero crossings
964
    x, y = _find_append_zero_crossings(np.copy(pressure), y)
965
966
    # CAPE
967
    # Only use data between the LFC and EL for calculation
968
    p_mask = _less_or_close(x, lfc_pressure) & _greater_or_close(x, el_pressure)
969
    x_clipped = x[p_mask]
970
    y_clipped = y[p_mask]
971
    cape = (Rd * (np.trapz(y_clipped, np.log(x_clipped)) * units.degK)).to(units('J/kg'))
972
973
    # CIN
974
    # Only use data between the surface and LFC for calculation
975
    p_mask = _greater_or_close(x, lfc_pressure)
976
    x_clipped = x[p_mask]
977
    y_clipped = y[p_mask]
978
    cin = (Rd * (np.trapz(y_clipped, np.log(x_clipped)) * units.degK)).to(units('J/kg'))
979
980
    return cape, cin
981
982
983
def _find_append_zero_crossings(x, y):
984
    r"""
985
    Find and interpolate zero crossings.
986
987
    Estimate the zero crossings of an x,y series and add estimated crossings to series,
988
    returning a sorted array with no duplicate values.
989
990
    Parameters
991
    ----------
992
    x : `pint.Quantity`
993
        x values of data
994
    y : `pint.Quantity`
995
        y values of data
996
997
    Returns
998
    -------
999
    x : `pint.Quantity`
1000
        x values of data
1001
    y : `pint.Quantity`
1002
        y values of data
1003
1004
    """
1005
    # Find and append crossings to the data
1006
    crossings = find_intersections(x[1:], y[1:], np.zeros_like(y[1:]) * y.units)
1007
    x = concatenate((x, crossings[0]))
1008
    y = concatenate((y, crossings[1]))
1009
1010
    # Resort so that data are in order
1011
    sort_idx = np.argsort(x)
1012
    x = x[sort_idx]
1013
    y = y[sort_idx]
1014
1015
    # Remove duplicate data points if there are any
1016
    keep_idx = np.ediff1d(x, to_end=[1]) > 0
1017
    x = x[keep_idx]
1018
    y = y[keep_idx]
1019
    return x, y
1020
1021
1022
@exporter.export
1023
@check_units('[pressure]', '[temperature]', '[temperature]')
1024
def most_unstable_parcel(pressure, temperature, dewpoint, heights=None,
1025
                         bottom=None, depth=300 * units.hPa):
1026
    """
1027
    Determine the most unstable parcel in a layer.
1028
1029
    Determines the most unstable parcel of air by calculating the equivalent
1030
    potential temperature and finding its maximum in the specified layer.
1031
1032
    Parameters
1033
    ----------
1034
    pressure: `pint.Quantity`
1035
        Atmospheric pressure profile
1036
    temperature: `pint.Quantity`
1037
        Atmospheric temperature profile
1038
    dewpoint: `pint.Quantity`
1039
        Atmospheric dewpoint profile
1040
    heights: `pint.Quantity`, optional
1041
        Atmospheric height profile. Standard atmosphere assumed when None (the default).
1042
    bottom: `pint.Quantity`, optional
1043
        Bottom of the layer to consider for the calculation in pressure or height.
1044
        Defaults to using the bottom pressure or height.
1045
    depth: `pint.Quantity`, optional
1046
        Depth of the layer to consider for the calculation in pressure or height. Defaults
1047
        to 300 hPa.
1048
1049
    Returns
1050
    -------
1051
    `pint.Quantity`
1052
        Pressure, temperature, and dew point of most unstable parcel in the profile.
1053
    integer
1054
        Index of the most unstable parcel in the given profile
1055
1056
    See Also
1057
    --------
1058
    get_layer
1059
1060
    """
1061
    p_layer, T_layer, Td_layer = get_layer(pressure, temperature, dewpoint, bottom=bottom,
1062
                                           depth=depth, heights=heights, interpolate=False)
1063
    theta_e = equivalent_potential_temperature(p_layer, T_layer, Td_layer)
1064
    max_idx = np.argmax(theta_e)
1065
    return p_layer[max_idx], T_layer[max_idx], Td_layer[max_idx], max_idx
1066
1067
1068
@exporter.export
1069
@check_units('[temperature]', '[pressure]', '[temperature]')
1070
def isentropic_interpolation(theta_levels, pressure, temperature, *args, **kwargs):
1071
    r"""Interpolate data in isobaric coordinates to isentropic coordinates.
1072
1073
    Parameters
1074
    ----------
1075
    theta_levels : array
1076
        One-dimensional array of desired theta surfaces
1077
    pressure : array
1078
        One-dimensional array of pressure levels
1079
    temperature : array
1080
        Array of temperature
1081
    args : array, optional
1082
        Any additional variables will be interpolated to each isentropic level.
1083
1084
    Returns
1085
    -------
1086
    list
1087
        List with pressure at each isentropic level, followed by each additional
1088
        argument interpolated to isentropic coordinates.
1089
1090
    Other Parameters
1091
    ----------------
1092
    axis : int, optional
1093
        The axis corresponding to the vertical in the temperature array, defaults to 0.
1094
    tmpk_out : bool, optional
1095
        If true, will calculate temperature and output as the last item in the output list.
1096
        Defaults to False.
1097
    max_iters : int, optional
1098
        The maximum number of iterations to use in calculation, defaults to 50.
1099
    eps : float, optional
1100
        The desired absolute error in the calculated value, defaults to 1e-6.
1101
1102
    Notes
1103
    -----
1104
    Input variable arrays must have the same number of vertical levels as the pressure levels
1105
    array. Pressure is calculated on isentropic surfaces by assuming that temperature varies
1106
    linearly with the natural log of pressure. Linear interpolation is then used in the
1107
    vertical to find the pressure at each isentropic level. Interpolation method from
1108
    [Ziv1994]_. Any additional arguments are assumed to vary linearly with temperature and will
1109
    be linearly interpolated to the new isentropic levels.
1110
1111
    See Also
1112
    --------
1113
    potential_temperature
1114
1115
    """
1116
    # iteration function to be used later
1117
    # Calculates theta from linearly interpolated temperature and solves for pressure
1118
    def _isen_iter(iter_log_p, isentlevs_nd, ka, a, b, pok):
1119
        exner = pok * np.exp(-ka * iter_log_p)
1120
        t = a * iter_log_p + b
1121
        # Newton-Raphson iteration
1122
        f = isentlevs_nd - t * exner
1123
        fp = exner * (ka * t - a)
1124
        return iter_log_p - (f / fp)
1125
1126
    # Change when Python 2.7 no longer supported
1127
    # Pull out keyword arguments
1128
    tmpk_out = kwargs.pop('tmpk_out', False)
1129
    max_iters = kwargs.pop('max_iters', 50)
1130
    eps = kwargs.pop('eps', 1e-6)
1131
    axis = kwargs.pop('axis', 0)
1132
1133
    # Get dimensions in temperature
1134
    ndim = temperature.ndim
1135
1136
    # Convert units
1137
    pres = pressure.to('hPa')
1138
    temperature = temperature.to('kelvin')
1139
1140
    slices = [np.newaxis] * ndim
1141
    slices[axis] = slice(None)
1142
    pres = pres[slices]
1143
    pres = np.broadcast_to(pres, temperature.shape) * pres.units
1144
1145
    # Sort input data
1146
    sort_pres = np.argsort(pres.m, axis=axis)
1147
    sort_pres = np.swapaxes(np.swapaxes(sort_pres, 0, axis)[::-1], 0, axis)
1148
    sorter = broadcast_indices(pres, sort_pres, ndim, axis)
1149
    levs = pres[sorter]
1150
    theta_levels = np.asanyarray(theta_levels.to('kelvin')).reshape(-1)
1151
    sort_isentlevs = np.argsort(theta_levels)
1152
    tmpk = temperature[sorter]
1153
    isentlevels = theta_levels[sort_isentlevs]
1154
1155
    # Make the desired isentropic levels the same shape as temperature
1156
    isentlevs_nd = isentlevels
1157
    isentlevs_nd = isentlevs_nd[slices]
1158
    shape = list(temperature.shape)
1159
    shape[axis] = isentlevels.size
1160
    isentlevs_nd = np.broadcast_to(isentlevs_nd, shape)
1161
1162
    # exponent to Poisson's Equation, which is imported above
1163
    ka = kappa.to('dimensionless').m
1164
1165
    # calculate theta for each point
1166
    pres_theta = potential_temperature(levs, tmpk)
1167
1168
    # Raise error if input theta level is larger than pres_theta max
1169
    if np.max(pres_theta.m) < np.max(theta_levels):
1170
        raise ValueError('Input theta level out of data bounds')
1171
1172
    # Find log of pressure to implement assumption of linear temperature dependence on
1173
    # ln(p)
1174
    log_p = np.log(levs.m)
1175
1176
    # Calculations for interpolation routine
1177
    pok = P0 ** ka
1178
1179
    # index values for each point for the pressure level nearest to the desired theta level
1180
    minv = np.apply_along_axis(np.searchsorted, axis, pres_theta.m, theta_levels)
1181
1182
    # Create index values for broadcasting arrays
1183
    above = broadcast_indices(tmpk, minv, ndim, axis)
1184
    below = broadcast_indices(tmpk, minv - 1, ndim, axis)
1185
1186
    # calculate constants for the interpolation
1187
    a = (tmpk.m[above] - tmpk.m[below]) / (log_p[above] - log_p[below])
1188
    b = tmpk.m[above] - a * log_p[above]
1189
1190
    # calculate first guess for interpolation
1191
    first_guess = 0.5 * (log_p[above] + log_p[below])
1192
1193
    # iterative interpolation using scipy.optimize.fixed_point and _isen_iter defined above
1194
    log_p_solved = so.fixed_point(_isen_iter, first_guess, args=(isentlevs_nd, ka, a, b,
1195
                                                                 pok.m),
1196
                                  xtol=eps, maxiter=max_iters)
1197
1198
    # get back pressure and assign nan for values with pressure greater than 1000 hPa
1199
    isentprs = np.exp(log_p_solved)
1200
    isentprs[isentprs > np.max(pressure.m)] = np.nan
1201
1202
    # create list for storing output data
1203
    ret = []
1204
    ret.append(isentprs * units.hPa)
1205
1206
    # if tmpk_out = true, calculate temperature and output as last item in list
1207
    if tmpk_out:
1208
        ret.append((isentlevs_nd / ((P0.m / isentprs) ** ka)) * units.kelvin)
1209
1210
    # check to see if any additional arguments were given, if so, interpolate to
1211
    # new isentropic levels
1212
    try:
1213
        args[0]
1214
    except IndexError:
1215
        return ret
1216
    else:
1217
        # do an interpolation for each additional argument
1218
        for arr in args:
1219
            var = arr[sorter]
1220
            # interpolate to isentropic levels and add to temporary output array
1221
            arg_out = interp(isentlevels, pres_theta.m, var, axis=axis)
1222
            ret.append(arg_out)
1223
1224
    # output values as a list
1225
    return ret
1226
1227
1228
@exporter.export
1229
@check_units('[pressure]', '[temperature]', '[temperature]')
1230
def surface_based_cape_cin(pressure, temperature, dewpoint):
1231
    r"""Calculate surface-based CAPE and CIN.
1232
1233
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
1234
    of a given upper air profile for a surface-based parcel. CIN is integrated
1235
    between the surface and LFC, CAPE is integrated between the LFC and EL (or top of
1236
    sounding). Intersection points of the measured temperature profile and parcel profile are
1237
    linearly interpolated.
1238
1239
    Parameters
1240
    ----------
1241
    pressure : `pint.Quantity`
1242
        Atmospheric pressure profile. The first entry should be the starting
1243
        (surface) observation.
1244
    temperature : `pint.Quantity`
1245
        Temperature profile
1246
    dewpoint : `pint.Quantity`
1247
        Dewpoint profile
1248
1249
    Returns
1250
    -------
1251
    `pint.Quantity`
1252
        Surface based Convective Available Potential Energy (CAPE).
1253
    `pint.Quantity`
1254
        Surface based Convective INhibition (CIN).
1255
1256
    See Also
1257
    --------
1258
    cape_cin, parcel_profile
1259
1260
    """
1261
    profile = parcel_profile(pressure, temperature[0], dewpoint[0])
1262
    return cape_cin(pressure, temperature, dewpoint, profile)
1263
1264
1265
@exporter.export
1266
@check_units('[pressure]', '[temperature]', '[temperature]')
1267
def most_unstable_cape_cin(pressure, temperature, dewpoint, **kwargs):
1268
    r"""Calculate most unstable CAPE/CIN.
1269
1270
    Calculate the convective available potential energy (CAPE) and convective inhibition (CIN)
1271
    of a given upper air profile and most unstable parcel path. CIN is integrated between the
1272
    surface and LFC, CAPE is integrated between the LFC and EL (or top of sounding).
1273
    Intersection points of the measured temperature profile and parcel profile are linearly
1274
    interpolated.
1275
1276
    Parameters
1277
    ----------
1278
    pressure : `pint.Quantity`
1279
        Pressure profile
1280
    temperature : `pint.Quantity`
1281
        Temperature profile
1282
    dewpoint : `pint.Quantity`
1283
        Dewpoint profile
1284
1285
    Returns
1286
    -------
1287
    `pint.Quantity`
1288
        Most unstable Convective Available Potential Energy (CAPE).
1289
    `pint.Quantity`
1290
        Most unstable Convective INhibition (CIN).
1291
1292
    See Also
1293
    --------
1294
    cape_cin, most_unstable_parcel, parcel_profile
1295
1296
    """
1297
    _, parcel_temperature, parcel_dewpoint, parcel_idx = most_unstable_parcel(pressure,
1298
                                                                              temperature,
1299
                                                                              dewpoint,
1300
                                                                              **kwargs)
1301
    mu_profile = parcel_profile(pressure[parcel_idx:], parcel_temperature, parcel_dewpoint)
1302
    return cape_cin(pressure[parcel_idx:], temperature[parcel_idx:],
1303
                    dewpoint[parcel_idx:], mu_profile)
1304
1305
1306
@exporter.export
1307
@check_units('[pressure]', '[temperature]', '[temperature]')
1308
def mixed_parcel(p, temperature, dewpt, parcel_start_pressure=None,
1309
                 heights=None, bottom=None, depth=100 * units.hPa, interpolate=True):
1310
    r"""Calculate the properties of a parcel mixed from a layer.
1311
1312
    Determines the properties of an air parcel that is the result of complete mixing of a
1313
    given atmospheric layer.
1314
1315
    Parameters
1316
    ----------
1317
    p : `pint.Quantity`
1318
        Atmospheric pressure profile
1319
    temperature : `pint.Quantity`
1320
        Atmospheric temperature profile
1321
    dewpt : `pint.Quantity`
1322
        Atmospheric dewpoint profile
1323
    parcel_start_pressure : `pint.Quantity`, optional
1324
        Pressure at which the mixed parcel should begin (default None)
1325
    heights: `pint.Quantity`, optional
1326
        Atmospheric heights corresponding to the given pressures (default None)
1327
    bottom : `pint.Quantity`, optional
1328
        The bottom of the layer as a pressure or height above the surface pressure
1329
        (default None)
1330
    depth : `pint.Quantity`, optional
1331
        The thickness of the layer as a pressure or height above the bottom of the layer
1332
        (default 100 hPa)
1333
    interpolate : bool, optional
1334
        Interpolate the top and bottom points if they are not in the given data
1335
1336
    Returns
1337
    -------
1338
    `pint.Quantity, pint.Quantity, pint.Quantity`
1339
        The pressure, temperature, and dewpoint of the mixed parcel.
1340
1341
    """
1342
    # If a parcel starting pressure is not provided, use the surface
1343
    if not parcel_start_pressure:
1344
        parcel_start_pressure = p[0]
1345
1346
    # Calculate the potential temperature and mixing ratio over the layer
1347
    theta = potential_temperature(p, temperature)
1348
    mixing_ratio = saturation_mixing_ratio(p, dewpt)
1349
1350
    # Mix the variables over the layer
1351
    mean_theta, mean_mixing_ratio = mixed_layer(p, theta, mixing_ratio, bottom=bottom,
1352
                                                heights=heights, depth=depth,
1353
                                                interpolate=interpolate)
1354
1355
    # Convert back to temperature
1356
    mean_temperature = (mean_theta / potential_temperature(parcel_start_pressure,
1357
                                                           1 * units.kelvin)) * units.kelvin
1358
1359
    # Convert back to dewpoint
1360
    mean_vapor_pressure = vapor_pressure(parcel_start_pressure, mean_mixing_ratio)
1361
    mean_dewpoint = dewpoint(mean_vapor_pressure)
1362
1363
    return (parcel_start_pressure, mean_temperature.to(temperature.units),
1364
            mean_dewpoint.to(dewpt.units))
1365
1366
1367
@exporter.export
1368
@check_units('[pressure]')
1369
def mixed_layer(p, *args, **kwargs):
1370
    r"""Mix variable(s) over a layer, yielding a mass-weighted average.
1371
1372
    This function will integrate a data variable with respect to pressure and determine the
1373
    average value using the mean value theorem.
1374
1375
    Parameters
1376
    ----------
1377
    p : array-like
1378
        Atmospheric pressure profile
1379
    datavar : array-like
1380
        Atmospheric variable measured at the given pressures
1381
    heights: array-like, optional
1382
        Atmospheric heights corresponding to the given pressures (default None)
1383
    bottom : `pint.Quantity`, optional
1384
        The bottom of the layer as a pressure or height above the surface pressure
1385
        (default None)
1386
    depth : `pint.Quantity`, optional
1387
        The thickness of the layer as a pressure or height above the bottom of the layer
1388
        (default 100 hPa)
1389
    interpolate : bool, optional
1390
        Interpolate the top and bottom points if they are not in the given data
1391
1392
    Returns
1393
    -------
1394
    `pint.Quantity`
1395
        The mixed value of the data variable.
1396
1397
    """
1398
    # Pull out keyword arguments, remove when we drop Python 2.7
1399
    heights = kwargs.pop('heights', None)
1400
    bottom = kwargs.pop('bottom', None)
1401
    depth = kwargs.pop('depth', 100 * units.hPa)
1402
    interpolate = kwargs.pop('interpolate', True)
1403
1404
    layer = get_layer(p, *args, heights=heights, bottom=bottom,
1405
                      depth=depth, interpolate=interpolate)
1406
    p_layer = layer[0]
1407
    datavars_layer = layer[1:]
1408
1409
    ret = []
1410
    for datavar_layer in datavars_layer:
1411
        actual_depth = abs(p_layer[0] - p_layer[-1])
1412
        ret.append((-1. / actual_depth.m) * np.trapz(datavar_layer, p_layer) *
1413
                   datavar_layer.units)
1414
    return ret
1415