Completed
Pull Request — master (#322)
by
unknown
01:21
created

cape()   B

Complexity

Conditions 5

Size

Total Lines 65

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
cc 5
c 4
b 0
f 0
dl 0
loc 65
rs 8.2662

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 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, concatenate, units
15
16
exporter = Exporter(globals())
17
18
sat_pressure_0c = 6.112 * units.millibar
19
20
21
@exporter.export
22
def potential_temperature(pressure, temperature):
23
    r"""Calculate the potential temperature.
24
25
    Uses the Poisson equation to calculation the potential temperature
26
    given `pressure` and `temperature`.
27
28
    Parameters
29
    ----------
30
    pressure : `pint.Quantity`
31
        The total atmospheric pressure
32
    temperature : `pint.Quantity`
33
        The temperature
34
35
    Returns
36
    -------
37
    `pint.Quantity`
38
        The potential temperature corresponding to the the temperature and
39
        pressure.
40
41
    See Also
42
    --------
43
    dry_lapse
44
45
    Notes
46
    -----
47
    Formula:
48
49
    .. math:: \Theta = T (P_0 / P)^\kappa
50
51
    Examples
52
    --------
53
    >>> from metpy.units import units
54
    >>> metpy.calc.potential_temperature(800. * units.mbar, 273. * units.kelvin)
55
    290.9814150577374
56
    """
57
    return temperature * (P0 / pressure).to('dimensionless')**kappa
58
59
60
@exporter.export
61
def dry_lapse(pressure, temperature):
62
    r"""Calculate the temperature at a level assuming only dry processes.
63
64
    This function lifts a parcel starting at `temperature`, conserving
65
    potential temperature. The starting pressure should be the first item in
66
    the `pressure` array.
67
68
    Parameters
69
    ----------
70
    pressure : `pint.Quantity`
71
        The atmospheric pressure level(s) of interest
72
    temperature : `pint.Quantity`
73
        The starting temperature
74
75
    Returns
76
    -------
77
    `pint.Quantity`
78
       The resulting parcel temperature at levels given by `pressure`
79
80
    See Also
81
    --------
82
    moist_lapse : Calculate parcel temperature assuming liquid saturation
83
                  processes
84
    parcel_profile : Calculate complete parcel profile
85
    potential_temperature
86
    """
87
    return temperature * (pressure / pressure[0])**kappa
88
89
90
@exporter.export
91
def moist_lapse(pressure, temperature):
92
    r"""Calculate the temperature at a level assuming liquid saturation processes.
93
94
    This function lifts a parcel starting at `temperature`. The starting
95
    pressure should be the first item in the `pressure` array. Essentially,
96
    this function is calculating moist pseudo-adiabats.
97
98
    Parameters
99
    ----------
100
    pressure : `pint.Quantity`
101
        The atmospheric pressure level(s) of interest
102
    temperature : `pint.Quantity`
103
        The starting temperature
104
105
    Returns
106
    -------
107
    `pint.Quantity`
108
       The temperature corresponding to the the starting temperature and
109
       pressure levels.
110
111
    See Also
112
    --------
113
    dry_lapse : Calculate parcel temperature assuming dry adiabatic processes
114
    parcel_profile : Calculate complete parcel profile
115
116
    Notes
117
    -----
118
    This function is implemented by integrating the following differential
119
    equation:
120
121
    .. math:: \frac{dT}{dP} = \frac{1}{P} \frac{R_d T + L_v r_s}
122
                                {C_{pd} + \frac{L_v^2 r_s \epsilon}{R_d T^2}}
123
124
    This equation comes from [1]_.
125
126
    References
127
    ----------
128
    .. [1] Bakhshaii, A. and R. Stull, 2013: Saturated Pseudoadiabats--A
129
           Noniterative Approximation. J. Appl. Meteor. Clim., 52, 5-15.
130
    """
131
    def dt(t, p):
132
        t = units.Quantity(t, temperature.units)
133
        p = units.Quantity(p, pressure.units)
134
        rs = saturation_mixing_ratio(p, t)
135
        frac = ((Rd * t + Lv * rs) /
136
                (Cp_d + (Lv * Lv * rs * epsilon / (Rd * t * t)))).to('kelvin')
137
        return frac / p
138
    return units.Quantity(si.odeint(dt, atleast_1d(temperature).squeeze(),
139
                                    pressure.squeeze()).T.squeeze(), temperature.units)
140
141
142
@exporter.export
143
def lcl(pressure, temperature, dewpt, max_iters=50, eps=1e-2):
144
    r"""Calculate the lifted condensation level (LCL) using from the starting point.
145
146
    The starting state for the parcel is defined by `temperature`, `dewpt`,
147
    and `pressure`.
148
149
    Parameters
150
    ----------
151
    pressure : `pint.Quantity`
152
        The starting atmospheric pressure
153
    temperature : `pint.Quantity`
154
        The starting temperature
155
    dewpt : `pint.Quantity`
156
        The starting dew point
157
158
    Returns
159
    -------
160
    `(pint.Quantity, pint.Quantity)`
161
        The LCL pressure and temperature
162
163
    Other Parameters
164
    ----------------
165
    max_iters : int, optional
166
        The maximum number of iterations to use in calculation, defaults to 50.
167
    eps : float, optional
168
        The desired absolute error in the calculated value, defaults to 1e-2.
169
170
    See Also
171
    --------
172
    parcel_profile
173
174
    Notes
175
    -----
176
    This function is implemented using an iterative approach to solve for the
177
    LCL. The basic algorithm is:
178
179
    1. Find the dew point from the LCL pressure and starting mixing ratio
180
    2. Find the LCL pressure from the starting temperature and dewpoint
181
    3. Iterate until convergence
182
183
    The function is guaranteed to finish by virtue of the `max_iters` counter.
184
    """
185
    w = mixing_ratio(saturation_vapor_pressure(dewpt), pressure)
186
    new_p = p = pressure
187
    eps = units.Quantity(eps, p.units)
188
    while max_iters:
189
        td = dewpoint(vapor_pressure(p, w))
190
        new_p = pressure * (td / temperature) ** (1. / kappa)
191
        if np.abs(new_p - p).max() < eps:
192
            break
193
        p = new_p
194
        max_iters -= 1
195
196
    return new_p, td
197
198
199
@exporter.export
200
def lfc(pressure, temperature, dewpt):
201
    r"""Calculate the level of free convection (LFC).
202
203
    This works by finding the first intersection of the ideal parcel path and
204
    the measured parcel temperature.
205
206
    Parameters
207
    ----------
208
    pressure : `pint.Quantity`
209
        The atmospheric pressure
210
    temperature : `pint.Quantity`
211
        The temperature at the levels given by `pressure`
212
    dewpt : `pint.Quantity`
213
        The dew point at the levels given by `pressure`
214
215
    Returns
216
    -------
217
    `pint.Quantity`
218
        The LFC
219
220
    See Also
221
    --------
222
    parcel_profile
223
    """
224
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
225
226
    # The parcel profile and data have the same first data point, so we ignore
227
    # that point to get the real first intersection for the LFC calculation.
228
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:])
229
    if len(x) == 0:
