Skip to content

Commit

Permalink
Simplify and fix dewpoint calc
Browse files Browse the repository at this point in the history
Beyond standardizing this calc on WMO8, dewpoint_from_specific_humidity
can be simplified to not require temperature as an argument. Add an
internal implementation to support this and preserve API compatibility.
  • Loading branch information
dcamron committed Oct 25, 2023
1 parent eebba0a commit 380f4e7
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 17 deletions.
79 changes: 64 additions & 15 deletions src/metpy/calc/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -3871,21 +3871,16 @@ def static_stability(pressure, temperature, vertical_dim=0):


@exporter.export
@preprocess_and_wrap(
wrap_like='temperature',
broadcast=('pressure', 'temperature', 'specific_humidity')
)
@check_units('[pressure]', '[temperature]', '[dimensionless]')
def dewpoint_from_specific_humidity(pressure, temperature, specific_humidity):
r"""Calculate the dewpoint from specific humidity, temperature, and pressure.
def dewpoint_from_specific_humidity(*args, **kwargs):
r"""Calculate the dewpoint from specific humidity and pressure.
Parameters
----------
pressure: `pint.Quantity`
Total atmospheric pressure
temperature: `pint.Quantity`
Air temperature
temperature: `pint.Quantity`, optional
Air temperature. Unused in calculation, pending deprecation.
specific_humidity: `pint.Quantity`
Specific humidity of air
Expand All @@ -3899,20 +3894,74 @@ def dewpoint_from_specific_humidity(pressure, temperature, specific_humidity):
--------
>>> from metpy.calc import dewpoint_from_specific_humidity
>>> from metpy.units import units
>>> dewpoint_from_specific_humidity(1000 * units.hPa, 10 * units.degC, 5 * units('g/kg'))
<Quantity(3.73203192, 'degree_Celsius')>
>>> dewpoint_from_specific_humidity(1000 * units.hPa, 5 * units('g/kg'))
<Quantity(3.79314079, 'degree_Celsius')>
.. versionchanged:: 1.6
Made `temperature` arg optional, to be deprecated
.. versionchanged:: 1.0
Changed signature from ``(specific_humidity, temperature, pressure)``
See Also
--------
relative_humidity_from_mixing_ratio, dewpoint_from_relative_humidity
relative_humidity_from_mixing_ratio, dewpoint_from_relative_humidity, dewpoint
Notes
-----
Employs [WMO8]_ eq 4.A.6,
.. math:: e = \frac{w}{\epsilon + w} p
with
.. math:: w = \frac{q}{1-q}
where
* :math:`q` is specific humidity
* :math:`w` is mixing ratio
* :math:`\epsilon` is the molecular weight ratio of vapor to dry air
to calculate vapor partial pressure :math:`e` for dewpoint calculation input. See
:func:`~dewpoint` for additional information.
"""
return dewpoint_from_relative_humidity(temperature,
relative_humidity_from_specific_humidity(
pressure, temperature, specific_humidity))
pressure = kwargs.pop('pressure', None)
specific_humidity = kwargs.pop('specific_humidity', None)

return _dewpoint_from_specific_humidity(
pressure if pressure else args[0],
specific_humidity if specific_humidity else args[-1])


@preprocess_and_wrap(
wrap_like='specific_humidity',
broadcast=('pressure', 'specific_humidity')
)
@check_units(pressure='[pressure]', specific_humidity='[dimensionless]')
def _dewpoint_from_specific_humidity(pressure, specific_humidity):
r"""Calculate the dewpoint from specific humidity and pressure.
See :func:`~dewpoint_from_specific_humidity` for more information. This implementation
is provided internally to preserve backwards compatibility with MetPy<1.6.
Parameters
----------
pressure: `pint.Quantity`
Total atmospheric pressure
specific_humidity: `pint.Quantity`
Specific humidity of air
Returns
-------
`pint.Quantity`
Dew point temperature
"""
w = mixing_ratio_from_specific_humidity(specific_humidity)
e = pressure * w / (mpconsts.nounit.epsilon + w)

return dewpoint(e)


@exporter.export
Expand Down
31 changes: 29 additions & 2 deletions tests/calc/test_thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,8 +198,8 @@ def test_moist_lapse_starting_points(start, direction):
assert_almost_equal(temp, truth, 4)


@pytest.mark.xfail(platform.machine() == 'aarch64',
reason='ValueError is not raised on aarch64')
@pytest.mark.xfail('arm' in platform.machine() or 'aarch' in platform.machine(),
reason='ValueError is not raised on ARM architecture')
@pytest.mark.xfail(sys.platform == 'win32', reason='solve_ivp() does not error on Windows')
@pytest.mark.xfail(packaging.version.parse(scipy.__version__) < packaging.version.parse('1.7'),
reason='solve_ivp() does not error on Scipy < 1.7')
Expand Down Expand Up @@ -1816,6 +1816,33 @@ def test_dewpoint_specific_humidity_old_signature():
dewpoint_from_specific_humidity(q, temperature, p)


def test_dewpoint_specific_humidity_kwargs():
"""Test kw-specified signature for backwards compatibility MetPy>=1.6."""
p = 1013.25 * units.mbar
temperature = 20. * units.degC
q = 0.012 * units.dimensionless
td = dewpoint_from_specific_humidity(
pressure=p, temperature=temperature, specific_humidity=q)
assert_almost_equal(td, 17.036 * units.degC, 3)


def test_dewpoint_specific_humidity_mixed_args_kwargs():
"""Test mixed arg, kwarg handling for backwards compatibility MetPy>=1.6."""
p = 1013.25 * units.mbar
temperature = 20. * units.degC
q = 0.012 * units.dimensionless
td = dewpoint_from_specific_humidity(p, temperature=temperature, specific_humidity=q)
assert_almost_equal(td, 17.036 * units.degC, 3)


def test_dewpoint_specific_humidity_two_args():
"""Test new signature, Temperature unneeded, MetPy>=1.6."""
p = 1013.25 * units.mbar
q = 0.012 * units.dimensionless
td = dewpoint_from_specific_humidity(p, q)
assert_almost_equal(td, 17.036 * units.degC, 3)


def test_lfc_not_below_lcl():
"""Test sounding where LFC appears to be (but isn't) below LCL."""
levels = np.array([1002.5, 1001.7, 1001., 1000.3, 999.7, 999., 998.2, 977.9,
Expand Down

0 comments on commit 380f4e7

Please sign in to comment.