Skip to content

Commit

Permalink
SnowSplitter output cube names are now the same as their StaGE couter…
Browse files Browse the repository at this point in the history
…parts.
  • Loading branch information
MoseleyS committed Jan 5, 2024
1 parent 6f1593a commit dd2210b
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 19 deletions.
12 changes: 8 additions & 4 deletions improver/precipitation_type/snow_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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]
)
Expand Down
102 changes: 87 additions & 15 deletions improver_tests/precipitation_type/snow_splitter/test_snow_splitter.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -103,29 +142,62 @@ 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"""

rain_cube.data = np.full_like(rain_cube.data, 1)
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])
)

0 comments on commit dd2210b

Please sign in to comment.