Skip to content

Commit

Permalink
Add CLIs to support rescaling of the forecast based on altitude diffe…
Browse files Browse the repository at this point in the history
…rence (#1917)

* Add CLIs and acceptance tests. Make minor update to correct dtype.

* Corrections to estimate-dz-rescaling CLI docstrings.

* Updates to docstrings following review comments.

* Updates to docstrings within the estimate-dz-rescaling CLI.
  • Loading branch information
gavinevans authored Jul 13, 2023
1 parent 57a1003 commit 8b218e0
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 0 deletions.
1 change: 1 addition & 0 deletions improver/calibration/dz_rescaling.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ def _create_hour_coord(self, source_cube: Cube, target_cube: Cube):
np.array(frt_hour, np.int32), long_name=f"{coord_name}_hour", units="hours",
)
hour_coord.convert_units("seconds")
hour_coord.points = hour_coord.points.astype(np.int32)
target_cube.add_aux_coord(hour_coord)

def process(self, forecasts: Cube, truths: Cube, neighbour_cube: Cube) -> Cube:
Expand Down
72 changes: 72 additions & 0 deletions improver/cli/apply_dz_rescaling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown copyright. The Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""CLI to apply a scaling factor to account for a correction linked to the
difference in altitude between the grid point and the site location."""

from improver import cli


@cli.clizefy
@cli.with_output
def process(
forecast: cli.inputcube,
scaling_factor: cli.inputcube,
*,
site_id_coord: str = "wmo_id",
):
"""Apply a scaling factor to account for a correction linked to the difference
in altitude between the grid point and the site location.
Args:
forecast (iris.cube.Cube):
Percentile forecasts.
rescaling_cube (iris.cube.Cube):
Multiplicative scaling factor to adjust the percentile forecasts.
This cube is expected to contain multiple values for the forecast_period
and forecast_reference_time_hour coordinates. The most appropriate
forecast period and forecast reference_time_hour pair within the
rescaling cube are chosen using the forecast reference time hour from
the forecast and the nearest forecast period that is greater than or
equal to the forecast period of the forecast. This cube is generated
using the estimate_dz_rescaling CLI.
site_id_coord (str):
The name of the site ID coordinate. This defaults to 'wmo_id'.
Returns:
iris.cube.Cube:
Percentile forecasts that have been rescaled to account for a difference
in altitude between the grid point and the site location.
"""

from improver.calibration.dz_rescaling import ApplyDzRescaling

return ApplyDzRescaling(site_id_coord=site_id_coord)(forecast, scaling_factor)
114 changes: 114 additions & 0 deletions improver/cli/estimate_dz_rescaling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown copyright. The Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""CLI to estimate a scaling factor to account for a correction linked to the
difference in altitude between the grid point and the site location."""

from improver import cli


@cli.clizefy
@cli.with_output
def process(
forecast: cli.inputcube,
truth: cli.inputcube,
neighbour_cube: cli.inputcube,
*,
forecast_period: int,
dz_lower_bound: float = None,
dz_upper_bound: float = None,
land_constraint: bool = False,
similar_altitude: bool = False,
site_id_coord: str = "wmo_id",
):
"""Estimate a scaling factor to account for a correction linked to the difference
in altitude between the grid point and the site location.
Args:
forecast (iris.cube.Cube):
Historical percentile forecasts. A 50th percentile forecast is required.
truth (iris.cube.Cube):
Truths that are expected to match the validity times of the forecasts.
neighbour_cube (iris.cube.Cube):
The neighbour cube is a cube of spot-data neighbours and
the spot site information.
forecast_period (int):
The forecast period that is considered representative of
the input forecasts. This is required as the input forecasts could
contain multiple forecast periods.
dz_lower_bound (float):
The lowest acceptable value for the difference in altitude
between the grid point and the site. Sites with a lower
(or more negative) difference in altitude will be excluded.
If the altitude difference is calculated as site altitude minus the
grid point altitude, the lower bound therefore indicates how far below
the orographic surface (i.e. within unresolved valleys) the correction
should be applied. Defaults to None.
dz_upper_bound (float):
The highest acceptable value for the difference in altitude
between the grid point and the site. Sites with a larger
positive difference in altitude will be excluded.
If the altitude difference is calculated as site altitude minus the
grid point altitude, the upper bound therefore indicates how far above
the orographic surface (i.e. on unresolved hills or mountains) the
correction should be applied. Defaults to None.
land_constraint (bool):
Use to select the nearest-with-land-constraint neighbour-selection
method from the neighbour_cube. This means that the grid points
should be land points except for sites where none were found within
the search radius when the neighbour cube was created. May be used
with similar_altitude.
similar_altitude (bool):
Use to select the nearest-with-height-constraint neighbour-selection
method from the neighbour_cube. These are grid points that were found
to be the closest in altitude to the spot site within the search radius
defined when the neighbour cube was created. May be used with
land_constraint.
site_id_coord (str):
The name of the site ID coordinate. This defaults to 'wmo_id'.
Returns:
iris.cube.Cube:
Cube containing a scaling factor computed using the difference
in altitude between the grid point and the site location.
"""

