Completed
Pull Request — master (#440)
by
unknown
01:32
created

mixed_layer()   A

Complexity

Conditions 1

Size

Total Lines 3

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 3
rs 10
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
11
from .tools import find_intersections
12
from ..constants import Cp_d, epsilon, kappa, Lv, P0, Rd
13
from ..package_tools import Exporter
14
from ..units import atleast_1d, check_units, concatenate, units
15
16
exporter = Exporter(globals())
17
18
sat_pressure_0c = 6.112 * units.millibar
19
20
21
@exporter.export
22
@check_units('[pressure]', '[temperature]')
23
def potential_temperature(pressure, temperature):
24
    r"""Calculate the potential temperature.
25
26
    Uses the Poisson equation to calculation the potential temperature
27
    given `pressure` and `temperature`.
28
29
    Parameters
30
    ----------
31
    pressure : `pint.Quantity`
32
        The total atmospheric pressure
33
    temperature : `pint.Quantity`
34
        The temperature
35
36
    Returns
37
    -------
38
    `pint.Quantity`
39
        The potential temperature corresponding to the the temperature and
40
        pressure.
41
42
    See Also
43
    --------
44
    dry_lapse
45
46
    Notes
47
    -----
48
    Formula:
49
50
    .. math:: \Theta = T (P_0 / P)^\kappa
51
52
    Examples
53
    --------
54
    >>> from metpy.units import units
55
    >>> metpy.calc.potential_temperature(800. * units.mbar, 273. * units.kelvin)
56
    <Quantity(290.96653180346203, 'kelvin')>
57
58
    """
59
    return temperature * (P0 / pressure).to('dimensionless')**kappa
60
61
62
@exporter.export
63
@check_units('[pressure]', '[temperature]')
64
def dry_lapse(pressure, temperature):
65
    r"""Calculate the temperature at a level assuming only dry processes.
66
67
    This function lifts a parcel starting at `temperature`, conserving
68
    potential temperature. The starting pressure should be the first item in
69
    the `pressure` array.
70
71
    Parameters
72
    ----------
73
    pressure : `pint.Quantity`
74
        The atmospheric pressure level(s) of interest
75
    temperature : `pint.Quantity`
76
        The starting temperature
77
78
    Returns
79
    -------
80
    `pint.Quantity`
81
       The resulting parcel temperature at levels given by `pressure`
82
83
    See Also
84
    --------
85
    moist_lapse : Calculate parcel temperature assuming liquid saturation
86
                  processes
87
    parcel_profile : Calculate complete parcel profile
88
    potential_temperature
89
90
    """
91
    return temperature * (pressure / pressure[0])**kappa
92
93
94
@exporter.export
95
@check_units('[pressure]', '[temperature]')
96
def moist_lapse(pressure, temperature):
97
    r"""Calculate the temperature at a level assuming liquid saturation processes.
98
99
    This function lifts a parcel starting at `temperature`. The starting
100
    pressure should be the first item in the `pressure` array. Essentially,
101
    this function is calculating moist pseudo-adiabats.
102
103
    Parameters
104
    ----------
105
    pressure : `pint.Quantity`
106
        The atmospheric pressure level(s) of interest
107
    temperature : `pint.Quantity`
108
        The starting temperature
109
110
    Returns
111
    -------
112
    `pint.Quantity`
113
       The temperature corresponding to the the starting temperature and
114
       pressure levels.
115
116
    See Also
117
    --------
118
    dry_lapse : Calculate parcel temperature assuming dry adiabatic processes
119
    parcel_profile : Calculate complete parcel profile
120
121
    Notes
122
    -----
123
    This function is implemented by integrating the following differential
124
    equation:
125
126
    .. math:: \frac{dT}{dP} = \frac{1}{P} \frac{R_d T + L_v r_s}
127
                                {C_{pd} + \frac{L_v^2 r_s \epsilon}{R_d T^2}}
128
129
    This equation comes from [Bakhshaii2013]_.
130
131
    """
132
    def dt(t, p):
133
        t = units.Quantity(t, temperature.units)
134
        p = units.Quantity(p, pressure.units)
135
        rs = saturation_mixing_ratio(p, t)
136
        frac = ((Rd * t + Lv * rs) /
137
                (Cp_d + (Lv * Lv * rs * epsilon / (Rd * t * t)))).to('kelvin')
138
        return frac / p
139
    return units.Quantity(si.odeint(dt, atleast_1d(temperature).squeeze(),
140
                                    pressure.squeeze()).T.squeeze(), temperature.units)
141
142
143
@exporter.export
144
@check_units('[pressure]', '[temperature]', '[temperature]')
145
def lcl(pressure, temperature, dewpt, max_iters=50, eps=1e-2):
146
    r"""Calculate the lifted condensation level (LCL) using from the starting point.
147
148
    The starting state for the parcel is defined by `temperature`, `dewpt`,
149
    and `pressure`.
150
151
    Parameters
152
    ----------
153
    pressure : `pint.Quantity`
154
        The starting atmospheric pressure
155
    temperature : `pint.Quantity`
156
        The starting temperature
157
    dewpt : `pint.Quantity`
158
        The starting dew point
159
160
    Returns
161
    -------
162
    `(pint.Quantity, pint.Quantity)`
163
        The LCL pressure and temperature
164
165
    Other Parameters
166
    ----------------
167
    max_iters : int, optional
168
        The maximum number of iterations to use in calculation, defaults to 50.
169
    eps : float, optional
170
        The desired absolute error in the calculated value, defaults to 1e-2.
171
172
    See Also
173
    --------
174
    parcel_profile
175
176
    Notes
177
    -----
178
    This function is implemented using an iterative approach to solve for the
179
    LCL. The basic algorithm is:
180
181
    1. Find the dew point from the LCL pressure and starting mixing ratio
182
    2. Find the LCL pressure from the starting temperature and dewpoint
183
    3. Iterate until convergence
184
185
    The function is guaranteed to finish by virtue of the `max_iters` counter.
186
187
    """
188
    w = mixing_ratio(saturation_vapor_pressure(dewpt), pressure)
189
    new_p = p = pressure
190
    eps = units.Quantity(eps, p.units)
191
    while max_iters:
192
        td = dewpoint(vapor_pressure(p, w))
193
        new_p = pressure * (td / temperature) ** (1. / kappa)
194
        if np.abs(new_p - p).max() < eps:
195
            break
196
        p = new_p
197
        max_iters -= 1
198
199
    else:
200
        # We have not converged
201
        raise RuntimeError('LCL calculation has not converged.')
202 View Code Duplication
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
203
    return new_p, td
204
205
206
@exporter.export
207
@check_units('[pressure]', '[temperature]', '[temperature]')
208
def lfc(pressure, temperature, dewpt):
209
    r"""Calculate the level of free convection (LFC).
210
211
    This works by finding the first intersection of the ideal parcel path and
212
    the measured parcel temperature.
213
214
    Parameters
215
    ----------
216
    pressure : `pint.Quantity`
217
        The atmospheric pressure
218
    temperature : `pint.Quantity`
219
        The temperature at the levels given by `pressure`
220
    dewpt : `pint.Quantity`
221
        The dew point at the levels given by `pressure`
222
223
    Returns
224
    -------
225
    `pint.Quantity`
226
        The LFC pressure and temperature
227
228
    See Also
229
    --------
230
    parcel_profile
231
232
    """
233
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
234
235
    # The parcel profile and data have the same first data point, so we ignore
236
    # that point to get the real first intersection for the LFC calculation.
237
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:],
238
                              direction='increasing')