230
        return None, None
231
    else:
232
        return x[0], y[0]
233
234
235
@exporter.export
236
def el(pressure, temperature, dewpt):
237
    r"""Calculate the equilibrium level (EL).
238
239
    This works by finding the last intersection of the ideal parcel path and
240
    the measured parcel temperature. If there is one or fewer intersections, there is
241
    no equilibrium level.
242
243
    Parameters
244
    ----------
245
    pressure : `pint.Quantity`
246
        The atmospheric pressure
247
    temperature : `pint.Quantity`
248
        The temperature at the levels given by `pressure`
249
    dewpt : `pint.Quantity`
250
        The dew point at the levels given by `pressure`
251
252
    Returns
253
    -------
254
    `pint.Quantity, pint.Quantity`
255
        The EL pressure and temperature
256
257
    See Also
258
    --------
259
    parcel_profile
260
    """
261
    ideal_profile = parcel_profile(pressure, temperature[0], dewpt[0]).to('degC')
262
    x, y = find_intersections(pressure[1:], ideal_profile[1:], temperature[1:])
263
264
    # If there is only one intersection, it's the LFC and we return None.
265
    if len(x) <= 1:
266
        return None, None
267
268
    else:
269
        return x[-1], y[-1]
270
271
272
@exporter.export
273
def parcel_profile(pressure, temperature, dewpt):
274
    r"""Calculate the profile a parcel takes through the atmosphere.
275
276
    The parcel starts at `temperature`, and `dewpt`, lifted up
277
    dry adiabatically to the LCL, and then moist adiabatically from there.
278
    `pressure` specifies the pressure levels for the profile.
279
280
    Parameters
281
    ----------
282
    pressure : `pint.Quantity`
283
        The atmospheric pressure level(s) of interest. The first entry should be the starting
284
        point pressure.
285
    temperature : `pint.Quantity`
286
        The starting temperature
287
    dewpt : `pint.Quantity`
288
        The starting dew point
289
290
    Returns
291
    -------
292
    `pint.Quantity`
293
        The parcel temperatures at the specified pressure levels.
294
295
    See Also
296
    --------
297
    lcl, moist_lapse, dry_lapse
298
    """