from improver.calibration.dz_rescaling import EstimateDzRescaling

plugin = EstimateDzRescaling(
forecast_period=forecast_period,
dz_lower_bound=dz_lower_bound,
dz_upper_bound=dz_upper_bound,
land_constraint=land_constraint,
similar_altitude=similar_altitude,
site_id_coord=site_id_coord,
)
return plugin(forecast, truth, neighbour_cube)
7 changes: 7 additions & 0 deletions improver_tests/acceptance/SHA256SUMS
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ fbe539f8793d944c55f4c27ebead909787a8e260002f625c773b85c79ee77bab ./apply-bias-c
2ba4874e063eaddaacf9c4e5b84f9ef8f2ce18d58002daf7eaa3e1c8ead18183 ./apply-bias-correction/single_bias_file/bias_data/20220813T0300Z-PT0003H00M-wind_speed_at_10m.nc
591bad70882d35c0a4fa75ae31903d4dcf72ff384731cc2ca23f311b859155c3 ./apply-bias-correction/single_bias_file/kgo.nc
33a5f82c499c8059afb3a64193d58ffb75f40183447af0608f67ad2daf19483b ./apply-bias-correction/with_bounds/kgo.nc
e50a8cdbb61ffc505e87ca15ed6b1c4981870d9b28d688e0e7502c7b1b3a75ce ./apply-dz-rescaling/20230220T1200Z-PT0006H00M-wind_speed_at_10m.nc
f8243f70ba344d219c79a832f399d2cf31b7d06fe52da159900e8c82abba99a0 ./apply-dz-rescaling/kgo.nc
b339ef0d6eafeb0d334d4b3c15b24da776856796701e7aca25c5de564eb55614 ./apply-dz-rescaling/scaled_dz.nc
52b02f4c6e4adc604d4961ce87b4ada5985d07eac1d3c703cbab985de223228f ./apply-emos-coefficients/alternative_percentiles/kgo.nc
ff3a00a16fa94697d6e529a6f98384e4602f70a7d6ce26669c3947c8ac2e6f7e ./apply-emos-coefficients/land_sea/landmask.nc
bee125371a9db4c80fb7f8d1b0644a2710903aad27023b5939331f7ec5f3b617 ./apply-emos-coefficients/land_sea/percentiles_kgo.nc
Expand Down Expand Up @@ -203,6 +206,10 @@ e210bf956dd3574eda3a64fdc3371ab16f85048ca120a3823e90d751d2325c46 ./enforce-cons
5f7fa625272af0a51f6a59dd20a6f75cb53aa46299630783c32a77242c607313 ./enforce-consistent-forecasts/single_bound_percentile_kgo.nc
7e435c2db5e792b71bb5170140fbe1e37cc03d96bd88f73ce7196f9469ba3e13 ./enforce-consistent-forecasts/single_bound_probability_kgo.nc
09eb8d42b1df18831660bfebef46a09436e66946a48488d8be89157e8e3666ef ./enforce-consistent-forecasts/single_bound_realization_kgo.nc
286b0c915126655d37ff23d32730ba8b06954020dd4747adb44e7ced848dcc16 ./estimate-dz-rescaling/T1200Z-PT0006H00M-wind_speed_at_10m.nc
3dea94d8c5461224224f6b9d6f3568606cbfaa1a8c59cfcb92e61452c3e24c90 ./estimate-dz-rescaling/T1200Z-srfc_wind_sped_spot_truths.nc
ca912d16879f7601283529e3404a4ac312f1cd6b8fd071af9cd4eaa43cb00284 ./estimate-dz-rescaling/kgo.nc
a469208a1ebfef59f4d9d3736926744ad988e1f32e6ccf2b20dd4031a3f119ff ./estimate-dz-rescaling/neighbour.nc
9b48f3eabeed93a90654a5e6a2f06fcb7297cdba40f650551d99a5e310cf27dd ./estimate-emos-coefficients-from-table/altitude.nc
93e0c05333bc93ca252027ee5e2da12ee93fac5bbff4bab00b9c334ad83141e2 ./estimate-emos-coefficients-from-table/forecast_table/_common_metadata
037e1cfc55c703a30cdbc1dee78da47020a598683247dc29a8ef88e65b53dfb1 ./estimate-emos-coefficients-from-table/forecast_table/_metadata
Expand Down
58 changes: 58 additions & 0 deletions improver_tests/acceptance/test_apply_dz_rescaling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown copyright. The Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Tests for the apply-dz-rescaling CLI
"""

import pytest

from . import acceptance as acc

pytestmark = [pytest.mark.acc, acc.skip_if_kgo_missing]
CLI = acc.cli_name_with_dashes(__file__)
run_cli = acc.run_cli(CLI)


def test_apply_dz_rescaling(tmp_path):
"""Test apply_dz_rescaling CLI."""
kgo_dir = acc.kgo_root() / "apply-dz-rescaling/"
kgo_path = kgo_dir / "kgo.nc"
forecast_path = kgo_dir / "20230220T1200Z-PT0006H00M-wind_speed_at_10m.nc"
scaled_dz_path = kgo_dir / "scaled_dz.nc"
output_path = tmp_path / "output.nc"
args = [
forecast_path,
scaled_dz_path,
"--output",
output_path,
]
run_cli(args)
acc.compare(output_path, kgo_path)
67 changes: 67 additions & 0 deletions improver_tests/acceptance/test_estimate_dz_rescaling.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# -*- coding: utf-8 -*-
# -----------------------------------------------------------------------------
# (C) British Crown copyright. The Met Office.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice, this
# list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of the copyright holder nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
"""
Tests for the estimate-dz-rescaling CLI
"""

import pytest

from . import acceptance as acc

pytestmark = [pytest.mark.acc, acc.skip_if_kgo_missing]
CLI = acc.cli_name_with_dashes(__file__)
run_cli = acc.run_cli(CLI)


def test_estimate_dz_rescaling(tmp_path):
"""Test estimate_dz_rescaling CLI."""
kgo_dir = acc.kgo_root() / "estimate-dz-rescaling/"
kgo_path = kgo_dir / "kgo.nc"
forecast_path = kgo_dir / "T1200Z-PT0006H00M-wind_speed_at_10m.nc"
truth_path = kgo_dir / "T1200Z-srfc_wind_sped_spot_truths.nc"
neighbour_path = kgo_dir / "neighbour.nc"
output_path = tmp_path / "output.nc"
args = [
forecast_path,
truth_path,
neighbour_path,
"--forecast-period",
"6",
"--dz-lower-bound",
"-550",
"--dz-upper-bound",
"550",
"--land-constraint",
"--output",
output_path,
]
run_cli(args)
acc.compare(output_path, kgo_path)
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,5 @@ def test_estimate_dz_rescaling(
assert result.units == "1"
assert result.coord("forecast_reference_time_hour").points == 0
assert result.coord("forecast_reference_time_hour").units == "seconds"
assert result.coord("forecast_reference_time_hour").points.dtype == np.int32
np.testing.assert_allclose(result.data, expected_data, atol=1e-4, rtol=1e-4)

0 comments on commit 8b218e0

Please sign in to comment.