239
    if len(x) == 0:
240 View Code Duplication
        return np.nan * pressure.units, np.nan * temperature.units
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
241
    else:
242
        return x[0], y[0]
243
244
245
@exporter.export
246
@check_units('[pressure]', '[temperature]', '[temperature]')
247
def el(pressure, temperature, dewpt):
248
    r"""Calculate the equilibrium level.
249
250
    This works by finding the last intersection of the ideal parcel path and
251
    the measured environmental temperature. If there is one or fewer intersections, there is
252
    no equilibrium level.
253
254
    Parameters
255
    ----------
256
    pressure : `pint.Quantity`
257
        The atmospheric pressure
258
    temperature : `pint.Quantity`
259
        The temperature at the levels given by `pressure`
260
    dewpt : `pint.Quantity`
261
        The dew point at the levels given by `pressure`
262
263
    Returns
264
    -------
265
    `pint.Quantity, pint.Quantity`
266
        The EL pressure and temperature
267
268
    See Also
269
    --------
270
    parcel_profile
271
272
    """
273
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
274
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:])
275
276
    # If there is only one intersection, it's the LFC and we return None.
277
    if len(x) <= 1:
278
        return np.nan * pressure.units, np.nan * temperature.units
279
    else:
280
        return x[-1], y[-1]
