From 0b43f7f23690bd1017234b837eadf2d8a9df43ac Mon Sep 17 00:00:00 2001 From: myuau Date: Sat, 18 Apr 2026 11:09:13 -0400 Subject: [PATCH 1/7] feat: implement altimeter and MSLP inverse functions with round-trip tests --- src/metpy/calc/basic.py | 81 ++++++++++++++++++++++++++++++++++++++++ tests/calc/test_basic.py | 53 ++++++++++++++++++++++++++ 2 files changed, 134 insertions(+) diff --git a/src/metpy/calc/basic.py b/src/metpy/calc/basic.py index 7f516b03a9b..9675bb410f2 100644 --- a/src/metpy/calc/basic.py +++ b/src/metpy/calc/basic.py @@ -1223,6 +1223,87 @@ def altimeter_to_station_pressure(altimeter_value, height): + units.Quantity(0.3, 'hPa')) +@exporter.export +@preprocess_and_wrap(wrap_like='station_pressure') +@check_units('[pressure]', '[length]') +def station_pressure_to_altimeter(station_pressure, height): + r"""Convert station pressure to the altimeter setting. + + This function calculates the altimeter setting, which is the pressure value + to which an aircraft altimeter scale is set so that it indicates the + altitude above mean sea-level. This is the mathematical inverse of + altimeter_to_station_pressure. + + Parameters + ---------- + station_pressure : `pint.Quantity` + The atmospheric pressure at the designated station elevation + + height: `pint.Quantity` + Elevation of the station measuring pressure + + Returns + ------- + `pint.Quantity` + The altimeter setting value (in. Hg or hPa) + + Notes + ----- + This function is implemented by inverting the equations from the + Smithsonian Handbook (1951) p. 269 used in altimeter_to_station_pressure. + + The 0.3 hPa offset is subtracted from the station pressure before + performing the exponentiation to ensure mathematical consistency with + the original forward formula. + """ + # Calculate the Poisson constant n (approx. 0.190284) + # n = (Rd * gamma) / g + n = (mpconsts.Rd * gamma / mpconsts.g).to_base_units() + + # 1. Subtract the 0.3 hPa offset from station pressure first + # 2. Raise to power of n + # 3. Add the height correction term + # 4. Raise the entire result to 1/n + return (((station_pressure - units.Quantity(0.3, 'hPa')) ** n + + ((p0.to(station_pressure.units) ** n * gamma * height) / t0)) ** (1 / n)) + + +@exporter.export +@preprocess_and_wrap(wrap_like='station_pressure') +@check_units('[pressure]', '[length]', '[temperature]') +def station_to_sea_level_pressure(station_pressure, height, temperature): + r"""Convert station pressure to sea level pressure (MSLP). + + This function calculates the Mean Sea Level Pressure (MSLP) using the + station pressure, the station elevation, and the current temperature. + This follows the requirements for atmospheric pressure reduction. + + Parameters + ---------- + station_pressure : `pint.Quantity` + The atmospheric pressure measured at the station + + height : `pint.Quantity` + The elevation of the station + + temperature : `pint.Quantity` + The air temperature at the station + + Returns + ------- + `pint.Quantity` + The calculated sea level pressure + + Notes + ----- + The formula used is a variation of the hypsometric equation: + .. math:: P_{slp} = P_s \exp \left( \frac{g \cdot H}{R_d \cdot \bar{T}} \right) + """ + # Rd is the gas constant for dry air, g is acceleration due to gravity + # These are typically pulled from metpy.constants + return station_pressure * np.exp((mpconsts.g * height) / (mpconsts.Rd * temperature)) + + @exporter.export @preprocess_and_wrap(wrap_like='altimeter_value') @check_units('[pressure]', '[length]', '[temperature]') diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index dc8770ef4d7..77929c3c10f 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -10,6 +10,7 @@ from metpy.calc import (add_height_to_pressure, add_pressure_to_height, altimeter_to_sea_level_pressure, altimeter_to_station_pressure, + station_to_sea_level_pressure, station_pressure_to_altimeter, apparent_temperature, coriolis_parameter, geopotential_to_height, heat_index, height_to_geopotential, height_to_pressure_std, pressure_to_height_std, sigma_to_pressure, smooth_circular, @@ -790,6 +791,58 @@ def test_altimeter_to_station_pressure_hpa(array_type): assert_array_almost_equal(res, truth, 3) +def test_station_pressure_to_altimeter_inhg(): + """Test the station pressure to altimeter function with inches of mercury.""" + station_pressure = 950.96498 * units.hectopascal + elev = 500 * units.m + res = station_pressure_to_altimeter(station_pressure, elev) + truth = 29.8 * units.inHg + assert_almost_equal(res, truth, 3) + + +def test_station_pressure_to_altimeter_hpa(array_type): + """Test the station pressure to altimeter function with hectopascals.""" + mask = [False, True, False, True] + station_pressure = array_type( + [784.262996, 838.651657, 896.037821, 954.639265], 'hectopascal', mask=mask + ) + elev = array_type([2000., 1500., 1000., 500.], 'meter', mask=mask) + res = station_pressure_to_altimeter(station_pressure, elev) + truth = array_type([1000., 1005., 1010., 1013.], 'hectopascal', mask=mask) + assert_array_almost_equal(res, truth, 3) + +def test_station_to_sea_level_pressure_basic(): + """Test MSLP calculation with standard values.""" + p_station = 950.96498 * units.hPa + elev = 500 * units.m + temp = 30 * units.degC + # Based on the inverse of the existing test truth + expected_mslp = 1006.089 * units.hPa + + res = station_to_sea_level_pressure(p_station, elev, temp) + assert_almost_equal(res, expected_mslp, 2) + + +def test_altimeter_round_trip(array_type): + """Test that moving forward and backward returns the original value.""" + # Use a range of values to ensure the 0.3 hPa offset is handled correctly everywhere + altim_start = array_type([29.92, 30.00, 31.00], 'inHg') + elev = array_type([0, 1000, 2000], 'meter') + + # Forward then Backward + intermediate_p = altimeter_to_station_pressure(altim_start, elev) + final_altim = station_pressure_to_altimeter(intermediate_p, elev) + + assert_array_almost_equal(altim_start, final_altim, 4) + + +def test_station_pressure_to_altimeter_invalid_units(): + """Verify that the decorator catches incorrect unit types.""" + with pytest.raises(ValueError): + # Passing meters where a pressure unit is expected + station_pressure_to_altimeter(units.Quantity(100, 'm'), units.Quantity(100, 'm')) + + def test_altimiter_to_sea_level_pressure_inhg(): """Test the altimeter to sea level pressure function with inches of mercury.""" altim = 29.8 * units.inHg From 61f4f1aa7545a0f95b5dea866b59169c59e26f18 Mon Sep 17 00:00:00 2001 From: myuau Date: Sat, 18 Apr 2026 12:06:15 -0400 Subject: [PATCH 2/7] style: remove trailing whitespace in tests --- tests/calc/test_basic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index 77929c3c10f..7dfbd1f5851 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -818,7 +818,7 @@ def test_station_to_sea_level_pressure_basic(): temp = 30 * units.degC # Based on the inverse of the existing test truth expected_mslp = 1006.089 * units.hPa - + res = station_to_sea_level_pressure(p_station, elev, temp) assert_almost_equal(res, expected_mslp, 2) @@ -828,11 +828,11 @@ def test_altimeter_round_trip(array_type): # Use a range of values to ensure the 0.3 hPa offset is handled correctly everywhere altim_start = array_type([29.92, 30.00, 31.00], 'inHg') elev = array_type([0, 1000, 2000], 'meter') - + # Forward then Backward intermediate_p = altimeter_to_station_pressure(altim_start, elev) final_altim = station_pressure_to_altimeter(intermediate_p, elev) - + assert_array_almost_equal(altim_start, final_altim, 4) From 904241c9c48c11fab612e884a62c5ae9293966da Mon Sep 17 00:00:00 2001 From: myuau Date: Sat, 18 Apr 2026 12:26:16 -0400 Subject: [PATCH 3/7] docs: register new pressure functions in calc override template --- docs/_templates/overrides/metpy.calc.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/_templates/overrides/metpy.calc.rst b/docs/_templates/overrides/metpy.calc.rst index 926a82b58f3..0ec2da72b4f 100644 --- a/docs/_templates/overrides/metpy.calc.rst +++ b/docs/_templates/overrides/metpy.calc.rst @@ -194,6 +194,8 @@ Standard Atmosphere altimeter_to_station_pressure height_to_pressure_std pressure_to_height_std + station_pressure_to_altimeter + station_to_sea_level_pressure Smoothing --------- From a573fbb1757715b2fcceeaeb0626f47c8890f044 Mon Sep 17 00:00:00 2001 From: myuau Date: Sat, 18 Apr 2026 12:32:19 -0400 Subject: [PATCH 4/7] style: fix import sorting and whitespace in basic.py and tests --- tests/calc/test_basic.py | 34 ++++++++++++++++++++++++++-------- 1 file changed, 26 insertions(+), 8 deletions(-) diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index 7dfbd1f5851..534791457ff 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -8,14 +8,32 @@ import pytest import xarray as xr -from metpy.calc import (add_height_to_pressure, add_pressure_to_height, - altimeter_to_sea_level_pressure, altimeter_to_station_pressure, - station_to_sea_level_pressure, station_pressure_to_altimeter, - apparent_temperature, coriolis_parameter, geopotential_to_height, - heat_index, height_to_geopotential, height_to_pressure_std, - pressure_to_height_std, sigma_to_pressure, smooth_circular, - smooth_gaussian, smooth_n_point, smooth_rectangular, smooth_window, - wind_components, wind_direction, wind_speed, windchill, zoom_xarray) +from metpy.calc import ( + add_height_to_pressure, + add_pressure_to_height, + altimeter_to_sea_level_pressure, + altimeter_to_station_pressure, + apparent_temperature, + coriolis_parameter, + geopotential_to_height, + heat_index, + height_to_geopotential, + height_to_pressure_std, + pressure_to_height_std, + sigma_to_pressure, + smooth_circular, + smooth_gaussian, + smooth_n_point, + smooth_rectangular, + smooth_window, + station_pressure_to_altimeter, + station_to_sea_level_pressure, + wind_components, + wind_direction, + wind_speed, + windchill, + zoom_xarray, +) from metpy.cbook import get_test_data from metpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal from metpy.units import units From d74b2ab33ab2b316be0dfbe456a98944b44eaef0 Mon Sep 17 00:00:00 2001 From: myuau Date: Sat, 18 Apr 2026 12:54:23 -0400 Subject: [PATCH 5/7] manual style fix for imports and spacing --- tests/calc/test_basic.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index 534791457ff..c74bd9879dc 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -1,3 +1,4 @@ +# Final style fix for CI # Copyright (c) 2008,2015,2017,2019 MetPy Developers. # Distributed under the terms of the BSD 3-Clause License. # SPDX-License-Identifier: BSD-3-Clause From df6553d5100393a443ac720c0b6b5d7804a4c572 Mon Sep 17 00:00:00 2001 From: myuau Date: Sat, 18 Apr 2026 13:11:05 -0400 Subject: [PATCH 6/7] style: manual import and spacing fix --- tests/calc/test_basic.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index c74bd9879dc..534791457ff 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -1,4 +1,3 @@ -# Final style fix for CI # Copyright (c) 2008,2015,2017,2019 MetPy Developers. # Distributed under the terms of the BSD 3-Clause License. # SPDX-License-Identifier: BSD-3-Clause From a2c122f087e6d12150ea4c9b946f223853615691 Mon Sep 17 00:00:00 2001 From: myuau Date: Sat, 18 Apr 2026 13:18:20 -0400 Subject: [PATCH 7/7] Fix import order and spacing in tests/calc/test_basic.py --- tests/calc/test_basic.py | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/tests/calc/test_basic.py b/tests/calc/test_basic.py index 534791457ff..a1300cf538a 100644 --- a/tests/calc/test_basic.py +++ b/tests/calc/test_basic.py @@ -8,32 +8,14 @@ import pytest import xarray as xr -from metpy.calc import ( - add_height_to_pressure, - add_pressure_to_height, - altimeter_to_sea_level_pressure, - altimeter_to_station_pressure, - apparent_temperature, - coriolis_parameter, - geopotential_to_height, - heat_index, - height_to_geopotential, - height_to_pressure_std, - pressure_to_height_std, - sigma_to_pressure, - smooth_circular, - smooth_gaussian, - smooth_n_point, - smooth_rectangular, - smooth_window, - station_pressure_to_altimeter, - station_to_sea_level_pressure, - wind_components, - wind_direction, - wind_speed, - windchill, - zoom_xarray, -) +from metpy.calc import (add_height_to_pressure, add_pressure_to_height, + altimeter_to_sea_level_pressure, altimeter_to_station_pressure, + apparent_temperature, coriolis_parameter, geopotential_to_height, + heat_index, height_to_geopotential, height_to_pressure_std, + pressure_to_height_std, sigma_to_pressure, smooth_circular, + smooth_gaussian, smooth_n_point, smooth_rectangular, smooth_window, + station_pressure_to_altimeter, station_to_sea_level_pressure, + wind_components, wind_direction, wind_speed, windchill, zoom_xarray) from metpy.cbook import get_test_data from metpy.testing import assert_almost_equal, assert_array_almost_equal, assert_array_equal from metpy.units import units @@ -829,6 +811,7 @@ def test_station_pressure_to_altimeter_hpa(array_type): truth = array_type([1000., 1005., 1010., 1013.], 'hectopascal', mask=mask) assert_array_almost_equal(res, truth, 3) + def test_station_to_sea_level_pressure_basic(): """Test MSLP calculation with standard values.""" p_station = 950.96498 * units.hPa