Skip to content

Commit 4088caf

Browse files
committed
ENH: auto-detection of pressure conversion factor
1 parent f0fb5c2 commit 4088caf

2 files changed

Lines changed: 100 additions & 28 deletions

File tree

rocketpy/environment/environment.py

Lines changed: 87 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,7 +1120,7 @@ def set_atmospheric_model( # pylint: disable=too-many-statements
11201120
temperature=None,
11211121
wind_u=0,
11221122
wind_v=0,
1123-
pressure_conversion_factor="Pa",
1123+
pressure_conversion_factor=None,
11241124
):
11251125
"""Define the atmospheric model for this Environment.
11261126
@@ -1219,10 +1219,14 @@ def set_atmospheric_model( # pylint: disable=too-many-statements
12191219
return a corresponding wind-v in m/s.
12201220
pressure_conversion_factor : string, int, float
12211221
This defines the pressure conversion factor to Pa when type is
1222-
``forecast`` or ``reanalysis``. The pressure unit from the data may
1223-
not be in Pascal, so the correction is necessary. Valid strings are
1224-
("mbar", "hPa", "Pa"), or a strictly positive number if using a
1225-
custom pressure unit.
1222+
``forecast``, ``reanalysis``, or ``ensemble``. The pressure unit
1223+
from the data may not be in Pascal, so the correction is necessary.
1224+
Valid strings are ``"mbar"``, ``"hPa"``, or ``"Pa"``, or a strictly
1225+
positive number if using a custom pressure unit. ERA5 and ECMWF
1226+
reanalysis ``.nc`` files store pressure in hPa; use ``"hPa"`` for
1227+
those. MERRA2 files and online forecast models (GFS, NAM, RAP,
1228+
HRRR) store pressure in Pa; the default ``"Pa"`` is correct for
1229+
these.
12261230
12271231
Returns
12281232
-------
@@ -1272,27 +1276,36 @@ def set_atmospheric_model( # pylint: disable=too-many-statements
12721276
case "windy":
12731277
self.process_windy_atmosphere(file)
12741278
case "forecast" | "reanalysis" | "ensemble":
1275-
conversion_factor = 1
1276-
if not isinstance(pressure_conversion_factor, (float, int, str)):
1277-
raise ValueError(
1278-
"Argument 'pressure_conversion_factor' must be numeric or a standard pressure unit ('mbar', 'hPa', 'Pa')!"
1279-
)
1280-
if isinstance(pressure_conversion_factor, (float, int)):
1281-
if pressure_conversion_factor <= 0:
1282-
raise ValueError(
1283-
"Argument 'pressure_conversion_factor' must be strictly positive!"
1284-
)
1285-
else:
1286-
conversion_factor = pressure_conversion_factor
1287-
if isinstance(pressure_conversion_factor, str):
1288-
if pressure_conversion_factor.lower() in ("mbar", "hpa"):
1289-
conversion_factor = 100
1290-
elif pressure_conversion_factor.lower() == "pa":
1291-
conversion_factor = 1
1292-
else:
1279+
# Capture the user-supplied names before __validate_dictionary
1280+
# converts them to dicts, so they can drive auto-detection.
1281+
_input_dict = (
1282+
dictionary.upper() if isinstance(dictionary, str) else None
1283+
)
1284+
_input_file = (
1285+
file.upper() if isinstance(file, str) else None
1286+
)
1287+
1288+
# Validate format of user-supplied value (if any).
1289+
# When None, auto-detection runs after dictionary resolution.
1290+
if pressure_conversion_factor is not None:
1291+
if not isinstance(pressure_conversion_factor, (float, int, str)):
12931292
raise ValueError(
1294-
"Argument 'pressure_conversion_factor' unit must be a standard pressure unit ('mbar', 'hPa', 'Pa')!"
1293+
"Argument 'pressure_conversion_factor' must be numeric or a standard pressure unit ('mbar', 'hPa', 'Pa')!"
12951294
)
1295+
if isinstance(pressure_conversion_factor, (float, int)):
1296+
if pressure_conversion_factor <= 0:
1297+
raise ValueError(
1298+
"Argument 'pressure_conversion_factor' must be strictly positive!"
1299+
)
1300+
if isinstance(pressure_conversion_factor, str):
1301+
if pressure_conversion_factor.lower() not in (
1302+
"mbar",
1303+
"hpa",
1304+
"pa",
1305+
):
1306+
raise ValueError(
1307+
"Argument 'pressure_conversion_factor' unit must be a standard pressure unit ('mbar', 'hPa', 'Pa')!"
1308+
)
12961309

12971310
if isinstance(file, str):
12981311
shortcut_map = self.__atm_type_file_to_function_map.get(type, {})
@@ -1325,6 +1338,32 @@ def set_atmospheric_model( # pylint: disable=too-many-statements
13251338
)
13261339

13271340
dictionary = self.__validate_dictionary(file, dictionary)
1341+
1342+
# Determine the numeric conversion factor (pressure → Pa).
1343+
if pressure_conversion_factor is not None:
1344+
# User explicitly supplied a value — honour it.
1345+
if isinstance(pressure_conversion_factor, str):
1346+
conversion_factor = (
1347+
100
1348+
if pressure_conversion_factor.lower() in ("mbar", "hpa")
1349+
else 1
1350+
)
1351+
else:
1352+
conversion_factor = pressure_conversion_factor
1353+
else:
1354+
# Auto-detect. Primary source: known-model lookup table.
1355+
# Fallback: units attribute inside the file (handled inside
1356+
# get_pressure_levels_from_file when conversion_factor=None).
1357+
_hpa_dicts = {"ECMWF", "ECMWF_V0", "ERA5", "MERRA2"}
1358+
_pa_files = {"GFS", "NAM", "RAP", "HRRR", "AIGFS", "HIRESW", "GEFS"}
1359+
if _input_dict in _hpa_dicts or _input_file in _hpa_dicts:
1360+
conversion_factor = 100
1361+
elif _input_file in _pa_files:
1362+
conversion_factor = 1
1363+
else:
1364+
# Unknown model or custom dict: read units from file.
1365+
conversion_factor = None
1366+
13281367
try:
13291368
fetch_function = self.__atm_type_file_to_function_map[type][file]
13301369
except KeyError:
@@ -1339,6 +1378,30 @@ def set_atmospheric_model( # pylint: disable=too-many-statements
13391378
)
13401379
else:
13411380
self.process_ensemble(dataset, dictionary, conversion_factor)
1381+
1382+
ground_pressure = self.pressure(self.elevation)
1383+
if not (30_000 <= ground_pressure <= 120_000):
1384+
if pressure_conversion_factor is None:
1385+
hint = (
1386+
"The unit was auto-detected from the file's pressure "
1387+
"level variable, but the result is still out of range. "
1388+
"Override by passing pressure_conversion_factor explicitly "
1389+
"('hPa' for ERA5/ECMWF files, 'Pa' for MERRA2 and online "
1390+
"forecast models such as GFS, NAM, RAP, HRRR)."
1391+
)
1392+
else:
1393+
hint = (
1394+
f"pressure_conversion_factor='{pressure_conversion_factor}' "
1395+
f"may be wrong. ERA5/ECMWF reanalysis files store pressure "
1396+
f"in hPa — use 'hPa'. MERRA2 and online forecast models "
1397+
f"(GFS, NAM, RAP, HRRR) store pressure in Pa — use 'Pa'."
1398+
)
1399+
warnings.warn(
1400+
f"Ground-level pressure is {ground_pressure:.0f} Pa, which is "
1401+
f"outside the expected range [30 000 Pa, 120 000 Pa]. {hint}",
1402+
UserWarning,
1403+
stacklevel=2,
1404+
)
13421405
case _: # pragma: no cover
13431406
raise ValueError(f"Unknown model type '{type}'.")
13441407

rocketpy/environment/tools.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -178,9 +178,11 @@ def get_pressure_levels_from_file(data, dictionary, conversion_factor):
178178
The netCDF4 dataset containing the pressure level data.
179179
dictionary : dict
180180
A dictionary mapping variable names to dataset keys.
181-
conversion_factor : float, int
182-
Specifies the factor by which the pressure will be multiplied
183-
in order to transform it to Pascal.
181+
conversion_factor : float, int, or None
182+
Specifies the factor by which the pressure will be multiplied to
183+
transform it to Pascal. If ``None``, the factor is auto-detected from
184+
the ``units`` attribute of the pressure level variable in the dataset
185+
(e.g. ``"millibars"`` or ``"hPa"`` → 100; ``"Pa"`` → 1).
184186
185187
Returns
186188
-------
@@ -193,7 +195,14 @@ def get_pressure_levels_from_file(data, dictionary, conversion_factor):
193195
If the pressure levels cannot be read from the file.
194196
"""
195197
try:
196-
levels = conversion_factor * data.variables[dictionary["level"]][:]
198+
level_var = data.variables[dictionary["level"]]
199+
if conversion_factor is None:
200+
raw_units = getattr(level_var, "units", "").lower().strip()
201+
if raw_units in ("hpa", "mbar", "millibars", "hectopascal", "hectopascals"):
202+
conversion_factor = 100
203+
else:
204+
conversion_factor = 1
205+
levels = conversion_factor * level_var[:]
197206
except KeyError as e:
198207
raise ValueError(
199208
"Unable to read pressure levels from file. Check file and dictionary."

0 commit comments

Comments
 (0)