Repository navigation
Description
What went wrong?
With default settings, saturation_mixing_ratio returns finite humidity for a masked temperature, and saturation_vapor_pressure(..., phase='auto') also loses the mask. Explicit liquid/solid vapor-pressure selection preserves it.
Per the masked-array guidance and Quantity constructor guidance, the masked observation should stay masked or NaN. The known 300 K observation should follow the mixing-ratio formula and saturation-pressure formula.
Operating System
Linux.
Version
MetPy development snapshot 1.7.0.post478+g7910b5ceb, built from source at 7910b5ceb506551606acb6e3c00a5c3f3a4c5a2b. Upstream main matched this pin on 2026-10-12.
| Dependency | Primary | CI dependency set |
|---|---|---|
| NumPy | 2.5.4 | 2.4.6 |
| SciPy | 1.18.1 | 1.17.1 |
| Pint | 0.26.1 | 0.25.3 |
| xarray | 2026.9.0 | 2025.10.1 |
| matplotlib | 3.11.2 | 3.10.9 |
| mpmath | 1.4.1 | 1.4.1 |
The "CI dependency set" is our second local environment's pins, not an upstream CI run.
Python Version
3.12.3 in both environments.
Code to Reproduce
Save the snippet as reproduce.py and the lists below as requirements-primary.txt and requirements-ci.txt, then run:
git clone https://github.com/Unidata/MetPy.git MetPy
git -C MetPy checkout --detach 7910b5ceb506551606acb6e3c00a5c3f3a4c5a2b
python3 -m venv .venv
. .venv/bin/activate
python -m pip install --only-binary=:all: -r requirements-primary.txt
python -m pip install --no-deps ./MetPy
python reproduce.pyFor the second environment, repeat in a fresh venv with requirements-ci.txt. Reproducer code is CC0-1.0.
import numpy as np
from metpy.calc import mixing_ratio, saturation_mixing_ratio, saturation_vapor_pressure
from metpy.units import units
temperature = units.Quantity(np.ma.array([300., 300.], mask=[False, True]), 'K')
pressure = 1000 * units.hPa
ratio = saturation_mixing_ratio(pressure, temperature).m_as('dimensionless')
auto = saturation_vapor_pressure(temperature, phase='auto').m_as('Pa')
liquid = saturation_vapor_pressure(temperature, phase='liquid').m_as('Pa')
composed = mixing_ratio(saturation_vapor_pressure(temperature), pressure).m_as('dimensionless')
for label, value in [('ratio', ratio), ('auto', auto),
('liquid control', liquid), ('composed control', composed)]:
print(label, value, 'mask:', np.ma.getmaskarray(value))
assert np.ma.is_masked(ratio[1]) or np.isnan(ratio[1])
assert np.ma.is_masked(auto[1]) or np.isnan(auto[1])requirements-primary.txt
certifi==2026.7.22
charset-normalizer==3.5.2
contourpy==1.4.0
cycler==0.12.1
flexcache==0.3
flexparser==0.4
fonttools==4.66.1
idna==3.20
iniconfig==2.3.1
kiwisolver==1.5.1
matplotlib==3.11.2
mpmath==1.4.1
numpy==2.5.4
packaging==26.3
pandas==3.0.6
pillow==12.3.0
Pint==0.26.1
platformdirs==4.13.0
pluggy==1.6.0
pooch==1.9.0
Pygments==2.21.0
pyparsing==3.3.3
pyproj==3.8.0
pytest==9.1.1
python-dateutil==2.9.0.post0
requests==2.34.2
scipy==1.18.1
six==1.17.0
traitlets==5.16.1
typing_extensions==4.16.0
urllib3==2.8.0
xarray==2026.9.0
requirements-ci.txt
certifi==2026.7.22
charset-normalizer==3.5.2
contourpy==1.4.0
cycler==0.12.1
flexcache==0.3
flexparser==0.4
fonttools==4.66.1
idna==3.20
kiwisolver==1.5.1
matplotlib==3.10.9
mpmath==1.4.1
numpy==2.4.6
packaging==26.3
pandas==3.0.5
pillow==12.3.0
Pint==0.25.3
platformdirs==4.13.0
pooch==1.9.0
pyparsing==3.3.3
pyproj==3.7.2
python-dateutil==2.9.0.post0
requests==2.34.2
scipy==1.17.1
six==1.17.0
traitlets==5.16.1
typing_extensions==4.16.0
urllib3==2.8.0
xarray==2025.10.1
Errors, Traceback, and Logs
The original per-function reproducers and controls gave these results in both local environments. The combined snippet above condenses those calls and hasn't itself been rerun.
| Call | Known observation | Masked observation | Output mask |
|---|---|---|---|
| Default saturation mixing ratio | 0.02274315 kg/kg | 0.62195691 kg/kg | [False, False] |
| Automatic saturation pressure | 3527.71024218 Pa | 611.2 Pa | [False, False] |
| Explicit liquid control | 3527.71024218 Pa | masked | [False, True] |
| Composed pressure/ratio control | 0.02274315 kg/kg | masked | [False, True] |
Independent formula values for the known entry are 0.0227431500517953089848 kg/kg and 3527.71024217564343662 Pa. Both stored temperatures are valid 300 K, and the second is marked missing.
The two selection sites use np.where, which drops the calculated masks. A fix needs selection that preserves missing observations while keeping the mixing-ratio warning and NaN behavior for undefined equilibrium states. We didn't test a fix. PR 3726 covers a masked exponential warning in another calculation, while this report is about finite unmasked output.
We checked both documented masked constructors and both dependency sets. Explicit liquid/solid selection, the composed calculation and NaN controls kept missing values, and the automatic-phase check also covered a known 260 K entry. We didn't survey other temperatures or real datasets.
— ChipLab · CLAB-2026-1079
Activity
FIX: Preserve masks in saturation pressure/mixing ratio selection