From 0419260a48a1c654489ec5c75574b3b157f3ad15 Mon Sep 17 00:00:00 2001 From: bayliffe Date: Fri, 3 Nov 2023 14:07:49 +0000 Subject: [PATCH] Merge #1959 into 1.6.x branch (#1963) * Loosen dz scaling application to use the longest forecast period scaling coefficient available if no forecast period has a greater length than the forecast itself. * Adjustments to doc-strings as per review. --- improver/calibration/dz_rescaling.py | 18 ++++++++---------- improver/cli/apply_dz_rescaling.py | 6 ++++-- .../dz_rescaling/test_apply_dz_rescaling.py | 18 ++++++++++-------- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/improver/calibration/dz_rescaling.py b/improver/calibration/dz_rescaling.py index 74abf125c1..d7730e7f63 100644 --- a/improver/calibration/dz_rescaling.py +++ b/improver/calibration/dz_rescaling.py @@ -373,7 +373,9 @@ def _create_forecast_period_constraint( """Create a forecast period constraint to identify the most appropriate forecast period from the scaled_dz to extract. The most appropriate scaled dz is selected by choosing the nearest forecast period that is greater than or - equal to the forecast period of the forecast. + equal to the forecast period of the forecast. If no forecast periods in the + scaled dz cube are greater than the forecast period of the forecast, the + longest available forecast period is used. Args: forecast: Forecast to be adjusted using dz rescaling. @@ -391,16 +393,12 @@ def _create_forecast_period_constraint( - forecast.coord("forecast_period").points ) - if not any(fp_diff >= 0): - (fp_hour,) = forecast.coord("forecast_period").points / SECONDS_IN_HOUR - msg = ( - "There is no scaled version of the difference in altitude for " - f"a forecast period greater than or equal to {fp_hour}" - ) - raise ValueError(msg) + if any(fp_diff >= 0): + fp_index = np.argmax(fp_diff >= 0) + chosen_fp = scaled_dz.coord("forecast_period").points[fp_index] + else: + chosen_fp = scaled_dz.coord("forecast_period").points[-1] - fp_index = np.argmax(fp_diff >= 0) - chosen_fp = scaled_dz.coord("forecast_period").points[fp_index] return iris.Constraint(forecast_period=chosen_fp) @staticmethod diff --git a/improver/cli/apply_dz_rescaling.py b/improver/cli/apply_dz_rescaling.py index 41542a6c39..3bd6b51bda 100755 --- a/improver/cli/apply_dz_rescaling.py +++ b/improver/cli/apply_dz_rescaling.py @@ -56,8 +56,10 @@ def process( 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. + equal to the forecast period of the forecast. However, if the forecast + period of the forecast exceeds all forecast periods within the rescaling + cube, the scaling factor from the maximum forecast period is used. + 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'. diff --git a/improver_tests/calibration/dz_rescaling/test_apply_dz_rescaling.py b/improver_tests/calibration/dz_rescaling/test_apply_dz_rescaling.py index e326ed5d29..3002e4c464 100644 --- a/improver_tests/calibration/dz_rescaling/test_apply_dz_rescaling.py +++ b/improver_tests/calibration/dz_rescaling/test_apply_dz_rescaling.py @@ -158,7 +158,7 @@ def _create_scaling_factor_cube( @pytest.mark.parametrize("wmo_id", [True, False]) -@pytest.mark.parametrize("forecast_period", [6, 18]) +@pytest.mark.parametrize("forecast_period", [6, 18, 30]) @pytest.mark.parametrize("frt_hour", [3, 12]) @pytest.mark.parametrize("scaling_factor", [0.99, 1.01]) @pytest.mark.parametrize("forecast_period_offset", [0, -1, -5]) @@ -177,9 +177,11 @@ def test_apply_dz_rescaling( contains the scaling_factor value. forecast_period_offset (hours) adjusts the forecast period coord on the forecast cube to ensure the plugin always snaps to the next largest forecast_time when the - precise point is not available. + precise point is not available except when the forecast period of the forecast + exceeds all forecast periods within the scaling factor cube. In this case, the + last forecast period within the scaling factor cube will be used. frt_hour_offset (hours) alters the forecast reference time hour within the forecast - whilst the forececast reference time hour of the scaling factor remains the same. + whilst the forecast reference time hour of the scaling factor remains the same. This checks that the a mismatch in the forecast reference time hour can still result in a match, if a leniency is specified. """ @@ -199,8 +201,11 @@ def test_apply_dz_rescaling( forecast_period + forecast_period_offset, forecast, ) + # Use min(fp, 24) here to ensure that the scaling cube contains + # the scaling factor for the last forecast_period if the specified + # forecast period is beyond the T+24 limit of the scaling cube. scaling_factor = _create_scaling_factor_cube( - frt_hour, forecast_period, scaling_factor + frt_hour, min(forecast_period, 24), scaling_factor ) kwargs = {} @@ -275,10 +280,7 @@ def test_mismatching_sites(): @pytest.mark.parametrize( "forecast_period,frt_hour,exception", - [ - (25, 3, "forecast period greater than or equal to 25"), - (7, 1, "forecast reference time hour equal to 1"), - ], + [(7, 1, "forecast reference time hour equal to 1")], ) def test_no_appropriate_scaled_dz(forecast_period, frt_hour, exception): """Test an exception is raised if no appropriate scaled version of the difference