Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standardize RH calcs on WMO8 #3242

Merged
merged 10 commits into from
Nov 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ line-length = 95
exclude = ["docs", "build", "src/metpy/io/_metar_parser/metar_parser.py"]
select = ["A", "B", "C", "D", "E", "E226", "F", "G", "I", "N", "Q", "R", "S", "T", "U", "W"]
ignore = ["F405", "I001", "RET504", "RET505", "RET506", "RET507", "RUF100"]
preview = true
explicit-preview-rules = true

[tool.ruff.per-file-ignores]
"ci/filter_links.py" = ["E731", "T201", "S603", "S607"]
Expand Down
150 changes: 114 additions & 36 deletions src/metpy/calc/thermo.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Distributed under the terms of the BSD 3-Clause License.
# SPDX-License-Identifier: BSD-3-Clause
"""Contains a collection of thermodynamic calculations."""
from inspect import Parameter, Signature, signature

import numpy as np
import scipy.integrate as si
import scipy.optimize as so
Expand Down Expand Up @@ -55,15 +57,15 @@ def relative_humidity_from_dewpoint(temperature, dewpoint):

Notes
-----
.. math:: rh = \frac{e(T_d)}{e_s(T)}
.. math:: RH = \frac{e(T_d)}{e_s(T)}

.. versionchanged:: 1.0
Renamed ``dewpt`` parameter to ``dewpoint``

"""
e = saturation_vapor_pressure(dewpoint)
e_s = saturation_vapor_pressure(temperature)
return (e / e_s)
return e / e_s


@exporter.export
Expand Down Expand Up @@ -2026,28 +2028,33 @@ def mixing_ratio_from_relative_humidity(pressure, temperature, relative_humidity
>>> T = 28.1 * units.degC
>>> rh = .65
>>> mixing_ratio_from_relative_humidity(p, T, rh).to('g/kg')
<Quantity(15.9828362, 'gram / kilogram')>
<Quantity(15.7646969, 'gram / kilogram')>

See Also
--------
relative_humidity_from_mixing_ratio, saturation_mixing_ratio

Notes
-----
Formula adapted from [Hobbs1977]_ pg. 74.
Employs [WMO8]_ eq. 4.A.16 as derived from WMO relative humidity definition based on
vapor partial pressures (eq. 4.A.15).

.. math:: w = (rh)(w_s)
.. math:: RH = \frac{w}{\epsilon + w} \frac{\epsilon + w_s}{w_s}
.. math:: \therefore w = \frac{\epsilon * w_s * RH}{\epsilon + w_s (1 - RH)}

* :math:`w` is mixing ratio
* :math:`rh` is relative humidity as a unitless ratio
* :math:`w_s` is the saturation mixing ratio
* :math:`\epsilon` is the molecular weight ratio of vapor to dry air
* :math:`RH` is relative humidity as a unitless ratio


.. versionchanged:: 1.0
Changed signature from ``(relative_humidity, temperature, pressure)``

"""
return (relative_humidity
* saturation_mixing_ratio(pressure, temperature)).to('dimensionless')
w_s = saturation_mixing_ratio(pressure, temperature)
return (mpconsts.nounit.epsilon * w_s * relative_humidity
/ (mpconsts.nounit.epsilon + w_s * (1 - relative_humidity))).to('dimensionless')


@exporter.export
Expand Down Expand Up @@ -2081,27 +2088,31 @@ def relative_humidity_from_mixing_ratio(pressure, temperature, mixing_ratio):
>>> from metpy.units import units
>>> relative_humidity_from_mixing_ratio(1013.25 * units.hPa,
... 30 * units.degC, 18/1000).to('percent')
<Quantity(66.1763544, 'percent')>
<Quantity(67.1277085, 'percent')>

See Also
--------
mixing_ratio_from_relative_humidity, saturation_mixing_ratio

Notes
-----
Formula based on that from [Hobbs1977]_ pg. 74.
Employs [WMO8]_ eq. 4.A.16 as derived from WMO relative humidity definition based on
vapor partial pressures (eq. 4.A.15).

.. math:: rh = \frac{w}{w_s}
.. math:: RH = \frac{w}{\epsilon + w} \frac{\epsilon + w_s}{w_s}

* :math:`rh` is relative humidity as a unitless ratio
* :math:`w` is mixing ratio
* :math:`w_s` is the saturation mixing ratio
* :math:`\epsilon` is the molecular weight ratio of vapor to dry air
* :math:`RH` is relative humidity as a unitless ratio

.. versionchanged:: 1.0
Changed signature from ``(mixing_ratio, temperature, pressure)``

