diff --git a/esmvalcore/cmor/tables/custom/CMOR_siextent.dat b/esmvalcore/cmor/tables/custom/CMOR_siextent.dat index 736744595f..5dc87ae84a 100644 --- a/esmvalcore/cmor/tables/custom/CMOR_siextent.dat +++ b/esmvalcore/cmor/tables/custom/CMOR_siextent.dat @@ -7,7 +7,7 @@ modeling_realm: seaIce ! Variable attributes: !---------------------------------- standard_name: -units: m2 +units: 1 cell_methods: area: mean where sea time: mean cell_measures: area: areacello long_name: Sea Ice Extent diff --git a/esmvalcore/preprocessor/_derive/siextent.py b/esmvalcore/preprocessor/_derive/siextent.py index b12ffc24a5..c729116088 100644 --- a/esmvalcore/preprocessor/_derive/siextent.py +++ b/esmvalcore/preprocessor/_derive/siextent.py @@ -1,4 +1,4 @@ -"""Derivation of variable `sithick`.""" +"""Create mask for derivation of variable `siextent`.""" import logging @@ -14,15 +14,15 @@ class DerivedVariable(DerivedVariableBase): - """Derivation of variable `siextent`.""" + """Create mask for derivation of variable `siextent`.""" @staticmethod def required(project): - """Declare the variables needed for derivation.""" - required = [ - {"short_name": "sic", "optional": "true"}, - {"short_name": "siconca", "optional": "true"}, - ] + """Declare the variable needed for derivation.""" + # 'sic' is sufficient as there is already an entry + # in the mapping table esmvalcore/cmor/variable_alt_names.yml + required = [{"short_name": "sic"}] + return required @staticmethod @@ -30,11 +30,11 @@ def calculate(cubes): """Compute sea ice extent. Returns an array of ones in every grid point where - the sea ice area fraction has values > 15 . + the sea ice area fraction has values > 15% . Use in combination with the preprocessor `area_statistics(operator='sum')` to weigh by the area and - compute global or regional sea ice extent values. + compute global or regional sea ice extent values (in m2). Arguments --------- @@ -48,16 +48,19 @@ def calculate(cubes): sic = cubes.extract_cube(Constraint(name="sic")) except iris.exceptions.ConstraintMismatchError: try: - sic = cubes.extract_cube(Constraint(name="siconca")) + sic = cubes.extract_cube(Constraint(name="siconc")) except iris.exceptions.ConstraintMismatchError as exc: raise RecipeError( "Derivation of siextent failed due to missing variables " - "sic and siconca." + "sic and siconc." ) from exc ones = da.ones_like(sic) siextent_data = da.ma.masked_where(sic.lazy_data() < 15.0, ones) siextent = sic.copy(siextent_data) - siextent.units = "m2" + # unit is 1 as this is just a mask that has to be used with + # preprocessor area_statistics(operator='sum') to obtain the + # sea ice extent (m2) + siextent.units = "1" return siextent diff --git a/tests/unit/preprocessor/_derive/test_siextent.py b/tests/unit/preprocessor/_derive/test_siextent.py index 576a678daa..65def5b88d 100644 --- a/tests/unit/preprocessor/_derive/test_siextent.py +++ b/tests/unit/preprocessor/_derive/test_siextent.py @@ -31,38 +31,17 @@ def cubes_siconca(): [[[20, 10], [10, 10]], [[10, 10], [10, 10]], [[10, 10], [10, 10]]], units="%", standard_name=sic_name, - var_name="siconca", + var_name="siconc", dim_coords_and_dims=[(time_coord, 0)], ) return iris.cube.CubeList([sic_cube]) -@pytest.fixture -def cubes(): - sic_name = "sea_ice_area_fraction" - time_coord = iris.coords.DimCoord([0.0, 1.0, 2.0], standard_name="time") - sic_cube = iris.cube.Cube( - [[[20, 10], [10, 10]], [[10, 10], [10, 10]], [[10, 10], [10, 10]]], - units="%", - standard_name=sic_name, - var_name="sic", - dim_coords_and_dims=[(time_coord, 0)], - ) - siconca_cube = iris.cube.Cube( - [[[20, 10], [10, 10]], [[10, 10], [10, 10]], [[10, 10], [10, 10]]], - units="%", - standard_name=sic_name, - var_name="siconca", - dim_coords_and_dims=[(time_coord, 0)], - ) - return iris.cube.CubeList([sic_cube, siconca_cube]) - - def test_siextent_calculation_sic(cubes_sic): """Test function ``calculate`` when sic is available.""" derived_var = siextent.DerivedVariable() out_cube = derived_var.calculate(cubes_sic) - assert out_cube.units == cf_units.Unit("m2") + assert out_cube.units == cf_units.Unit("1") out_data = out_cube.data expected = np.ma.ones_like(cubes_sic[0].data) expected.mask = True @@ -71,11 +50,11 @@ def test_siextent_calculation_sic(cubes_sic): np.testing.assert_array_equal(out_data[0][0][0], expected[0][0][0]) -def test_siextent_calculation_siconca(cubes_siconca): - """Test function ``calculate`` when siconca is available.""" +def test_siextent_calculation_siconc(cubes_siconca): + """Test function ``calculate`` when siconc is available.""" derived_var = siextent.DerivedVariable() out_cube = derived_var.calculate(cubes_siconca) - assert out_cube.units == cf_units.Unit("m2") + assert out_cube.units == cf_units.Unit("1") out_data = out_cube.data expected = np.ma.ones_like(cubes_siconca[0].data) expected.mask = True @@ -84,25 +63,12 @@ def test_siextent_calculation_siconca(cubes_siconca): np.testing.assert_array_equal(out_data[0][0][0], expected[0][0][0]) -def test_siextent_calculation(cubes): - """Test function ``calculate`` when sic and siconca are available.""" - derived_var = siextent.DerivedVariable() - out_cube = derived_var.calculate(cubes) - assert out_cube.units == cf_units.Unit("m2") - out_data = out_cube.data - expected = np.ma.ones_like(cubes[0].data) - expected.mask = True - expected[0][0][0] = 1.0 - np.testing.assert_array_equal(out_data.mask, expected.mask) - np.testing.assert_array_equal(out_data[0][0][0], expected[0][0][0]) - - def test_siextent_no_data(cubes_sic): derived_var = siextent.DerivedVariable() cubes_sic[0].var_name = "wrong" msg = ( "Derivation of siextent failed due to missing variables " - "sic and siconca." + "sic and siconc." ) with pytest.raises(RecipeError, match=msg): derived_var.calculate(cubes_sic) @@ -113,6 +79,7 @@ def test_siextent_required(): derived_var = siextent.DerivedVariable() output = derived_var.required(None) assert output == [ - {"short_name": "sic", "optional": "true"}, - {"short_name": "siconca", "optional": "true"}, + { + "short_name": "sic", + } ]