299
    # Find the LCL
300
    l = lcl(pressure[0], temperature, dewpt)[0].to(pressure.units)
301
302
    # Find the dry adiabatic profile, *including* the LCL. We need >= the LCL in case the
303
    # LCL is included in the levels. It's slightly redundant in that case, but simplifies
304
    # the logic for removing it later.
305
    press_lower = concatenate((pressure[pressure >= l], l))
306
    t1 = dry_lapse(press_lower, temperature)
307
308
    # Find moist pseudo-adiabatic profile starting at the LCL
309
    press_upper = concatenate((l, pressure[pressure < l]))
310
    t2 = moist_lapse(press_upper, t1[-1]).to(t1.units)
311
312
    # Return LCL *without* the LCL point
313
    return concatenate((t1[:-1], t2[1:]))
314
315
316
@exporter.export
317
def virtual_temperature(pressure, temperature, dewpt):
318
    r"""Calculate virtual temperature.
319
320
    Description
321
322
    Parameters
323
    ----------
324
    pressure : `pint.Quantity`
325
        The atmospheric pressure level(s) of interest. The first entry should be the starting
326
        point pressure.
327
    temperature : `pint.Quantity`
328
        The starting temperature
329
    dewpt : `pint.Quantity`
330
        The starting dew point
331
332
    Returns
333
    -------
334
    `pint.Quantity`
335
        The virtual temperature.
336
337
    See Also
338
    --------
339
    mixing_ratio, saturation_vapor_pressure
340
    """
341
    Rv = mixing_ratio(saturation_vapor_pressure(dewpt), pressure)
342
    return temperature * (1 + Rv / epsilon) / (1 + Rv)
343
344
def _find_append_zero_crossings(x, y):
345
    r"""
346
    Find and interpolate zero crossings
347
348
    Estimate the zero crossings of an x,y series and add estimated crossings to series,
349
    returning a sorted array with no duplicate values.
350
351
    Parameters
352
    ----------
353
    x : `pint.Quantity`
354
        x values of data
355
    y : `pint.Quantity`
356
        y values of data
357
358
    Returns
359
    -------
360
    x : `pint.Quantity`
361
        x values of data
362
    y : `pint.Quantity`
363
        y values of data
364
365
    """
366
    # Find and append crossings to the data
367
    crossings = find_intersections(x[1:], y[1:], np.zeros_like(y[1:]))
368
    x = concatenate((x, crossings[0]))
369
    y = concatenate((y, crossings[1]))
370
371
    # Resort so that data are in order
372
    sort_idx = np.argsort(x)
373
    x = x[sort_idx]
374
    y = y[sort_idx]
375
376
    # Remove duplicate data points if there are any
377
    keep_idx = np.ediff1d(x, to_end=[1]) > 0
378
    x = x[keep_idx]
379
    y = y[keep_idx]
380
    return x, y
381
382
@exporter.export
383
def cape(pressure, temperature, dewpt, parcel_temperature=None, use_virtual_temperature=False):
384
    r"""Calculate convective available potential energy (CAPE).
385
386
        Description
387
388
        Parameters
389
        ----------
390
        pressure : `pint.Quantity`
391
            The atmospheric pressure level(s) of interest. The first entry should be the starting
392
            point pressure.
393
        temperature : `pint.Quantity`
394
            The starting temperature
395
        dewpt : `pint.Quantity`
396
            The starting dew point
397
        parcel_temperature : `pint.Quantity`
398
            The temperature of the parcel
399
400
        Returns
401
        -------
402
        `pint.Quantity`
403
            Convective available potential energy (CAPE).
404
405
        See Also
406
        --------
407
        virtual_temperature, find_intersections, lfc, el
408
        """
409
    # Calculate limits of integration
410
    # If there is no LFC, no need to proceed. No EL and we use the top reading of the sounding.
411
    lfc_pressure = lfc(pressure, temperature, dewpt)[0]
412
    el_pressure = el(pressure, temperature, dewpt)[0]
413
414
    if lfc_pressure is None:
415
        return 0 * units('J/kg')
416
    else:
417
        lfc_pressure = lfc_pressure.magnitude
418
419
    if el_pressure is None:
420
        el_pressure = pressure[-1].magnitude
421
    else:
422
        el_pressure = el_pressure.magnitude
423
424
    # If no parcel path is specified, calculated the surface based parcel path
425
    if parcel_temperature is None:
426
        parcel_temperature = parcel_profile(pressure, temperature[0], dewpt[0])
427
428
    # Calculate virtual temperature if requested
429
    if use_virtual_temperature:
430
        temperature = virtual_temperature(pressure, temperature, dewpt)
431
        parcel_temperature = virtual_temperature(pressure, parcel_temperature, dewpt)
432
433
    y = (parcel_temperature - temperature).to(units.degK)
434
435
    x,y = _find_append_zero_crossings(np.copy(pressure),y)
436
437
    # Clip out negative area (temperature parcel < temperature environment)
438
    y[y <= 0 * units.degK] = 0 * units.degK
439
440
    # Only use data between the LFC and EL for calculation
441
442
    p_mask = (x <= lfc_pressure) & (x >= el_pressure)
443
    x = x[p_mask]
444
    y = y[p_mask]
445
446
    return (Rd * (np.trapz(y, np.log(x)) * units.degK)).to(units('J/kg'))
447
448
449
@exporter.export
450
def cin(pressure, temperature, dewpt, parcel_temperature=None, use_virtual_temperature=False):
451
    r"""Calculate convective inhibition (CIN).
452
453
        Description
454
455
        Parameters
456
        ----------
457
        pressure : `pint.Quantity`
458
            The atmospheric pressure level(s) of interest. The first entry should be the starting
459
            point pressure.
460
        temperature : `pint.Quantity`
461
            The starting temperature
462
        dewpt : `pint.Quantity`
463
            The starting dew point
464
        parcel_temperature : `pint.Quantity`
465
            The temperature of the parcel
466
467
        Returns
468
        -------
469
        `pint.Quantity`
470
            Convective inhibition (CIN).
471
472
        See Also
473
        --------
474
        virtual_temperature, find_intersections, lfc
475
        """
476
    # Calculate limits of integration
477
    # If there is no LFC, no need to proceed.
478
    lfc_pressure = lfc(pressure, temperature, dewpt)[0]
479
    if lfc_pressure is None:
480
        return 0 * units('J/kg')
481
    else:
482
        lfc_pressure = lfc_pressure.magnitude
483
484
    # If no parcel path is specified, calculated the surface based parcel path
485
    if parcel_temperature is None:
486
        parcel_temperature = parcel_profile(pressure, temperature[0], dewpt[0])
487
488
    # Calculate virtual temperature if requested
489
    if use_virtual_temperature:
490
        temperature = virtual_temperature(pressure, temperature, dewpt)
491
        parcel_temperature = virtual_temperature(pressure, parcel_temperature, dewpt)
492
493
    y = (parcel_temperature - temperature).to(units.degK)
494
495
    x,y = _find_append_zero_crossings(np.copy(pressure),y)
