Skip to content

Saturation calculations drop masked temperatures at two np.where sites #4154

Open

Description

@vera-rubin

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.py

For 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

  1. added a commit that references this issue on Oct 24, 2026
    35197ce
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions