Repository: Unidata/MetPy

Issue:
SWEAT Index
Another severe weather index. From COD: http://weather.cod.edu/sirvatka/si.html

## SWEAT Index

SWEAT = 12Td850 + 20(TT - 49) + 2f850 + f500 + 125(s + 0.2);
where the first term is set to zero if the 850 mb Td (°C) is negative; TT is the Total Totals Index (if TT < 49, the term is set to zero); f is the wind speed in knots; and s = sin (500 mb wind direction - 850 mb wind direction). The last term is set to zero if any of the following is not met:

1) the 850 mb wind is between 130°-250°; 
2) the 500 mb wind is between 210°-310°; 
3) (the 500 mb wind direction - the 850 mb wind direction) is greater than zero; or 4) both the wind speeds are greater than or equal to 15 kts.

SWEAT values +250 indicate a potential for strong convection. 
SWEAT values +300 indicate the threshold for severe thunderstorms. 
SWEAT values +400 indicate the threshold for tornadoes.

Comments:

Patch:
diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst
index f6eeeaf372..88a9d5cdad 100644
--- a/docs/_templates/overrides/metpy.calc.rst
+++ b/docs/_templates/overrides/metpy.calc.rst
@@ -90,6 +90,7 @@ Soundings
       storm_relative_helicity
       supercell_composite
       surface_based_cape_cin
+      sweat_index
       total_totals_index
       vertical_totals
 
diff --git a/setup.cfg b/setup.cfg
index cd334e3677..6eed3787a4 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -45,7 +45,7 @@ install_requires =
     matplotlib>=3.3.0
     numpy>=1.18.0
     pandas>=1.0.0
-    pint>=0.10.1
+    pint>=0.13
     pooch>=1.2.0
     pyproj>=2.5.0
     scipy>=1.4.0
diff --git a/src/metpy/calc/thermo.py b/src/metpy/calc/thermo.py
index 1eb1424bb7..44bb3aa5d8 100644
--- a/src/metpy/calc/thermo.py
+++ b/src/metpy/calc/thermo.py
@@ -4352,3 +4352,87 @@ def cross_totals(pressure, temperature, dewpoint):
 
     # Calculate vertical totals.
     return td850 - t500
+
+
+@exporter.export
+@preprocess_and_wrap()
+@check_units('[pressure]', '[temperature]', '[temperature]', '[speed]')
+def sweat_index(pressure, temperature, dewpoint, speed, direction):
+    """Calculate SWEAT Index.
+
+    SWEAT Index derived from [Miller1972]_:
+
+    .. math:: SWEAT = 12Td_{850} + 20(TT - 49) + 2f_{850} + f_{500} + 125(S + 0.2)
+
+    where:
+
+    * :math:`Td_{850}` is the dewpoint at 850 hPa; the first term is set to zero
+      if :math:`Td_{850}` is negative.
+    * :math:`TT` is the total totals index; the second term is set to zero
+      if :math:`TT` is less than 49
+    * :math:`f_{850}` is the wind speed at 850 hPa
+    * :math:`f_{500}` is the wind speed at 500 hPa
+    * :math:`S` is the shear term: :math:`sin{(dd_{850} - dd_{500})}`, where
+      :math:`dd_{850}` and :math:`dd_{500}` are the wind directions at 850 hPa and 500 hPa,
+      respectively. It is set to zero if any of the following conditions are not met:
+
+    1. :math:`dd_{850}` is between 130 - 250 degrees
+    2. :math:`dd_{500}` is between 210 - 310 degrees
+    3. :math:`dd_{500} - dd_{850} > 0`
+    4. both the wind speeds are greater than or equal to 15 kts
+
+    Calculation of the SWEAT Index consists of a low-level moisture, instability,
+    and the vertical wind shear (both speed and direction). This index aim to
+    determine the likeliness of severe weather and tornadoes.
+
+    Parameters
+    ----------
+    pressure : `pint.Quantity`
+        Pressure level(s), in order from highest to lowest pressure
+
+    temperature : `pint.Quantity`
+        Temperature corresponding to pressure
+
+    dewpoint : `pint.Quantity`
+        Dewpoint temperature corresponding to pressure
+
+    speed : `pint.Quantity`
+        Wind speed corresponding to pressure
+
+    direction : `pint.Quantity`
+        Wind direction corresponding to pressure
+
+    Returns
+    -------
+    `pint.Quantity`
+        SWEAT Index
+
+    """
+    # Find dewpoint at 850 hPa.
+    td850 = interpolate_1d(units.Quantity(850, 'hPa'), pressure, dewpoint)
+
+    # Find total totals index.
+    tt = total_totals_index(pressure, temperature, dewpoint)
+
+    # Find wind speed and direction at 850 and 500 hPa
+    (f850, f500), (dd850, dd500) = interpolate_1d(units.Quantity([850, 500],
+                                                  'hPa'), pressure, speed,
+                                                  direction)
+
+    # First term is set to zero if Td850 is negative
+    first_term = 12 * np.clip(td850.m_as('degC'), 0, None)
+
+    # Second term is set to zero if TT is less than 49
+    second_term = 20 * np.clip(tt.m_as('degC') - 49, 0, None)
+
+    # Shear term is set to zero if any of four conditions are not met
+    required = ((units.Quantity(130, 'deg') <= dd850) & (dd850 <= units.Quantity(250, 'deg'))
+                & (units.Quantity(210, 'deg') <= dd500) & (dd500 <= units.Quantity(310, 'deg'))
+                & (dd500 - dd850 > 0)
+                & (f850 >= units.Quantity(15, 'knots'))
+                & (f500 >= units.Quantity(15, 'knots')))
+    shear_term = np.atleast_1d(125 * (np.sin(dd500 - dd850) + 0.2))
+    shear_term[~required] = 0
+
+    # Calculate sweat index.
+    return first_term + second_term + (2 * f850.m) + f500.m + shear_term


Test patch:
diff --git a/tests/calc/test_thermo.py b/tests/calc/test_thermo.py
index cc8e5827bf..1766b91bb2 100644
--- a/tests/calc/test_thermo.py
+++ b/tests/calc/test_thermo.py
@@ -27,7 +27,7 @@ from metpy.calc import (brunt_vaisala_frequency, brunt_vaisala_frequency_squared
                         saturation_equivalent_potential_temperature, saturation_mixing_ratio,
                         saturation_vapor_pressure, showalter_index,
                         specific_humidity_from_dewpoint, specific_humidity_from_mixing_ratio,
-                        static_stability, surface_based_cape_cin,
+                        static_stability, surface_based_cape_cin, sweat_index,
                         temperature_from_potential_temperature, thickness_hydrostatic,
                         thickness_hydrostatic_from_relative_humidity, total_totals_index,
                         vapor_pressure, vertical_totals, vertical_velocity,
@@ -2157,3 +2157,40 @@ def test_parcel_profile_with_lcl_as_dataset_duplicates():
     profile = parcel_profile_with_lcl_as_dataset(pressure, temperature, dewpoint)
 
     xr.testing.assert_allclose(profile, truth, atol=1e-5)
+
+
+def test_sweat_index():
+    """Test the SWEAT Index calculation."""
+    pressure = np.array([1008., 1000., 947., 925., 921., 896., 891., 889., 866.,
+                         858., 850., 835., 820., 803., 733., 730., 700., 645.,
+                         579., 500., 494., 466., 455., 441., 433., 410., 409.,
+                         402., 400., 390., 388., 384., 381., 349., 330., 320.,
+                         306., 300., 278., 273., 250., 243., 208., 200., 196.,
+                         190., 179., 159., 151., 150., 139.]) * units.hPa
+    temperature = np.array([27.4, 26.4, 22.9, 21.4, 21.2, 20.7, 20.6, 21.2, 19.4,
+                            19.1, 18.8, 17.8, 17.4, 16.3, 11.4, 11.2, 10.2, 6.1,
+                            0.6, -4.9, -5.5, -8.5, -9.9, -11.7, -12.3, -13.7, -13.8,
+                            -14.9, -14.9, -16.1, -16.1, -16.9, -17.3, -21.7, -24.5, -26.1,
+                            -28.3, -29.5, -33.1, -34.2, -39.3, -41., -50.2, -52.5, -53.5,
+                            -55.2, -58.6, -65.2, -68.1, -68.5, -72.5]) * units.degC
+    dewpoint = np.array([24.9, 24.6, 22., 20.9, 20.7, 14.8, 13.6, 12.2, 16.8,
+                         16.6, 16.5, 15.9, 13.6, 13.2, 11.3, 11.2, 8.6, 4.5,
+                         -0.8, -8.1, -9.5, -12.7, -12.7, -12.8, -13.1, -24.7, -24.4,
+                         -21.9, -24.9, -36.1, -31.1, -26.9, -27.4, -33., -36.5, -47.1,
+                         -31.4, -33.5, -40.1, -40.8, -44.1, -45.6, -54., -56.1, -56.9,
+                         -58.6, -61.9, -68.4, -71.2, -71.6, -77.2]) * units.degC
+    speed = np.array([0., 3., 10., 12., 12., 14., 14., 14., 12.,
+                      12., 12., 12., 11., 11., 12., 12., 10., 10.,
+                      8., 5., 4., 1., 0., 3., 5., 10., 10.,
+                      11., 11., 13., 14., 14., 15., 23., 23., 24.,
+                      24., 24., 26., 27., 28., 30., 25., 24., 26.,
+                      28., 33., 29., 32., 26., 26.]) * units.knot
+    direction = np.array([0., 170., 200., 205., 204., 200., 197., 195., 180.,
+                          175., 175., 178., 181., 185., 160., 160., 165., 165.,
+                          203., 255., 268., 333., 0., 25., 40., 83., 85.,
+                          89., 90., 100., 103., 107., 110., 90., 88., 87.,
+                          86., 85., 85., 85., 60., 55., 60., 50., 46.,
+                          40., 45., 35., 50., 50., 50.]) * units.degree
+
+    sweat = sweat_index(pressure, temperature, dewpoint, speed, direction)
+    assert_almost_equal(sweat, 227., 2)