From dd2210bf86aeb51efd74024c0d374b655fab63ef Mon Sep 17 00:00:00 2001 From: Stephen Moseley Date: Fri, 5 Jan 2024 16:54:16 +0000 Subject: [PATCH] SnowSplitter output cube names are now the same as their StaGE couterparts. --- improver/precipitation_type/snow_splitter.py | 12 ++- .../snow_splitter/test_snow_splitter.py | 102 +++++++++++++++--- 2 files changed, 95 insertions(+), 19 deletions(-) diff --git a/improver/precipitation_type/snow_splitter.py b/improver/precipitation_type/snow_splitter.py index a79ea8ba51..2e64e4aab4 100644 --- a/improver/precipitation_type/snow_splitter.py +++ b/improver/precipitation_type/snow_splitter.py @@ -120,7 +120,9 @@ def process(self, cubes: CubeList,) -> Cube: Returns: Cube of rain/snow (depending on self.output_is_rain) rate/accumulation (depending on - precipitation cube) + precipitation cube). The name will be an updated version of precip_cube.name(). + "precipitation" will be replaced with "rainfall" or "snowfall" and "lwe_" will be + removed for rain output. Raises: ValueError: If, at some grid square, both snow_cube and rain_cube have a probability of @@ -140,18 +142,20 @@ def process(self, cubes: CubeList,) -> Cube: if self.output_is_rain: required_cube = rain_cube other_cube = snow_cube - name = "rain" + name = "rainfall" else: required_cube = snow_cube other_cube = rain_cube - name = "lwe_snow" + name = "snowfall" # arbitrary function that maps combinations of rain and snow probabilities # to an appropriate coefficient coefficient_cube = (required_cube - other_cube + 1) / 2 coefficient_cube.data = coefficient_cube.data.astype(np.float32) - new_name = precip_cube.name().replace("lwe_precipitation", name) + new_name = precip_cube.name().replace("precipitation", name) + if self.output_is_rain: + new_name = new_name.replace("lwe_", "") output_cube = Combine(operation="*", new_name=new_name)( [precip_cube, coefficient_cube] ) diff --git a/improver_tests/precipitation_type/snow_splitter/test_snow_splitter.py b/improver_tests/precipitation_type/snow_splitter/test_snow_splitter.py index 14676cd7cd..64b0014c29 100644 --- a/improver_tests/precipitation_type/snow_splitter/test_snow_splitter.py +++ b/improver_tests/precipitation_type/snow_splitter/test_snow_splitter.py @@ -29,6 +29,8 @@ # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. """Tests for the SnowSplitter plugin""" +from datetime import datetime + import numpy as np import pytest from iris.cube import Cube, CubeList @@ -82,14 +84,51 @@ def precip_rate_cube() -> Cube: return precip_cube +@pytest.fixture() +def precip_acc_cube() -> Cube: + """Set up a r, y, x cube of precipitation accumulation""" + data = np.full((2, 2, 2), fill_value=1, dtype=np.float32) + precip_cube = set_up_variable_cube( + data, + name="lwe_thickness_of_precipitation_amount", + units="m/s", + time_bounds=(datetime(2017, 11, 10, 3, 0), datetime(2017, 11, 10, 4, 0)), + attributes=LOCAL_MANDATORY_ATTRIBUTES, + ) + return precip_cube + + +def run_test( + cube_name, + expected, + output_is_rain, + precip_cube, + rain_cube, + rain_value, + snow_cube, + snow_value, +): + rain_cube.data = np.full_like(rain_cube.data, rain_value) + snow_cube.data = np.full_like(snow_cube.data, snow_value) + result = SnowSplitter(output_is_rain=output_is_rain)( + CubeList([snow_cube, rain_cube, precip_cube]) + ) + if expected == "dependent": + expected = rain_value if output_is_rain else snow_value + assert np.isclose(result.data, expected).all() + assert result.name() == cube_name + assert result.units == "m/s" + assert result.attributes == LOCAL_MANDATORY_ATTRIBUTES + + @pytest.mark.parametrize( - "output_is_rain,cube_name", ((True, "rain_rate"), (False, "lwe_snow_rate")) + "output_is_rain,cube_name", ((True, "rainfall_rate"), (False, "lwe_snowfall_rate")) ) @pytest.mark.parametrize( "rain_value,snow_value,expected", ((0, 0, 0.5), (0, 1, "dependent"), (1, 0, "dependent")), ) -def test_basic( +def test_rates( snow_cube, rain_cube, precip_rate_cube, @@ -103,23 +142,56 @@ def test_basic( rain/snow rate is returned. The correct output will sometimes depend on whether the output_is_rain is True or False. Also check the name of the returned cube has been updated correctly""" - rain_cube.data = np.full_like(rain_cube.data, rain_value) - snow_cube.data = np.full_like(snow_cube.data, snow_value) - - result = SnowSplitter(output_is_rain=output_is_rain)( - CubeList([snow_cube, rain_cube, precip_rate_cube]) + run_test( + cube_name, + expected, + output_is_rain, + precip_rate_cube, + rain_cube, + rain_value, + snow_cube, + snow_value, ) - if expected == "dependent": - expected = rain_value if output_is_rain else snow_value - assert np.isclose(result.data, expected).all() - assert result.name() == cube_name - assert result.units == "m/s" - assert result.attributes == LOCAL_MANDATORY_ATTRIBUTES +@pytest.mark.parametrize( + "output_is_rain,cube_name", + ( + (True, "thickness_of_rainfall_amount"), + (False, "lwe_thickness_of_snowfall_amount"), + ), +) +@pytest.mark.parametrize( + "rain_value,snow_value,expected", + ((0, 0, 0.5), (0, 1, "dependent"), (1, 0, "dependent")), +) +def test_accumulations( + snow_cube, + rain_cube, + precip_acc_cube, + snow_value, + rain_value, + output_is_rain, + cube_name, + expected, +): + """Check that for all possible combinations of rain and snow probabilities the correct + rain/snow rate is returned. The correct output will sometimes depend on whether the + output_is_rain is True or False. Also check the name of the returned cube has been + updated correctly""" + run_test( + cube_name, + expected, + output_is_rain, + precip_acc_cube, + rain_cube, + rain_value, + snow_cube, + snow_value, + ) -def test_both_phases_1(snow_cube, rain_cube, precip_rate_cube): +def test_both_phases_1(snow_cube, rain_cube, precip_cube): """Test an error is raised if both snow and rain_cube have a probability of 1""" @@ -127,5 +199,5 @@ def test_both_phases_1(snow_cube, rain_cube, precip_rate_cube): snow_cube.data = np.full_like(snow_cube.data, 1) with pytest.raises(ValueError, match="1 grid square where the probability of snow"): SnowSplitter(output_is_rain=False)( - CubeList([snow_cube, rain_cube, precip_rate_cube]) + CubeList([snow_cube, rain_cube, precip_cube]) )