281
282
283
@exporter.export
284
@check_units('[pressure]', '[temperature]', '[temperature]')
285
def parcel_profile(pressure, temperature, dewpt):
286
    r"""Calculate the profile a parcel takes through the atmosphere.
287
288
    The parcel starts at `temperature`, and `dewpt`, lifted up
289
    dry adiabatically to the LCL, and then moist adiabatically from there.
290
    `pressure` specifies the pressure levels for the profile.
291
292
    Parameters
293
    ----------
294
    pressure : `pint.Quantity`
295
        The atmospheric pressure level(s) of interest. The first entry should be the starting
296
        point pressure.
297
    temperature : `pint.Quantity`
298
        The starting temperature
299
    dewpt : `pint.Quantity`
300
        The starting dew point
301
302
    Returns
303
    -------
304
    `pint.Quantity`
305
        The parcel temperatures at the specified pressure levels.
306
307
    See Also
308
    --------
309
    lcl, moist_lapse, dry_lapse
310
311
    """
312
    # Find the LCL
313
    l = lcl(pressure[0], temperature, dewpt)[0].to(pressure.units)
314
315
    # Find the dry adiabatic profile, *including* the LCL. We need >= the LCL in case the
316
    # LCL is included in the levels. It's slightly redundant in that case, but simplifies
317
    # the logic for removing it later.
318
    press_lower = concatenate((pressure[pressure >= l], l))
319
    t1 = dry_lapse(press_lower, temperature)
320
321
    # Find moist pseudo-adiabatic profile starting at the LCL
322
    press_upper = concatenate((l, pressure[pressure < l]))
323
    t2 = moist_lapse(press_upper, t1[-1]).to(t1.units)
324
325
    # Return LCL *without* the LCL point
326
    return concatenate((t1[:-1], t2[1:]))
327
328
329
@exporter.export
330
@check_units('[pressure]', '[dimensionless]')
331
def vapor_pressure(pressure, mixing):
332
    r"""Calculate water vapor (partial) pressure.
333
334
    Given total `pressure` and water vapor `mixing` ratio, calculates the
335
    partial pressure of water vapor.
336
337
    Parameters
338
    ----------
339
    pressure : `pint.Quantity`
340
        total atmospheric pressure
341
    mixing : `pint.Quantity`
342
        dimensionless mass mixing ratio
343
344
    Returns
345
    -------
346
    `pint.Quantity`
347
        The ambient water vapor (partial) pressure in the same units as
348
        `pressure`.
349
350
    Notes
351
    -----
352
    This function is a straightforward implementation of the equation given in many places,
353
    such as [Hobbs1977]_ pg.71:
354
355
    .. math:: e = p \frac{r}{r + \epsilon}
356
357
    See Also
358
    --------
359
    saturation_vapor_pressure, dewpoint
360
361
    """
362
    return pressure * mixing / (epsilon + mixing)