"""
return mixing_ratio / saturation_mixing_ratio(pressure, temperature)
w_s = saturation_mixing_ratio(pressure, temperature)
return (mixing_ratio / (mpconsts.nounit.epsilon + mixing_ratio)
* (mpconsts.nounit.epsilon + w_s) / w_s)


@exporter.export
Expand Down Expand Up @@ -2217,28 +2228,33 @@ def relative_humidity_from_specific_humidity(pressure, temperature, specific_hum
>>> from metpy.units import units
>>> relative_humidity_from_specific_humidity(1013.25 * units.hPa,
... 30 * units.degC, 18/1000).to('percent')
<Quantity(67.3893629, 'percent')>
<Quantity(68.3229304, 'percent')>

See Also
--------
relative_humidity_from_mixing_ratio

Notes
-----
Formula based on that from [Hobbs1977]_ pg. 74. and [Salby1996]_ pg. 118.
Employs [WMO8]_ eq. 4.A.16 as derived from WMO relative humidity definition based on
vapor partial pressures (eq. 4.A.15).

.. math:: RH = \frac{q}{(1-q)w_s}
.. math:: RH = \frac{w}{\epsilon + w} \frac{\epsilon + w_s}{w_s}

* :math:`RH` is relative humidity as a unitless ratio
* :math:`q` is specific humidity
given :math: w = \frac{q}{1-q}

* :math:`w` is mixing ratio
* :math:`w_s` is the saturation mixing ratio
* :math:`q` is the specific humidity
* :math:`\epsilon` is the molecular weight ratio of vapor to dry air
* :math:`RH` is relative humidity as a unitless ratio

.. versionchanged:: 1.0
Changed signature from ``(specific_humidity, temperature, pressure)``

"""
return (mixing_ratio_from_specific_humidity(specific_humidity)
/ saturation_mixing_ratio(pressure, temperature))
return relative_humidity_from_mixing_ratio(
pressure, temperature, mixing_ratio_from_specific_humidity(specific_humidity))


@exporter.export
Expand Down Expand Up @@ -3544,7 +3560,7 @@ def thickness_hydrostatic_from_relative_humidity(pressure, temperature, relative
>>> thickness_hydrostatic_from_relative_humidity(p[ip1000_500],
... T[ip1000_500],
... rh[ip1000_500])
<Quantity(5781.35394, 'meter')>
<Quantity(5781.16001, 'meter')>

See Also
--------
Expand Down Expand Up @@ -3857,21 +3873,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 @@ -3885,20 +3896,87 @@ 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}

* :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))
sig = Signature([Parameter('pressure', Parameter.POSITIONAL_OR_KEYWORD),
Parameter('temperature', Parameter.POSITIONAL_OR_KEYWORD),
Parameter('specific_humidity', Parameter.POSITIONAL_OR_KEYWORD)])

try:
bound_args = sig.bind(*args, **kwargs)
_warnings.warn(
'Temperature argument is unused and will be deprecated in a future version.',
PendingDeprecationWarning)
except TypeError:
sig = signature(_dewpoint_from_specific_humidity)
bound_args = sig.bind(*args, **kwargs)

return _dewpoint_from_specific_humidity(bound_args.arguments['pressure'],
bound_args.arguments['specific_humidity'])


@preprocess_and_wrap(
wrap_like='specific_humidity',
broadcast=('specific_humidity', 'pressure')
)
@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

.. versionchanged:: 1.6
Made `temperature` arg optional, to be deprecated

.. versionchanged:: 1.0
Changed signature from ``(specific_humidity, temperature, pressure)``
"""
w = mixing_ratio_from_specific_humidity(specific_humidity)
e = pressure * w / (mpconsts.nounit.epsilon + w)

return dewpoint(e)


@exporter.export
Expand Down
10 changes: 5 additions & 5 deletions src/metpy/units.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,10 +249,10 @@ def _check_argument_units(args, defaults, dimensionality):
yield arg, val, 'none', need


def _get_changed_version(docstring):
def _get_changed_versions(docstring):
"""Find the most recent version in which the docs say a function changed."""
matches = re.findall(r'.. versionchanged:: ([\d.]+)', docstring)
return max(matches) if matches else None
return matches


def _check_units_outer_helper(func, *args, **kwargs):
Expand Down Expand Up @@ -302,10 +302,10 @@ def _check_units_inner_helper(func, sig, defaults, dims, *args, **kwargs):

# If function has changed, mention that fact
if func.__doc__:
changed_version = _get_changed_version(func.__doc__)
if changed_version:
changed_versions = _get_changed_versions(func.__doc__)
if changed_versions:
msg = (
f'This function changed in {changed_version}--double check '
f'This function changed in version(s) {changed_versions}--double check '
'that the function is being called properly.\n'
) + msg
raise ValueError(msg)
Expand Down
Loading