From 3f2cd5c55d70eea9d2ad5b7790e213f36b5ceaa5 Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Thu, 7 Nov 2024 15:44:32 +0100 Subject: [PATCH 01/10] fixed derivation script for siextent --- .../cmor/tables/custom/CMOR_siextent.dat | 2 +- esmvalcore/preprocessor/_derive/siextent.py | 25 +++++++++++-------- 2 files changed, 16 insertions(+), 11 deletions(-) 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..516f4241f5 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,14 +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.""" + """Declare the variable needed for derivation.""" + # 'sic' only is sufficient as there is already an entry + # in the mapping table esmvalcore/cmor/variable_alt_names.yml required = [ - {"short_name": "sic", "optional": "true"}, - {"short_name": "siconca", "optional": "true"}, + {"short_name": "sic"}, ] return required @@ -30,11 +31,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 --------- @@ -44,20 +45,24 @@ def calculate(cubes): ------- Cube containing sea ice extent. """ + print(cubes) try: 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" + siextent.units = "1" # 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) return siextent From 0373a94b828588847e3531e7381b71f1f73d95e0 Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Tue, 28 Jan 2025 14:56:16 +0100 Subject: [PATCH 02/10] Update siextent.py --- esmvalcore/preprocessor/_derive/siextent.py | 1 - 1 file changed, 1 deletion(-) diff --git a/esmvalcore/preprocessor/_derive/siextent.py b/esmvalcore/preprocessor/_derive/siextent.py index 516f4241f5..c4db32d4e8 100644 --- a/esmvalcore/preprocessor/_derive/siextent.py +++ b/esmvalcore/preprocessor/_derive/siextent.py @@ -45,7 +45,6 @@ def calculate(cubes): ------- Cube containing sea ice extent. """ - print(cubes) try: sic = cubes.extract_cube(Constraint(name="sic")) except iris.exceptions.ConstraintMismatchError: From 7a1be2fa3e81659e7e40fa7b3eee75fd9d8583bd Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Thu, 30 Jan 2025 14:26:59 +0100 Subject: [PATCH 03/10] moved comments above statement --- esmvalcore/preprocessor/_derive/siextent.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/esmvalcore/preprocessor/_derive/siextent.py b/esmvalcore/preprocessor/_derive/siextent.py index c4db32d4e8..52bbfa52d8 100644 --- a/esmvalcore/preprocessor/_derive/siextent.py +++ b/esmvalcore/preprocessor/_derive/siextent.py @@ -59,9 +59,9 @@ def calculate(cubes): ones = da.ones_like(sic) siextent_data = da.ma.masked_where(sic.lazy_data() < 15.0, ones) siextent = sic.copy(siextent_data) - siextent.units = "1" # 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) + # 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 From 2a949ca215922aedbb678d2935ab2efb1ad288f5 Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Thu, 30 Jan 2025 14:31:40 +0100 Subject: [PATCH 04/10] removed trailing whitespace --- esmvalcore/preprocessor/_derive/siextent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/esmvalcore/preprocessor/_derive/siextent.py b/esmvalcore/preprocessor/_derive/siextent.py index 52bbfa52d8..6bbf3064cc 100644 --- a/esmvalcore/preprocessor/_derive/siextent.py +++ b/esmvalcore/preprocessor/_derive/siextent.py @@ -19,7 +19,7 @@ class DerivedVariable(DerivedVariableBase): @staticmethod def required(project): """Declare the variable needed for derivation.""" - # 'sic' only is sufficient as there is already an entry + # 'sic' only is sufficient as there is already an entry # in the mapping table esmvalcore/cmor/variable_alt_names.yml required = [ {"short_name": "sic"}, From eb9cd61ae21f585163227889901c7d0a2e7c039e Mon Sep 17 00:00:00 2001 From: sloosvel Date: Fri, 31 Jan 2025 10:40:56 +0100 Subject: [PATCH 05/10] Fix units in tests --- tests/unit/preprocessor/_derive/test_siextent.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit/preprocessor/_derive/test_siextent.py b/tests/unit/preprocessor/_derive/test_siextent.py index 576a678daa..5d7ab33bd5 100644 --- a/tests/unit/preprocessor/_derive/test_siextent.py +++ b/tests/unit/preprocessor/_derive/test_siextent.py @@ -62,7 +62,7 @@ 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 @@ -75,7 +75,7 @@ def test_siextent_calculation_siconca(cubes_siconca): """Test function ``calculate`` when siconca 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 @@ -88,7 +88,7 @@ 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") + assert out_cube.units == cf_units.Unit("1") out_data = out_cube.data expected = np.ma.ones_like(cubes[0].data) expected.mask = True From 0c8beb935cbd12ac96f36cd08cb1a06fdf083609 Mon Sep 17 00:00:00 2001 From: Axel Lauer Date: Fri, 21 Feb 2025 10:53:29 +0100 Subject: [PATCH 06/10] updated siextent.py --- esmvalcore/preprocessor/_derive/siextent.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/esmvalcore/preprocessor/_derive/siextent.py b/esmvalcore/preprocessor/_derive/siextent.py index 6bbf3064cc..e3c9f3fd13 100644 --- a/esmvalcore/preprocessor/_derive/siextent.py +++ b/esmvalcore/preprocessor/_derive/siextent.py @@ -19,7 +19,7 @@ class DerivedVariable(DerivedVariableBase): @staticmethod def required(project): """Declare the variable needed for derivation.""" - # 'sic' only is sufficient as there is already an entry + # 'sic' is sufficient as there is already an entry # in the mapping table esmvalcore/cmor/variable_alt_names.yml required = [ {"short_name": "sic"}, @@ -50,11 +50,14 @@ def calculate(cubes): except iris.exceptions.ConstraintMismatchError: try: 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 siconc." - ) from exc + except: + try: + sic = cubes.extract_cube(Constraint(name="siconca")) + except iris.exceptions.ConstraintMismatchError as exc: + raise RecipeError( + "Derivation of siextent failed due to missing variables " + "sic and siconc/siconca." + ) from exc ones = da.ones_like(sic) siextent_data = da.ma.masked_where(sic.lazy_data() < 15.0, ones) From 1eaa6f011d15df0e70fcd9396529bf7a3570c2cf Mon Sep 17 00:00:00 2001 From: sloosvel Date: Fri, 21 Feb 2025 15:31:51 +0100 Subject: [PATCH 07/10] Separate variables per project --- esmvalcore/preprocessor/_derive/siextent.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/esmvalcore/preprocessor/_derive/siextent.py b/esmvalcore/preprocessor/_derive/siextent.py index e3c9f3fd13..82b779d111 100644 --- a/esmvalcore/preprocessor/_derive/siextent.py +++ b/esmvalcore/preprocessor/_derive/siextent.py @@ -21,9 +21,14 @@ def required(project): """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"}, - ] + if project == "CMIP5": + required = [{"short_name": "sic"}] + elif project == "CMIP6": + required = [ + {"short_name": "sic", "optional": True}, + {"short_name": "siconca", "optional": True}, + ] + return required @staticmethod @@ -50,7 +55,7 @@ def calculate(cubes): except iris.exceptions.ConstraintMismatchError: try: sic = cubes.extract_cube(Constraint(name="siconc")) - except: + except iris.exceptions.ConstraintMismatchError: try: sic = cubes.extract_cube(Constraint(name="siconca")) except iris.exceptions.ConstraintMismatchError as exc: From f2499bd4d5c61cac8dd7b2f03fb06a25a2b90b06 Mon Sep 17 00:00:00 2001 From: sloosvel Date: Mon, 10 Mar 2025 11:45:49 +0100 Subject: [PATCH 08/10] Do not consider siconca --- esmvalcore/preprocessor/_derive/siextent.py | 21 +++------ .../preprocessor/_derive/test_siextent.py | 47 +++---------------- 2 files changed, 13 insertions(+), 55 deletions(-) diff --git a/esmvalcore/preprocessor/_derive/siextent.py b/esmvalcore/preprocessor/_derive/siextent.py index 82b779d111..c729116088 100644 --- a/esmvalcore/preprocessor/_derive/siextent.py +++ b/esmvalcore/preprocessor/_derive/siextent.py @@ -21,13 +21,7 @@ def required(project): """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 - if project == "CMIP5": - required = [{"short_name": "sic"}] - elif project == "CMIP6": - required = [ - {"short_name": "sic", "optional": True}, - {"short_name": "siconca", "optional": True}, - ] + required = [{"short_name": "sic"}] return required @@ -55,14 +49,11 @@ def calculate(cubes): except iris.exceptions.ConstraintMismatchError: try: sic = cubes.extract_cube(Constraint(name="siconc")) - except iris.exceptions.ConstraintMismatchError: - try: - sic = cubes.extract_cube(Constraint(name="siconca")) - except iris.exceptions.ConstraintMismatchError as exc: - raise RecipeError( - "Derivation of siextent failed due to missing variables " - "sic and siconc/siconca." - ) from exc + except iris.exceptions.ConstraintMismatchError as exc: + raise RecipeError( + "Derivation of siextent failed due to missing variables " + "sic and siconc." + ) from exc ones = da.ones_like(sic) siextent_data = da.ma.masked_where(sic.lazy_data() < 15.0, ones) diff --git a/tests/unit/preprocessor/_derive/test_siextent.py b/tests/unit/preprocessor/_derive/test_siextent.py index 5d7ab33bd5..65def5b88d 100644 --- a/tests/unit/preprocessor/_derive/test_siextent.py +++ b/tests/unit/preprocessor/_derive/test_siextent.py @@ -31,33 +31,12 @@ 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() @@ -71,8 +50,8 @@ 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("1") @@ -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("1") - 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", + } ] From e748701de28ebfafd3680b0ac0d568c26d4e5188 Mon Sep 17 00:00:00 2001 From: sloosvel Date: Fri, 5 Dec 2025 12:13:19 +0100 Subject: [PATCH 09/10] Fix tests --- tests/unit/preprocessor/_derive/test_siextent.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/preprocessor/_derive/test_siextent.py b/tests/unit/preprocessor/_derive/test_siextent.py index 7be76a8640..ebf0317318 100644 --- a/tests/unit/preprocessor/_derive/test_siextent.py +++ b/tests/unit/preprocessor/_derive/test_siextent.py @@ -5,7 +5,6 @@ import numpy as np import pytest -from esmvalcore.exceptions import RecipeError from esmvalcore.preprocessor._derive import siextent @@ -70,7 +69,7 @@ def test_siextent_no_data(cubes_sic): "Derivation of siextent failed due to missing variables " "sic and siconc." ) - with pytest.raises(RecipeError, match=msg): + with pytest.raises(ValueError, match=msg): derived_var.calculate(cubes_sic) From 166e51ec00e6574255bdc855e47bb076b5524b3c Mon Sep 17 00:00:00 2001 From: sloosvel Date: Fri, 5 Dec 2025 12:19:56 +0100 Subject: [PATCH 10/10] Remove mention to siconca --- tests/unit/preprocessor/_derive/test_siextent.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/unit/preprocessor/_derive/test_siextent.py b/tests/unit/preprocessor/_derive/test_siextent.py index ebf0317318..da293c8da6 100644 --- a/tests/unit/preprocessor/_derive/test_siextent.py +++ b/tests/unit/preprocessor/_derive/test_siextent.py @@ -23,7 +23,7 @@ def cubes_sic(): @pytest.fixture -def cubes_siconca(): +def cubes_siconc(): 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( @@ -49,13 +49,13 @@ 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_siconc(cubes_siconca): +def test_siextent_calculation_siconc(cubes_siconc): """Test function ``calculate`` when siconc is available.""" derived_var = siextent.DerivedVariable() - out_cube = derived_var.calculate(cubes_siconca) + out_cube = derived_var.calculate(cubes_siconc) assert out_cube.units == cf_units.Unit("1") out_data = out_cube.data - expected = np.ma.ones_like(cubes_siconca[0].data) + expected = np.ma.ones_like(cubes_siconc[0].data) expected.mask = True expected[0][0][0] = 1.0 np.testing.assert_array_equal(out_data.mask, expected.mask)