363
364
365
@exporter.export
366
@check_units('[temperature]')
367
def saturation_vapor_pressure(temperature):
368
    r"""Calculate the saturation water vapor (partial) pressure.
369
370
    Parameters
371
    ----------
372
    temperature : `pint.Quantity`
373
        The temperature
374
375
    Returns
376
    -------
377
    `pint.Quantity`
378
        The saturation water vapor (partial) pressure
379
380
    See Also
381
    --------
382
    vapor_pressure, dewpoint
383
384
    Notes
385
    -----
386
    Instead of temperature, dewpoint may be used in order to calculate
387
    the actual (ambient) water vapor (partial) pressure.
388
389
    The formula used is that from [Bolton1980]_ for T in degrees Celsius:
390
391
    .. math:: 6.112 e^\frac{17.67T}{T + 243.5}
392
393
    """
394
    # Converted from original in terms of C to use kelvin. Using raw absolute values of C in
395
    # a formula plays havoc with units support.
396
    return sat_pressure_0c * np.exp(17.67 * (temperature - 273.15 * units.kelvin) /
397
                                    (temperature - 29.65 * units.kelvin))
398
399
400
@exporter.export
401
@check_units('[temperature]', '[dimensionless]')
402
def dewpoint_rh(temperature, rh):
403
    r"""Calculate the ambient dewpoint given air temperature and relative humidity.
404
405
    Parameters
406
    ----------
407
    temperature : `pint.Quantity`
408
        Air temperature
409
    rh : `pint.Quantity`
410
        Relative humidity expressed as a ratio in the range [0, 1]
411
412
    Returns
413
    -------
414
    `pint.Quantity`
415
        The dew point temperature
416
417
    See Also
418
    --------
419
    dewpoint, saturation_vapor_pressure
420
421
    """
422
    return dewpoint(rh * saturation_vapor_pressure(temperature))
423
424
425
@exporter.export
426
@check_units('[pressure]')
427
def dewpoint(e):
428
    r"""Calculate the ambient dewpoint given the vapor pressure.
429
430
    Parameters
431
    ----------
432
    e : `pint.Quantity`
433
        Water vapor partial pressure
434
435
    Returns
436
    -------
437
    `pint.Quantity`
438
        Dew point temperature
439
440
    See Also
441
    --------
442
    dewpoint_rh, saturation_vapor_pressure, vapor_pressure
443
444
    Notes
445
    -----
446
    This function inverts the [Bolton1980]_ formula for saturation vapor
447
    pressure to instead calculate the temperature. This yield the following
448
    formula for dewpoint in degrees Celsius:
449
450
    .. math:: T = \frac{243.5 log(e / 6.112)}{17.67 - log(e / 6.112)}
451
452
    """
453
    val = np.log(e / sat_pressure_0c)
454
    return 0. * units.degC + 243.5 * units.delta_degC * val / (17.67 - val)
455
456
457
@exporter.export
458
@check_units('[pressure]', '[pressure]', '[dimensionless]')
459
def mixing_ratio(part_press, tot_press, molecular_weight_ratio=epsilon):
460
    r"""Calculate the mixing ratio of a gas.
461
462
    This calculates mixing ratio given its partial pressure and the total pressure of
463
    the air. There are no required units for the input arrays, other than that
464
    they have the same units.
465
466
    Parameters
467
    ----------
468
    part_press : `pint.Quantity`
469
        Partial pressure of the constituent gas
470
    tot_press : `pint.Quantity`
471
        Total air pressure
472
    molecular_weight_ratio : `pint.Quantity` or float, optional
473
        The ratio of the molecular weight of the constituent gas to that assumed
474
        for air. Defaults to the ratio for water vapor to dry air
475
        (:math:`\epsilon\approx0.622`).
476
477
    Returns
478
    -------
479
    `pint.Quantity`
480
        The (mass) mixing ratio, dimensionless (e.g. Kg/Kg or g/g)
481
482
    Notes
483
    -----
484
    This function is a straightforward implementation of the equation given in many places,
485
    such as [Hobbs1977]_ pg.73:
486
487
    .. math:: r = \epsilon \frac{e}{p - e}
488
489
    See Also
490
    --------
491
    saturation_mixing_ratio, vapor_pressure
492
493
    """
494
    return molecular_weight_ratio * part_press / (tot_press - part_press)