496
497
    # Clip out positive area (temperature parcel > temperature environment)
498
    y[y >= 0 * units.degK] = 0 * units.degK
499
500
    # Only use data between the surface and the LFC for calculation
501
502
    p_mask = (x >= lfc_pressure)
503
    x = x[p_mask]
504
    y = y[p_mask]
505
506
    return (Rd * (np.trapz(y, np.log(x)) * units.degK)).to(units('J/kg'))
507
508
509
@exporter.export
510
def vapor_pressure(pressure, mixing):
511
    r"""Calculate water vapor (partial) pressure.
512
513
    Given total `pressure` and water vapor `mixing` ratio, calculates the
514
    partial pressure of water vapor.
515
516
    Parameters
517
    ----------
518
    pressure : `pint.Quantity`
519
        total atmospheric pressure
520
    mixing : `pint.Quantity`
521
        dimensionless mass mixing ratio
522
523
    Returns
524
    -------
525
    `pint.Quantity`
526
        The ambient water vapor (partial) pressure in the same units as
527
        `pressure`.
528
529
    Notes
530
    -----
531
    This function is a straightforward implementation of the equation given in many places,
532
    such as [2]_:
533
534
    .. math:: e = p \frac{r}{r + \epsilon}
535
536
    References
537
    ----------
538
    .. [2] Hobbs, Peter V. and Wallace, John M., 1977: Atmospheric Science, an Introductory
539
           Survey. 71.
540
541
    See Also
542
    --------
543
    saturation_vapor_pressure, dewpoint
544
    """
545
    return pressure * mixing / (epsilon + mixing)
546
547
548
@exporter.export
549
def saturation_vapor_pressure(temperature):
550
    r"""Calculate the saturation water vapor (partial) pressure.
551
552
    Parameters
553
    ----------
554
    temperature : `pint.Quantity`
555
        The temperature
556
557
    Returns
558
    -------
559
    `pint.Quantity`
560
        The saturation water vapor (partial) pressure
561
562
    See Also
563
    --------
564
    vapor_pressure, dewpoint
565
566
    Notes
567
    -----
568
    Instead of temperature, dewpoint may be used in order to calculate
569
    the actual (ambient) water vapor (partial) pressure.
570
571
    The formula used is that from Bolton 1980 [3]_ for T in degrees Celsius:
572
573
    .. math:: 6.112 e^\frac{17.67T}{T + 243.5}
574
575
    References
576
    ----------
577
    .. [3] Bolton, D., 1980: The Computation of Equivalent Potential
578
           Temperature. Mon. Wea. Rev., 108, 1046-1053.
579
    """
580
    # Converted from original in terms of C to use kelvin. Using raw absolute values of C in
581
    # a formula plays havoc with units support.
582
    return sat_pressure_0c * np.exp(17.67 * (temperature - 273.15 * units.kelvin) /
583
                                    (temperature - 29.65 * units.kelvin))
584
585
586
@exporter.export
587
def dewpoint_rh(temperature, rh):
588
    r"""Calculate the ambient dewpoint given air temperature and relative humidity.
589
590
    Parameters
591
    ----------
592
    temperature : `pint.Quantity`
593
        Air temperature
594
    rh : `pint.Quantity`
595
        Relative humidity expressed as a ratio in the range [0, 1]
596
597
    Returns
598
    -------
599
    `pint.Quantity`
600
        The dew point temperature
601
602
    See Also
603
    --------
604
    dewpoint, saturation_vapor_pressure
605
    """
606
    return dewpoint(rh * saturation_vapor_pressure(temperature))
607
608
609
@exporter.export
610
def dewpoint(e):
611
    r"""Calculate the ambient dewpoint given the vapor pressure.
612
613
    Parameters
614
    ----------
615
    e : `pint.Quantity`
616
        Water vapor partial pressure
617
618
    Returns
619
    -------
620
    `pint.Quantity`
621
        Dew point temperature
622
623
    See Also
624
    --------
625
    dewpoint_rh, saturation_vapor_pressure, vapor_pressure
626
627
    Notes
628
    -----
629
    This function inverts the Bolton 1980 [4]_ formula for saturation vapor
630
    pressure to instead calculate the temperature. This yield the following
631
    formula for dewpoint in degrees Celsius:
632
633
    .. math:: T = \frac{243.5 log(e / 6.112)}{17.67 - log(e / 6.112)}
634
635
    References
636
    ----------
637
    .. [4] Bolton, D., 1980: The Computation of Equivalent Potential
638
           Temperature. Mon. Wea. Rev., 108, 1046-1053.
639
    """
