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

Specifying vertical levels rather than height levels #2063

Merged
merged 15 commits into from
Feb 12, 2025
12 changes: 7 additions & 5 deletions improver/cli/generate_metadata_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def process(
Dictionary containing values for one or more of: "name", "units", "time",
"time_bounds", "frt", "spp__relative_to_threshold", "attributes"
(dictionary of additional metadata attributes) and "coords" (dictionary).
"coords" can contain "height_levels" (list of height/pressure level values),
"coords" can contain "vertical_levels" (list of height/pressure level values),
and one of "realizations", "percentiles" or "thresholds" (list of dimension
values).
ensemble_members (Optional[int]):
Expand Down Expand Up @@ -75,8 +75,8 @@ def process(

from improver.synthetic_data.generate_metadata import generate_metadata
from improver.synthetic_data.utilities import (
get_height_levels,
get_leading_dimension,
get_vertical_levels,
)
from improver.utilities.temporal import cycletime_to_datetime

Expand All @@ -88,9 +88,11 @@ def process(
(json_input["leading_dimension"], json_input["cube_type"]) = (
get_leading_dimension(coord_data)
)
json_input["height_levels"], json_input["pressure"] = get_height_levels(
coord_data
)
(
json_input["vertical_levels"],
json_input["pressure"],
json_input["height"],
) = get_vertical_levels(coord_data)

json_input.pop("coords", None)

Expand Down
18 changes: 9 additions & 9 deletions improver/synthetic_data/generate_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,25 +49,25 @@ def _create_data_array(
ensemble_members: int,
leading_dimension: Optional[List[float]],
npoints: int,
height_levels: Optional[List[float]],
vertical_levels: Optional[List[float]],
) -> ndarray:
"""Create data array of specified shape filled with zeros"""
if leading_dimension is not None:
nleading_dimension = len(leading_dimension)
else:
nleading_dimension = ensemble_members

if height_levels is not None:
nheight_levels = len(height_levels)
if vertical_levels is not None:
nvertical_levels = len(vertical_levels)
else:
nheight_levels = None
nvertical_levels = None

data_shape = []

if nleading_dimension > 1:
data_shape.append(nleading_dimension)
if nheight_levels is not None:
data_shape.append(nheight_levels)
if nvertical_levels is not None:
data_shape.append(nvertical_levels)

data_shape.append(npoints)
data_shape.append(npoints)
Expand Down Expand Up @@ -174,11 +174,11 @@ def generate_metadata(
kwargs[spacing_axis] = DEFAULT_GRID_SPACING[kwargs["spatial_grid"]]

# Create ndimensional array of zeros
if "height_levels" not in kwargs:
kwargs["height_levels"] = None
if "vertical_levels" not in kwargs:
kwargs["vertical_levels"] = None

data = _create_data_array(
ensemble_members, leading_dimension, npoints, kwargs["height_levels"]
ensemble_members, leading_dimension, npoints, kwargs["vertical_levels"]
)
missing_mandatory_attributes = MANDATORY_ATTRIBUTES - mandatory_attributes.keys()
if missing_mandatory_attributes:
Expand Down
67 changes: 42 additions & 25 deletions improver/synthetic_data/set_up_test_cubes.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,7 +285,7 @@ def _create_dimension_coord(

if issubclass(coord_array.dtype.type, float):
# option needed for realizations percentile & probability cube setup
# and heights coordinate
# and vertical coordinate
coord_array = coord_array.astype(np.float32)
else:
coord_array = np.arange(data_length).astype(np.int32)
Expand All @@ -304,8 +304,9 @@ def _construct_dimension_coords(
x_coord: Optional[DimCoord] = None,
spot_index: Optional[DimCoord] = None,
realizations: Optional[Union[List[float], ndarray]] = None,
height_levels: Optional[Union[List[float], ndarray]] = None,
vertical_levels: Optional[Union[List[float], ndarray]] = None,
pressure: bool = False,
height: bool = False,
) -> DimCoord:
"""Create array of all dimension coordinates. The expected dimension order
for gridded cubes is realization, height/pressure, y, x or realization,
Expand All @@ -314,8 +315,8 @@ def _construct_dimension_coords(

A realization coordinate will be created if the cube is
(n_spatial_dims + 1) or (n_spatial_dims + 2), even if no values for the
realizations argument are provided. To create a height coordinate, the
height_levels must be provided.
realizations argument are provided. To create a vertical coordinate, the
vertical_levels must be provided.
"""

data_shape = data.shape
Expand All @@ -330,24 +331,24 @@ def _construct_dimension_coords(

if (
realizations is not None
and height_levels is not None
and vertical_levels is not None
and ndims != n_spatial_dims + 2
):
raise ValueError(
f"Input data must have {n_spatial_dims + 2} dimensions to add both realization "
f"and height coordinates: got {ndims}"
f"and vertical coordinates: got {ndims}"
)

if height_levels is None and ndims > n_spatial_dims + 1:
if vertical_levels is None and ndims > n_spatial_dims + 1:
raise ValueError(
"Height levels must be provided if data has > "
"Vertical levels must be provided if data has > "
f"{n_spatial_dims + 1} dimensions."
)

dim_coords = []

if ndims == n_spatial_dims + 2 or (
height_levels is None and ndims == n_spatial_dims + 1
vertical_levels is None and ndims == n_spatial_dims + 1
):
coord_name = "realization"
coord_units = DIM_COORD_ATTRIBUTES[coord_name]["units"]
Expand All @@ -358,27 +359,35 @@ def _construct_dimension_coords(
)
dim_coords.append((realization_coord, 0))

if height_levels is not None and n_spatial_dims + 1 <= ndims <= n_spatial_dims + 2:
# Determine the index of the height coord based on if a realization coord has been created
if (
vertical_levels is not None
and n_spatial_dims + 1 <= ndims <= n_spatial_dims + 2
):
# Determine the index of vertical coord based on if a realization coord has been created
i = len(dim_coords)
coord_length = data_shape[i]

if pressure and height:
raise ValueError("Both pressure and height cannot be set to True")

if pressure:
coord_name = "pressure"
else:
elif height:
coord_name = "height"
else:
raise ValueError("Either pressure or height must be set to True")

coord_units = DIM_COORD_ATTRIBUTES[coord_name]["units"]
coord_attributes = DIM_COORD_ATTRIBUTES[coord_name]["attributes"]

height_coord = _create_dimension_coord(
height_levels,
vertical_coord = _create_dimension_coord(
vertical_levels,
coord_length,
coord_name,
units=coord_units,
attributes=coord_attributes,
)
dim_coords.append((height_coord, i))
dim_coords.append((vertical_coord, i))

if spot_index is not None:
dim_coords.append((spot_index, len(dim_coords)))
Expand Down Expand Up @@ -415,8 +424,9 @@ def set_up_spot_variable_cube(
unique_site_id: Optional[Union[List[str], ndarray]] = None,
unique_site_id_key: Optional[str] = None,
realizations: Optional[Union[List[float], ndarray]] = None,
height_levels: Optional[Union[List[float], ndarray]] = None,
vertical_levels: Optional[Union[List[float], ndarray]] = None,
pressure: bool = False,
height: bool = False,
*args,
**kwargs,
):
Expand Down Expand Up @@ -455,11 +465,13 @@ def set_up_spot_variable_cube(
realizations:
List of forecast realizations. If not present, taken from the
leading dimension of the input data array (if 2D).
height_levels:
List of height levels in metres or pressure levels in Pa.
vertical_levels:
List of vertical levels in height (metres) or pressure levels (Pa).
pressure:
Flag to indicate whether the height levels are specified as pressure, in Pa.
Flag to indicate whether the vertical levels are specified as pressure, in Pa.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd add another flag here for "height" and subsequent places that need it.

If False, use height in metres.
height:
Flag to indicate whether the vertical levels are specified as height, in metres.

Returns:
Cube containing a single spot variable field
Expand Down Expand Up @@ -508,8 +520,9 @@ def set_up_spot_variable_cube(
data,
spot_index=spot_index,
realizations=realizations,
height_levels=height_levels,
vertical_levels=vertical_levels,
pressure=pressure,
height=height,
)

cube = _variable_cube(data, dim_coords, *args, **kwargs)
Expand All @@ -534,8 +547,9 @@ def set_up_variable_cube(
y_grid_spacing: Optional[float] = None,
domain_corner: Optional[Tuple[float, float]] = None,
realizations: Optional[Union[List[float], ndarray]] = None,
height_levels: Optional[Union[List[float], ndarray]] = None,
vertical_levels: Optional[Union[List[float], ndarray]] = None,
pressure: bool = False,
height: bool = False,
*args,
**kwargs,
):
Expand Down Expand Up @@ -564,11 +578,13 @@ def set_up_variable_cube(
realizations:
List of forecast realizations. If not present, taken from the
leading dimension of the input data array (if 3D).
height_levels:
List of height levels in metres or pressure levels in Pa.
vertical_levels:
List of vertical levels in height (metres) or pressure levels (Pa).
pressure:
Flag to indicate whether the height levels are specified as pressure, in Pa.
Flag to indicate whether the vertical levels are specified as pressure, in Pa.
If False, use height in metres.
height:
Flag to indicate whether the vertical levels are specified as height, in metres.

Returns:
Cube containing a single gridded variable field
Expand All @@ -583,8 +599,9 @@ def set_up_variable_cube(
y_coord=y_coord,
x_coord=x_coord,
realizations=realizations,
height_levels=height_levels,
vertical_levels=vertical_levels,
pressure=pressure,
height=height,
)

cube = _variable_cube(data, dim_coords, *args, **kwargs)
Expand Down
14 changes: 8 additions & 6 deletions improver/synthetic_data/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,8 @@ def get_leading_dimension(coord_data: Dict[str, Any]) -> Tuple[List[float], str]
return leading_dimension, cube_type


def get_height_levels(coord_data: Dict[str, Any]) -> Tuple[List[float], bool]:
"""Gets height level values from coords nested dictionary and sets pressure
def get_vertical_levels(coord_data: Dict[str, Any]) -> Tuple[List[float], bool, bool]:
"""Gets vertical level values from coords nested dictionary and sets pressure
value based on whether heights or pressures key is used.

Args:
Expand All @@ -66,13 +66,15 @@ def get_height_levels(coord_data: Dict[str, Any]) -> Tuple[List[float], bool]:
and a bool specifying whether the coordinate should be created as height
levels or pressure levels.
"""
height_levels = None
vertical_levels = None
pressure = False
height = False

if "heights" in coord_data:
height_levels = coord_data["heights"]
vertical_levels = coord_data["heights"]
height = True
elif "pressures" in coord_data:
height_levels = coord_data["pressures"]
vertical_levels = coord_data["pressures"]
pressure = True

return height_levels, pressure
return vertical_levels, pressure, height
2 changes: 1 addition & 1 deletion improver_tests/acceptance/test_generate_metadata_cube.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def test_height_levels(tmp_path):

def test_single_height_level(tmp_path):
"""Test metadata cube generation giving single value (rather than comma separated
list) for height levels option demotes height to scalar coordinate"""
list) for vertical levels option demotes height to scalar coordinate"""
kgo_path = kgo_dir / "kgo_single_height_level.nc"
height_level_path = kgo_dir / "single_height_level.json"
output_path = tmp_path / "output.nc"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def t_cube_fixture(profile_shift) -> Cube:
t_cube = set_up_variable_cube(
data + profile_shift,
pressure=True,
height_levels=np.arange(100000, 29999, -10000),
vertical_levels=np.arange(100000, 29999, -10000),
name="air_temperature",
units="K",
attributes=LOCAL_MANDATORY_ATTRIBUTES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def temperature_on_pressure_levels() -> Cube:
t_cube = set_up_variable_cube(
data,
pressure=True,
height_levels=np.arange(100000, 29999, -10000),
vertical_levels=np.arange(100000, 29999, -10000),
name="air_temperature",
units="K",
attributes=LOCAL_MANDATORY_ATTRIBUTES,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -138,26 +138,30 @@ def test_basic(

def test_height_levels():
"""Check that the plugin works with height level data"""

temperature = set_up_variable_cube(
np.full((1, 2, 2, 2), fill_value=293, dtype=np.float32),
name="air_temperature",
units="K",
attributes=LOCAL_MANDATORY_ATTRIBUTES,
height_levels=[100, 400],
vertical_levels=[100, 400],
height=True,
)
pressure_cube = set_up_variable_cube(
np.full((1, 2, 2, 2), fill_value=100000, dtype=np.float32),
name="surface_air_pressure",
units="Pa",
attributes=LOCAL_MANDATORY_ATTRIBUTES,
height_levels=[100, 400],
vertical_levels=[100, 400],
height=True,
)
rel_humidity = set_up_variable_cube(
np.full((1, 2, 2, 2), fill_value=1.0, dtype=np.float32),
name="relative_humidity",
units="1",
attributes=LOCAL_MANDATORY_ATTRIBUTES,
height_levels=[100, 400],
vertical_levels=[100, 400],
height=True,
)
result = HumidityMixingRatio()([temperature, pressure_cube, rel_humidity])
metadata_ok(result, temperature)
Expand All @@ -171,15 +175,15 @@ def test_pressure_levels():
name="air_temperature",
units="K",
attributes=LOCAL_MANDATORY_ATTRIBUTES,
height_levels=[95000, 100000],
vertical_levels=[95000, 100000],
pressure=True,
)
rel_humidity = set_up_variable_cube(
np.full((1, 2, 2, 2), fill_value=1.0, dtype=np.float32),
name="relative_humidity",
units="1",
attributes=LOCAL_MANDATORY_ATTRIBUTES,
height_levels=[95000, 100000],
vertical_levels=[95000, 100000],
pressure=True,
)
result = HumidityMixingRatio()([temperature, rel_humidity])
Expand Down
4 changes: 2 additions & 2 deletions improver_tests/standardise/test_StandardiseMetadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def test_air_temperature_status_flag_coord(self):
spatial_grid="latlon",
standard_grid_metadata="gl_det",
pressure=True,
height_levels=[100000.0, 97500.0, 95000.0],
vertical_levels=[100000.0, 97500.0, 95000.0],
realizations=[0, 18, 19],
)
# The target cube has 'NaN' values in its data to denote points below
Expand Down Expand Up @@ -266,7 +266,7 @@ def test_air_temperature_status_flag_coord_without_realization(self):
spatial_grid="latlon",
standard_grid_metadata="gl_det",
pressure=True,
height_levels=[100000.0, 97500.0, 95000.0],
vertical_levels=[100000.0, 97500.0, 95000.0],
)
# The target cube has 'NaN' values in its data to denote points below
# surface altitude.
Expand Down
Loading