495
496
497
@exporter.export
498
@check_units('[pressure]', '[temperature]')
499
def saturation_mixing_ratio(tot_press, temperature):
500
    r"""Calculate the saturation mixing ratio of water vapor.
501
502
    This calculation is given total pressure and the temperature. The implementation
503
    uses the formula outlined in [Hobbs1977]_ pg.73.
504
505
    Parameters
506
    ----------
507
    tot_press: `pint.Quantity`
508
        Total atmospheric pressure
509
    temperature: `pint.Quantity`
510
        The temperature
511
512
    Returns
513
    -------
514
    `pint.Quantity`
515
        The saturation mixing ratio, dimensionless
516
517
    """
518
    return mixing_ratio(saturation_vapor_pressure(temperature), tot_press)
519
520
521
@exporter.export
522
@check_units('[pressure]', '[temperature]')
523
def equivalent_potential_temperature(pressure, temperature):
524
    r"""Calculate equivalent potential temperature.
525
526
    This calculation must be given an air parcel's pressure and temperature.
527
    The implementation uses the formula outlined in [Hobbs1977]_ pg.78-79.
528
529
    Parameters
530
    ----------
531
    pressure: `pint.Quantity`
532
        Total atmospheric pressure
533
    temperature: `pint.Quantity`
534
        The temperature
535
536
    Returns
537
    -------
538
    `pint.Quantity`
539
        The corresponding equivalent potential temperature of the parcel
540
541
    Notes
542
    -----
543
    .. math:: \Theta_e = \Theta e^\frac{L_v r_s}{C_{pd} T}
544
545
    """
546
    pottemp = potential_temperature(pressure, temperature)
547
    smixr = saturation_mixing_ratio(pressure, temperature)
548
    return pottemp * np.exp(Lv * smixr / (Cp_d * temperature))
549
550
551
@exporter.export
552
@check_units('[temperature]', '[dimensionless]', '[dimensionless]')
553
def virtual_temperature(temperature, mixing, molecular_weight_ratio=epsilon):
554
    r"""Calculate virtual temperature.
555
556
    This calculation must be given an air parcel's temperature and mixing ratio.
557
    The implementation uses the formula outlined in [Hobbs2006]_ pg.80.
558
559
    Parameters
560
    ----------
561
    temperature: `pint.Quantity`
562
        The temperature
563
    mixing : `pint.Quantity`
564
        dimensionless mass mixing ratio
565
    molecular_weight_ratio : `pint.Quantity` or float, optional
566
        The ratio of the molecular weight of the constituent gas to that assumed
567
        for air. Defaults to the ratio for water vapor to dry air
568
        (:math:`\epsilon\approx0.622`).
569
570
    Returns
571
    -------
572
    `pint.Quantity`
573
        The corresponding virtual temperature of the parcel
574
575
    Notes
576
    -----
577
    .. math:: T_v = T \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
578
579
    """
580
    return temperature * ((mixing + molecular_weight_ratio) /
581
                          (molecular_weight_ratio * (1 + mixing)))
582
583
584
@exporter.export
585
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
586
def virtual_potential_temperature(pressure, temperature, mixing,
587
                                  molecular_weight_ratio=epsilon):
588
    r"""Calculate virtual potential temperature.
589
590
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
591
    The implementation uses the formula outlined in [Markowski2010]_ pg.13.
592
593
    Parameters
594
    ----------
595
    pressure: `pint.Quantity`
596
        Total atmospheric pressure
597
    temperature: `pint.Quantity`
598
        The temperature
599
    mixing : `pint.Quantity`
600
        dimensionless mass mixing ratio
601
    molecular_weight_ratio : `pint.Quantity` or float, optional
602
        The ratio of the molecular weight of the constituent gas to that assumed
603
        for air. Defaults to the ratio for water vapor to dry air
604
        (:math:`\epsilon\approx0.622`).
605
606
    Returns
607
    -------
608
    `pint.Quantity`
609
        The corresponding virtual potential temperature of the parcel
610
611
    Notes
612
    -----
613
    .. math:: \Theta_v = \Theta \frac{\text{w} + \epsilon}{\epsilon\,(1 + \text{w})}
614
615
    """
616
    pottemp = potential_temperature(pressure, temperature)
617
    return virtual_temperature(pottemp, mixing, molecular_weight_ratio)
618
619
620
@exporter.export
621
@check_units('[pressure]', '[temperature]', '[dimensionless]', '[dimensionless]')
622
def density(pressure, temperature, mixing, molecular_weight_ratio=epsilon):
623
    r"""Calculate density.
624
625
    This calculation must be given an air parcel's pressure, temperature, and mixing ratio.
626
    The implementation uses the formula outlined in [Hobbs2006]_ pg.67.
627
628
    Parameters
629
    ----------
630
    temperature: `pint.Quantity`
631
        The temperature
632
    pressure: `pint.Quantity`
633
        Total atmospheric pressure
634
    mixing : `pint.Quantity`
635
        dimensionless mass mixing ratio
636
    molecular_weight_ratio : `pint.Quantity` or float, optional
637
        The ratio of the molecular weight of the constituent gas to that assumed
638
        for air. Defaults to the ratio for water vapor to dry air
639
        (:math:`\epsilon\approx0.622`).
640
641
    Returns
642
    -------
643
    `pint.Quantity`
644
        The corresponding density of the parcel
645
646
    Notes
647
    -----
648
    .. math:: \rho = \frac{p}{R_dT_v}
649
650
    """
651
    virttemp = virtual_temperature(temperature, mixing, molecular_weight_ratio)
652
    return (pressure / (Rd * virttemp)).to(units.kilogram / units.meter ** 3)
653
654
655
@exporter.export
656
@check_units('[temperature]', '[temperature]', '[pressure]')
657
def relative_humidity_wet_psychrometric(dry_bulb_temperature, web_bulb_temperature,
658
                                        pressure, **kwargs):
659
    r"""Calculate the relative humidity with wet bulb and dry bulb temperatures.
660
661
    This uses a psychrometric relationship as outlined in [WMO8-2008]_, with
662
    coefficients from [Fan1987]_.
663
664
    Parameters
665
    ----------
666
    dry_bulb_temperature: `pint.Quantity`
667
        Dry bulb temperature
668
    web_bulb_temperature: `pint.Quantity`
669
        Wet bulb temperature
670
    pressure: `pint.Quantity`
671
        Total atmospheric pressure
672
673
    Returns
674
    -------
675
    `pint.Quantity`
676
        Relative humidity
677
678
    Notes
679
    -----
680
    .. math:: RH = 100 \frac{e}{e_s}
681
682
    * :math:`RH` is relative humidity
683
    * :math:`e` is vapor pressure from the wet psychrometric calculation
684
    * :math:`e_s` is the saturation vapor pressure
685
686
    See Also
687
    --------
688
    psychrometric_vapor_pressure_wet, saturation_vapor_pressure
689
690
    """
691
    return (100 * units.percent * psychrometric_vapor_pressure_wet(dry_bulb_temperature,
692
            web_bulb_temperature, pressure, **kwargs) /
693
            saturation_vapor_pressure(dry_bulb_temperature))
694
695
696
@exporter.export
697
@check_units('[temperature]', '[temperature]', '[pressure]')
698
def psychrometric_vapor_pressure_wet(dry_bulb_temperature, wet_bulb_temperature, pressure,
699
                                     psychrometer_coefficient=6.21e-4 / units.kelvin):