640
    val = np.log(e / sat_pressure_0c)
641
    return 0. * units.degC + 243.5 * units.delta_degC * val / (17.67 - val)
642
643
644
@exporter.export
645
def mixing_ratio(part_press, tot_press, molecular_weight_ratio=epsilon):
646
    r"""Calculate the mixing ratio of a gas.
647
648
    This calculates mixing ratio given its partial pressure and the total pressure of
649
    the air. There are no required units for the input arrays, other than that
650
    they have the same units.
651
652
    Parameters
653
    ----------
654
    part_press : `pint.Quantity`
655
        Partial pressure of the constituent gas
656
    tot_press : `pint.Quantity`
657
        Total air pressure
658
    molecular_weight_ratio : `pint.Quantity` or float, optional
659
        The ratio of the molecular weight of the constituent gas to that assumed
660
        for air. Defaults to the ratio for water vapor to dry air
661
        (:math:`\epsilon\approx0.622`).
662
663
    Returns
664
    -------
665
    `pint.Quantity`
666
        The (mass) mixing ratio, dimensionless (e.g. Kg/Kg or g/g)
667
668
    Notes
669
    -----
670
    This function is a straightforward implementation of the equation given in many places,
671
    such as [5]_:
672
673
    .. math:: r = \epsilon \frac{e}{p - e}
674
675
    References
676
    ----------
677
    .. [5] Hobbs, Peter V. and Wallace, John M., 1977: Atmospheric Science, an Introductory
678
           Survey. 73.
679
680
    See Also
681
    --------
682
    saturation_mixing_ratio, vapor_pressure
683
    """
684
    return (molecular_weight_ratio * part_press / (tot_press - part_press)).to("dimensionless")
685
686
687
@exporter.export
688
def saturation_mixing_ratio(tot_press, temperature):
689
    r"""Calculate the saturation mixing ratio of water vapor.
690
691
    This calculation is given total pressure and the temperature. The implementation
692
    uses the formula outlined in [6]_.
693
694
    Parameters
695
    ----------
696
    tot_press: `pint.Quantity`
697
        Total atmospheric pressure
698
    temperature: `pint.Quantity`
699
        The temperature
700
701
    Returns
702
    -------
703
    `pint.Quantity`
704
        The saturation mixing ratio, dimensionless
705
706
    References
707
    ----------
708
    .. [6] Hobbs, Peter V. and Wallace, John M., 1977: Atmospheric Science, an Introductory
709
           Survey. 73.
710
    """
711
    return mixing_ratio(saturation_vapor_pressure(temperature), tot_press)
712
713
714
@exporter.export
715
def equivalent_potential_temperature(pressure, temperature):
716
    r"""Calculate equivalent potential temperature.
717
718
    This calculation must be given an air parcel's pressure and temperature.
719
    The implementation uses the formula outlined in [7]_.
720
721
    Parameters
722
    ----------
723
    pressure: `pint.Quantity`
724
        Total atmospheric pressure
725
    temperature: `pint.Quantity`
726
        The temperature
727
728
    Returns
729
    -------
730
    `pint.Quantity`
731
        The corresponding equivalent potential temperature of the parcel
732
733
    Notes
734
    -----
735
    .. math:: \Theta_e = \Theta e^\frac{L_v r_s}{C_{pd} T}
736
737
    References
738
    ----------
739
    .. [7] Hobbs, Peter V. and Wallace, John M., 1977: Atmospheric Science, an Introductory
740
           Survey. 78-79.
741
    """
742
    pottemp = potential_temperature(pressure, temperature)
743
    smixr = saturation_mixing_ratio(pressure, temperature)
744
    return pottemp * np.exp(Lv * smixr / (Cp_d * temperature))
745