700
    r"""Calculate the vapor pressure with wet bulb and dry bulb temperatures.
701
702
    This uses a psychrometric relationship as outlined in [WMO8-2008]_, with
703
    coefficients from [Fan1987]_.
704
705
    Parameters
706
    ----------
707
    dry_bulb_temperature: `pint.Quantity`
708
        Dry bulb temperature
709
    wet_bulb_temperature: `pint.Quantity`
710
        Wet bulb temperature
711
    pressure: `pint.Quantity`
712
        Total atmospheric pressure
713
    psychrometer_coefficient: `pint.Quantity`
714
        Psychrometer coefficient
715
716
    Returns
717
    -------
718
    `pint.Quantity`
719
        Vapor pressure
720
721
    Notes
722
    -----
723
    .. math:: e' = e'_w(T_w) - A p (T - T_w)
724
725
    * :math:`e'` is vapor pressure
726
    * :math:`e'_w(T_w)` is the saturation vapor pressure with respect to water at temperature
727
      :math:`T_w`
728
    * :math:`p` is the pressure of the wet bulb
729
    * :math:`T` is the temperature of the dry bulb
730
    * :math:`T_w` is the temperature of the wet bulb
731
    * :math:`A` is the psychrometer coefficient
732
733
    Psychrometer coefficient depends on the specific instrument being used and the ventilation
734
    of the instrument.
735
736
    See Also
737
    --------
738
    saturation_vapor_pressure
739
740
    """
741
    return (saturation_vapor_pressure(wet_bulb_temperature) - psychrometer_coefficient *
742
            pressure * (dry_bulb_temperature - wet_bulb_temperature).to('kelvin'))
743
744
745
def pressure_at_height_above_pressure(sfc_pressure, height):
746
    """
747
    Calculate the pressure of a certain height above another pressure level.
748
749
    Assumes a standard atmosphere.
750
    """
751
    sfc_height = pressure_to_height(sfc_pressure)
752
    AGL_height = sfc_height + height
753
    return pressure_at_height_above_pressure(AGL_height)
754
755
756
def mixed_layer(p, variable):
757
    depth = p[-1] - p[0]
758
    return (1. / depth.m) * np.trapz(variable, p) * variable.units
759
760
761
def mixed_layer_parcel(p, T, Td, bottom=None, depth=100 * units.hPa, parcel_pressure=p[0]):
762
763
    # If no bottom is specified, use the first pressure value
764
    if not bottom:
765
        bottom_pressure = p[0]
766
767
    # If bottom is specified as a height (AGL), convert to pressure
768
    if bottom.dimensionality == {'[length]': 1.0}:
769
        bottom_pressure = pressure_at_height_above_pressure(p[0], bottom)
770
771
772
    # Calculate the pressure at the top of the mixed layer
773
    if depth.dimensionality == {'[length]': 1.0}:
774
        top_pressure = pressure_at_height_above_pressure(bottom, depth)
775
    else:
776
        top_pressure = p[0] - depth
777
778
    # Clip out the pressure values we have data for in the layer
779
    inds = (p <= bottom_pressure) & (p >= top_pressure)
780
    p_interp = p[inds]
781
782
    # If we don't have the bottom or top requested, append them
783
    if top_pressure not in p_interp:
784
        p_interp = np.sort(np.append(p_interp, top_pressure)) * units.hPa
785
    if bottom_pressure not in p_interp:
786
        p_interp = np.sort(np.append(p_interp, bottom_pressure)) * units.hPa
787
788
    # Interpolate for the possibly missing bottom/top pressure values
789
    sort_args = np.argsort(p)
790
    T = np.interp(p_interp, p[sort_args], T[sort_args]) * units.degC
791
    Td = np.interp(p_interp, p[sort_args], Td[sort_args]) * units.degC
792
    p = p_interp
793
794
    # Determine the mean potential temperature in the layer
795
    theta = mpcalc.potential_temperature(p, T)
796
    theta_mean = mixed_layer(p, theta)
797
798
    # Determine the mean mixing ratio in the layer
799
    mixing_ratio = mpcalc.saturation_mixing_ratio(p, Td)
800
    mixing_ratio_mean = mixed_layer(p, mixing_ratio)
801
802
    # Convert the mixing ratio back to a dewpoint
803
    vapor_pressure_mean = mpcalc.vapor_pressure(parcel_pressure, mixing_ratio_mean)
804
    dewpoint_mean = mpcalc.dewpoint(vapor_pressure_mean)
805
806
    # Convert the potential temperature back to real temperature
807
    temperature_mean = theta_mean / mpcalc.potential_temperature(parcel_pressure,
808
                                                                 1 * units.degK).m
809
    return parcel_pressure, temperature_mean.to('degC'), dewpoint_mean
810