Repository: ESMValGroup/ESMValCore

Issue:
Dataset problem: Fixe wrong sign for nbp
**Describe the dataset issue**

- Project: CMIP6
- Full dataset description (fill out as many as you know, please):
  - dataset: SAM0-UNICON, GISS-E2-1-G
  - experiment: historical
  - mip: Lmon
  - grid: gn
  - variable used: nbp
  - data version:
- Problems encountered (please describe what the actual problems are: e.g. wrong standard name, issue with dimensions):
wrong sign of land surface flux
- Pointer to existing copy of data on ESGF node (it would be very useful if you could provide a physical
fullpath to the file(s) that are causing the problem, e.g. on CEDA Jasmin or DKRZ):
DKRZ:
/work/ik1017/CMIP6/data/CMIP6/CMIP/SNU/SAM0-UNICON/historical/r1i1p1f1/Lmon/nbp/gn/v20190323/nbp_Lmon_SAM0-UNICON_historical_r1i1p1f1_gn_198001-198912.nc
/work/ik1017/CMIP6/data/CMIP6/CMIP/NASA-GISS/GISS-E2-1-G/historical/r8i1p5f1/Lmon/nbp/gn/v20190905/nbp_Lmon_GISS-E2-1-G_historical_r8i1p5f1_gn_195101-200012.nc


Discussion:
remi-kazeroni: Tests added here also, made ready for review.
bettina-gier: Which realization did you use for the GISS model? Using r101i1p1f1 I don't see a need for the factor (looks to have the opposite sign of the other models now), I think I originally made this fix for r1i1p1f1 data which has since gotten retracted.
SAM0-UNICON looks fine though.
LisaBock: > Which realization did you use for the GISS model?

I used the ensemble r9i1p5f1 of GISS-E2-1-G, and the ensemble r4i1p5f1of GISS-E2-1-H for creating the land perfmetrics. And  I used this fix but only for the GISS-E2-1-G model. 
By the way, I am not sure if we could fix single ensemble members.
bettina-gier: GISS-E2-1-G r101i1p1f1 looks fine without any dataset fixes, r9i1p5f1 looks like absolute nonsense to me, off by several orders of magnitude, doesn't show any long-term trend etc. I'm not sure what went wrong there. Since different ensemble members look so different, I would suggest removing the fix for the GISS model from this pull request.
valeriupredoi: looks good - cheers @bettina-gier - I am just waiting on @bouweandela to set the main as default on Codacy so we get greens for all tests
zklaus: Would be nice to have a brief description in the PR and to go through the check list.
valeriupredoi: you're a star, Bouwe! Your advice proves useful not only for others but for yourself too :laughing: Cheers, shall we merge this then?
zklaus: Little side comment: Instead of making a dummy commit, next time you can just close and reopen the PR to trigger the checks.
bouweandela: Or click the re-analyze button on Codacy, which I did, but it doesn't help.
remi-kazeroni: Thanks all for your work and comments on this PR! This is good to merge in my opinion 👍 
zklaus: > Or click the re-analyze button on Codacy, which I did, but it doesn't help.

Yes, but that sometimes needs more rights on the codacy project than a user might have. Close/reopen is something every PR owner can do.
valeriupredoi: > Or click the re-analyze button on Codacy, which I did, but it doesn't help.

did that too, I didn't know about closing&reopening but I don't like to close stuff, am a pro-PR guy :grin: 

Patch:
diff --git a/doc/quickstart/install.rst b/doc/quickstart/install.rst
index 3069b2aba..1c78cd38b 100644
--- a/doc/quickstart/install.rst
+++ b/doc/quickstart/install.rst
@@ -203,5 +203,8 @@ You will find the tool available on HPC clusters and there will be no need to in
 yourself if you are just running diagnostics:
 
  - CEDA-JASMIN: `esmvaltool` is available on the scientific compute nodes (`sciX.jasmin.ac.uk` where
-   `X = 1, 2,`3, 4, 5`) after login and module loading via `module load esmvaltool`; see the helper page at
+   `X = 1, 2, 3, 4, 5`) after login and module loading via `module load esmvaltool`; see the helper page at
    `CEDA <https://help.jasmin.ac.uk/article/4955-community-software-esmvaltool>`__ ;
+ - DKRZ-Mistral: `esmvaltool` is available on login nodes (`mistral.dkrz.de`) and pre- and post-processing
+   nodes (`mistralpp.dkrz.de`) after login and module loading via `module load esmvaltool`; the command
+   `module help esmvaltool` provides some information about the module.
diff --git a/doc/recipe/preprocessor.rst b/doc/recipe/preprocessor.rst
index 8664530ba..997149225 100644
--- a/doc/recipe/preprocessor.rst
+++ b/doc/recipe/preprocessor.rst
@@ -1258,9 +1258,9 @@ The area manipulation module contains the following preprocessor functions:
 ``extract_region``
 ------------------
 
-This function masks data outside a rectangular region requested. The boundaries
-of the region are provided as latitude and longitude coordinates in the
-arguments:
+This function returns a subset of the data on the rectangular region requested.
+The boundaries of the region are provided as latitude and longitude coordinates
+in the arguments:
 
 * ``start_longitude``
 * ``end_longitude``
@@ -1270,6 +1270,10 @@ arguments:
 Note that this function can only be used to extract a rectangular region. Use
 ``extract_shape`` to extract any other shaped region from a shapefile.
 
+If the grid is irregular, the returned region retains the original coordinates,
+but is cropped to a rectangular bounding box defined by the start/end
+coordinates. The deselected area inside the region is masked.
+
 See also :func:`esmvalcore.preprocessor.extract_region`.
 
 
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2.py
index 4ab1f0a4b..51bc04685 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2.py
@@ -4,6 +4,7 @@ from shutil import copyfile
 import numpy as np
 from netCDF4 import Dataset
 
+from ..common import SiconcFixScalarCoord
 from ..fix import Fix
 from ..shared import (
     add_scalar_depth_coord,
@@ -12,7 +13,6 @@ from ..shared import (
     add_scalar_typesea_coord,
     fix_ocean_depth_coord,
 )
-from .gfdl_esm4 import Siconc as Addtypesi
 
 
 class Cl(Fix):
@@ -215,6 +215,9 @@ class Sftof(Fix):
         return cubes
 
 
+Siconc = SiconcFixScalarCoord
+
+
 class Tos(Fix):
     """Fixes for tos."""
 
@@ -243,9 +246,6 @@ class Tos(Fix):
         return cubes
 
 
-Siconc = Addtypesi
-
-
 class Omon(Fix):
     """Fixes for ocean variables."""
 
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py
index 74dff9bc7..80f2e5884 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_fv2.py
@@ -1,6 +1,8 @@
 """Fixes for CESM2-FV2 model."""
 from .cesm2 import Cl as BaseCl
+from .cesm2 import Fgco2 as BaseFgco2
 from .cesm2 import Tas as BaseTas
+from ..common import SiconcFixScalarCoord
 
 
 Cl = BaseCl
@@ -12,4 +14,10 @@ Cli = Cl
 Clw = Cl
 
 
+Fgco2 = BaseFgco2
+
+
+Siconc = SiconcFixScalarCoord
+
+
 Tas = BaseTas
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py
index 1bf837884..d0014f308 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm.py
@@ -2,7 +2,9 @@
 from netCDF4 import Dataset
 
 from .cesm2 import Cl as BaseCl
+from .cesm2 import Fgco2 as BaseFgco2
 from .cesm2 import Tas as BaseTas
+from ..common import SiconcFixScalarCoord
 
 
 class Cl(BaseCl):
@@ -47,4 +49,10 @@ Cli = Cl
 Clw = Cl
 
 
+Fgco2 = BaseFgco2
+
+
+Siconc = SiconcFixScalarCoord
+
+
 Tas = BaseTas
diff --git a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py
index cd453d66a..bc8068af8 100644
--- a/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py
+++ b/esmvalcore/cmor/_fixes/cmip6/cesm2_waccm_fv2.py
@@ -1,8 +1,11 @@
 """Fixes for CESM2-WACCM-FV2 model."""
 from .cesm2 import Tas as BaseTas
+from .cesm2 import Fgco2 as BaseFgco2
 from .cesm2_waccm import Cl as BaseCl
 from .cesm2_waccm import Cli as BaseCli
 from .cesm2_waccm import Clw as BaseClw
+from ..common import SiconcFixScalarCoord
+
 
 Cl = BaseCl
 
@@ -13,4 +16,10 @@ Cli = BaseCli
 Clw = BaseClw
 
 
+Fgco2 = BaseFgco2
+
+
+Siconc = SiconcFixScalarCoord
+
+
 Tas = BaseTas
diff --git a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py
index ce61e7fab..35baf68a1 100644
--- a/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py
+++ b/esmvalcore/cmor/_fixes/cmip6/gfdl_cm4.py
@@ -1,7 +1,7 @@
 """Fixes for GFDL-CM4 model."""
 import iris
 
-from ..common import ClFixHybridPressureCoord
+from ..common import ClFixHybridPressureCoord, SiconcFixScalarCoord
 from ..fix import Fix
 from ..shared import add_aux_coords_from_cubes, add_scalar_height_coord
 
@@ -38,6 +38,9 @@ Cli = Cl
 Clw = Cl
 
 
+Siconc = SiconcFixScalarCoord
+
+
 class Tas(Fix):
     """Fixes for tas."""
 
diff --git a/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py b/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py
index 02938907d..c2610a318 100644
--- a/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py
+++ b/esmvalcore/cmor/_fixes/cmip6/gfdl_esm4.py
@@ -1,36 +1,10 @@
 """Fixes for GFDL-ESM4 model."""
-import iris
+from ..common import SiconcFixScalarCoord
 from ..fix import Fix
-from ..shared import (add_scalar_depth_coord, fix_ocean_depth_coord)
-
-
-class Siconc(Fix):
-    """Fixes for siconc."""
-
-    def fix_metadata(self, cubes):
-        """
-        Fix missing type.
-
-        Parameters
-        ----------
-        cubes: iris CubeList
-            List of cubes to fix
-
-        Returns
-        -------
-        iris.cube.CubeList
-
-        """
-        typesi = iris.coords.AuxCoord(
-            'siconc',
-            standard_name='area_type',
-            long_name='Sea Ice area type',
-            var_name='type',
-            units='1',
-            bounds=None)
-        for cube in cubes:
-            cube.add_aux_coord(typesi)
-        return cubes
+from ..shared import (
+    add_scalar_depth_coord,
+    fix_ocean_depth_coord,
+)
 
 
 class Fgco2(Fix):
@@ -76,3 +50,6 @@ class Omon(Fix):
                 if z_coord.standard_name is None:
                     fix_ocean_depth_coord(cube)
         return cubes
+
+
+Siconc = SiconcFixScalarCoord
diff --git a/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py b/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py
index 9f98e319f..518a82e54 100644
--- a/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py
+++ b/esmvalcore/cmor/_fixes/cmip6/kiost_esm.py
@@ -1,4 +1,5 @@
 """Fixes for KIOST-ESM model."""
+from ..common import SiconcFixScalarCoord
 from ..fix import Fix
 from ..shared import add_scalar_height_coord
 
@@ -57,3 +58,6 @@ class Uas(SfcWind):
 
 class Vas(SfcWind):
     """Fixes for vas."""
+
+
+Siconc = SiconcFixScalarCoord
diff --git a/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py
index 696574b9a..dc0aa1ccb 100644
--- a/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py
+++ b/esmvalcore/cmor/_fixes/cmip6/sam0_unicon.py
@@ -1,5 +1,6 @@
 """Fixes for SAM0-UNICON model."""
 from ..common import ClFixHybridPressureCoord
+from ..fix import Fix
 
 
 Cl = ClFixHybridPressureCoord
@@ -9,3 +10,27 @@ Cli = ClFixHybridPressureCoord
 
 
 Clw = ClFixHybridPressureCoord
+
+
+class Nbp(Fix):
+    """Fixes for nbp."""
+
+    def fix_data(self, cube):
+        """Fix data.
+
+        Fixes wrong sign for land surface flux. Tested for v20190323.
+
+        Parameters
+        ----------
+        cube : iris.cube.Cube
+            Input cube.
+
+        Returns
+        -------
+        iris.cube.Cube
+
+        """
+        metadata = cube.metadata
+        cube *= -1
+        cube.metadata = metadata
+        return cube
diff --git a/esmvalcore/cmor/_fixes/common.py b/esmvalcore/cmor/_fixes/common.py
index 41c1d974a..90c64162e 100644
--- a/esmvalcore/cmor/_fixes/common.py
+++ b/esmvalcore/cmor/_fixes/common.py
@@ -6,7 +6,11 @@ import numpy as np
 from scipy.ndimage import map_coordinates
 
 from .fix import Fix
-from .shared import add_plev_from_altitude, fix_bounds
+from .shared import (
+    add_plev_from_altitude,
+    add_scalar_typesi_coord,
+    fix_bounds,
+)
 
 logger = logging.getLogger(__name__)
 
@@ -191,3 +195,24 @@ class OceanFixGrid(Fix):
         cube.coord('longitude').bounds = lon_vertices
 
         return iris.cube.CubeList([cube])
+
+
+class SiconcFixScalarCoord(Fix):
+    """Fixes for siconc."""
+
+    def fix_metadata(self, cubes):
+        """Add typesi coordinate.
+
+        Parameters
+        ----------
+        cubes : iris.cube.CubeList
+            Input cubes.
+
+        Returns
+        -------
+        iris.cube.CubeList
+
+        """
+        cube = self.get_cube_from_list(cubes)
+        add_scalar_typesi_coord(cube)
+        return iris.cube.CubeList([cube])
diff --git a/esmvalcore/cmor/_fixes/shared.py b/esmvalcore/cmor/_fixes/shared.py
index de03d7885..d5c74c095 100644
--- a/esmvalcore/cmor/_fixes/shared.py
+++ b/esmvalcore/cmor/_fixes/shared.py
@@ -361,6 +361,21 @@ def add_scalar_typesea_coord(cube, value='default'):
     return cube
 
 
+def add_scalar_typesi_coord(cube, value='sea_ice'):
+    """Add scalar coordinate 'typesi' with value of `value`."""
+    logger.debug("Adding typesi coordinate (%s)", value)
+    typesi_coord = iris.coords.AuxCoord(value,
+                                        var_name='type',
+                                        standard_name='area_type',
+                                        long_name='Sea Ice area type',
+                                        units=Unit('no unit'))
+    try:
+        cube.coord('area_type')
+    except iris.exceptions.CoordinateNotFoundError:
+        cube.add_aux_coord(typesi_coord, ())
+    return cube
+
+
 def cube_to_aux_coord(cube):
     """Convert cube to iris AuxCoord."""
     return iris.coords.AuxCoord(
diff --git a/esmvalcore/cmor/variable_alt_names.yml b/esmvalcore/cmor/variable_alt_names.yml
index 787a20c99..e0416797e 100644
--- a/esmvalcore/cmor/variable_alt_names.yml
+++ b/esmvalcore/cmor/variable_alt_names.yml
@@ -9,7 +9,6 @@
 ###############################################################################
 ---
 - ['sic', 'siconc']
-- ['sit', 'sithick']
 - ['tro3', 'o3']
 - ['usi', 'siu']
-- ['vsi', 'siv']
\ No newline at end of file
+- ['vsi', 'siv']
diff --git a/esmvalcore/preprocessor/_area.py b/esmvalcore/preprocessor/_area.py
index f3a6a69c7..b3b057a6f 100644
--- a/esmvalcore/preprocessor/_area.py
+++ b/esmvalcore/preprocessor/_area.py
@@ -22,14 +22,12 @@ from ._shared import (
 logger = logging.getLogger(__name__)
 
 
-# slice cube over a restricted area (box)
 def extract_region(cube, start_longitude, end_longitude, start_latitude,
                    end_latitude):
     """Extract a region from a cube.
 
     Function that subsets a cube on a box (start_longitude, end_longitude,
     start_latitude, end_latitude)
-    This function is a restriction of masked_cube_lonlat().
 
     Parameters
     ----------
@@ -63,16 +61,29 @@ def extract_region(cube, start_longitude, end_longitude, start_latitude,
             ignore_bounds=True,
         )
         region_subset = region_subset.intersection(longitude=(0., 360.))
-        return region_subset
-    # Irregular grids
-    lats = cube.coord('latitude').points
-    lons = cube.coord('longitude').points
+    else:
+        region_subset = _extract_irregular_region(
+            cube,
+            start_longitude,
+            end_longitude,
+            start_latitude,
+            end_latitude,
+        )
+    return region_subset
+
+
+def _extract_irregular_region(cube, start_longitude, end_longitude,
+                              start_latitude, end_latitude):
+    """Extract a region from a cube on an irregular grid."""
     # Convert longitudes to valid range
     if start_longitude != 360.:
         start_longitude %= 360.
     if end_longitude != 360.:
         end_longitude %= 360.
 
+    # Select coordinates inside the region
+    lats = cube.coord('latitude').points
+    lons = (cube.coord('longitude').points + 360.) % 360.
     if start_longitude <= end_longitude:
         select_lons = (lons >= start_longitude) & (lons <= end_longitude)
     else:
@@ -84,8 +95,19 @@ def extract_region(cube, start_longitude, end_longitude, start_latitude,
         select_lats = (lats >= start_latitude) | (lats <= end_latitude)
 
     selection = select_lats & select_lons
-    selection = da.broadcast_to(selection, cube.shape)
-    cube.data = da.ma.masked_where(~selection, cube.core_data())
+
+    # Crop the selection, but keep rectangular shape
+    i_range, j_range = selection.nonzero()
+    if i_range.size == 0:
+        raise ValueError("No data points available in selected region")
+    i_min, i_max = i_range.min(), i_range.max()
+    j_min, j_max = j_range.min(), j_range.max()
+    i_slice, j_slice = slice(i_min, i_max + 1), slice(j_min, j_max + 1)
+    cube = cube[..., i_slice, j_slice]
+    selection = selection[i_slice, j_slice]
+    # Mask remaining coordinates outside region
+    mask = da.broadcast_to(~selection, cube.shape)
+    cube.data = da.ma.masked_where(mask, cube.core_data())
     return cube
 
 


Test patch:
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py
index 0d53f330d..212902dfe 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2.py
@@ -14,9 +14,11 @@ from esmvalcore.cmor._fixes.cmip6.cesm2 import (
     Clw,
     Fgco2,
     Omon,
+    Siconc,
     Tas,
     Tos,
 )
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
@@ -285,6 +287,12 @@ def test_get_fgco2_fix():
     assert fix == [Fgco2(None), Omon(None)]
 
 
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
 def test_tas_fix_metadata(tas_cubes):
     """Test ``fix_metadata`` for ``tas``."""
     for cube in tas_cubes:
@@ -366,3 +374,8 @@ def test_fgco2_fix_metadata():
     # Check values of depth coordinate
     np.testing.assert_allclose(depth_coord.points, 0.0)
     assert depth_coord.bounds is None
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py
index 5159cbea9..4a5e04912 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2_fv2.py
@@ -1,7 +1,16 @@
 """Tests for the fixes of CESM2-FV2."""
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Cl as BaseCl
+from esmvalcore.cmor._fixes.cmip6.cesm2 import Fgco2 as BaseFgco2
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Tas as BaseTas
-from esmvalcore.cmor._fixes.cmip6.cesm2_fv2 import Cl, Cli, Clw, Tas
+from esmvalcore.cmor._fixes.cmip6.cesm2_fv2 import (
+    Cl,
+    Cli,
+    Clw,
+    Fgco2,
+    Siconc,
+    Tas,
+)
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 
 
@@ -38,6 +47,28 @@ def test_clw_fix():
     assert Clw is BaseCl
 
 
+def test_get_fgco2_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-FV2', 'Omon', 'fgco2')
+    assert fix == [Fgco2(None)]
+
+
+def test_fgco2_fix():
+    """Test fix for ``fgco2``."""
+    assert Fgco2 is BaseFgco2
+
+
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-FV2', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 def test_get_tas_fix():
     """Test getting of fix."""
     fix = Fix.get_fixes('CMIP6', 'CESM2-FV2', 'Amon', 'tas')
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py
index 6c9743701..5cc66aef7 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm.py
@@ -8,8 +8,17 @@ import numpy as np
 import pytest
 
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Cl as BaseCl
+from esmvalcore.cmor._fixes.cmip6.cesm2 import Fgco2 as BaseFgco2
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Tas as BaseTas
-from esmvalcore.cmor._fixes.cmip6.cesm2_waccm import Cl, Cli, Clw, Tas
+from esmvalcore.cmor._fixes.cmip6.cesm2_waccm import (
+    Cl,
+    Cli,
+    Clw,
+    Fgco2,
+    Siconc,
+    Tas,
+)
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 
 
@@ -72,6 +81,28 @@ def test_clw_fix():
     assert Clw is Cl
 
 
+def test_get_fgco2_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM', 'Omon', 'fgco2')
+    assert fix == [Fgco2(None)]
+
+
+def test_fgco2_fix():
+    """Test fix for ``fgco2``."""
+    assert Fgco2 is BaseFgco2
+
+
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 @pytest.fixture
 def tas_cubes():
     """Cubes to test fixes for ``tas``."""
diff --git a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py
index c5837211b..5aec25422 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_cesm2_waccm_fv2.py
@@ -1,7 +1,16 @@
 """Tests for the fixes of CESM2-WACCM-FV2."""
 from esmvalcore.cmor._fixes.cmip6.cesm2 import Tas as BaseTas
+from esmvalcore.cmor._fixes.cmip6.cesm2 import Fgco2 as BaseFgco2
 from esmvalcore.cmor._fixes.cmip6.cesm2_waccm import Cl as BaseCl
-from esmvalcore.cmor._fixes.cmip6.cesm2_waccm_fv2 import Cl, Cli, Clw, Tas
+from esmvalcore.cmor._fixes.cmip6.cesm2_waccm_fv2 import (
+    Cl,
+    Cli,
+    Clw,
+    Fgco2,
+    Siconc,
+    Tas,
+)
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 
 
@@ -38,6 +47,28 @@ def test_clw_fix():
     assert Clw is BaseCl
 
 
+def test_get_fgco2_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM-FV2', 'Omon', 'fgco2')
+    assert fix == [Fgco2(None)]
+
+
+def test_fgco2_fix():
+    """Test fix for ``fgco2``."""
+    assert Fgco2 is BaseFgco2
+
+
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM-FV2', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 def test_get_tas_fix():
     """Test getting of fix."""
     fix = Fix.get_fixes('CMIP6', 'CESM2-WACCM-FV2', 'Amon', 'tas')
diff --git a/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py b/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py
index d813a4214..f53379a1d 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_gfdl_cm4.py
@@ -3,7 +3,8 @@ import iris
 import numpy as np
 import pytest
 
-from esmvalcore.cmor._fixes.cmip6.gfdl_cm4 import Cl, Cli, Clw
+from esmvalcore.cmor._fixes.cmip6.gfdl_cm4 import Cl, Cli, Clw, Siconc
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
@@ -90,3 +91,14 @@ def test_get_clw_fix():
 def test_clw_fix():
     """Test fix for ``clw``."""
     assert Clw is Cl
+
+
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'GFDL-CM4', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
diff --git a/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py b/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py
index 2cad4a38b..d21323bb1 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_gfdl_esm4.py
@@ -3,60 +3,22 @@
 import iris
 import numpy as np
 import pytest
-from cf_units import Unit
 
 from esmvalcore.cmor._fixes.cmip6.gfdl_esm4 import Fgco2, Omon, Siconc
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
 
-@pytest.fixture
-def siconc_cubes():
-    """Sample cube."""
-    time_coord = iris.coords.DimCoord([0.0], standard_name='time',
-                                      var_name='time',
-                                      units='days since 6543-2-1')
-    lat_coord = iris.coords.DimCoord([-30.0], standard_name='latitude',
-                                     var_name='lat', units='degrees_north')
-    lon_coord = iris.coords.DimCoord([30.0], standard_name='longitude',
-                                     var_name='lon', units='degrees_east')
-    coords_specs = [(time_coord, 0), (lat_coord, 1), (lon_coord, 2)]
-    cube = iris.cube.Cube([[[22.0]]], standard_name='sea_ice_area_fraction',
-                          var_name='siconc', units='%',
-                          dim_coords_and_dims=coords_specs)
-    return iris.cube.CubeList([cube])
-
-
 def test_get_siconc_fix():
     """Test getting of fix."""
     fix = Fix.get_fixes('CMIP6', 'GFDL-ESM4', 'SImon', 'siconc')
     assert fix == [Siconc(None)]
 
 
-def test_siconc_fix_metadata(siconc_cubes):
-    """Test ``fix_metadata`` for ``cl``."""
-    assert len(siconc_cubes) == 1
-    siconc_cube = siconc_cubes[0]
-    assert siconc_cube.var_name == "siconc"
-
-    # Extract siconc cube
-    siconc_cube = siconc_cubes.extract_cube('sea_ice_area_fraction')
-    assert not siconc_cube.coords('typesi')
-
-    # Apply fix
-    vardef = get_var_info('CMIP6', 'SImon', 'siconc')
-    fix = Siconc(vardef)
-    fixed_cubes = fix.fix_metadata(siconc_cubes)
-    assert len(fixed_cubes) == 1
-    fixed_siconc_cube = fixed_cubes.extract_cube(
-        'sea_ice_area_fraction')
-    fixed_typesi_coord = fixed_siconc_cube.coord('area_type')
-    assert fixed_typesi_coord.points is not None
-    assert fixed_typesi_coord.bounds is None
-    np.testing.assert_equal(fixed_typesi_coord.points,
-                            ['siconc'])
-    np.testing.assert_equal(fixed_typesi_coord.units,
-                            Unit('1'))
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
 
 
 @pytest.fixture
diff --git a/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py b/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py
index bbe1c360d..41de6089c 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_kiost_esm.py
@@ -3,7 +3,12 @@ import iris
 import pytest
 from cf_units import Unit
 
-from esmvalcore.cmor._fixes.cmip6.kiost_esm import SfcWind, Tas
+from esmvalcore.cmor._fixes.cmip6.kiost_esm import (
+    SfcWind,
+    Siconc,
+    Tas,
+)
+from esmvalcore.cmor._fixes.common import SiconcFixScalarCoord
 from esmvalcore.cmor._fixes.fix import Fix
 from esmvalcore.cmor.table import get_var_info
 
@@ -97,6 +102,17 @@ def test_sfcwind_fix_metadata(sfcwind_cubes):
     assert coord == height_coord
 
 
+def test_get_siconc_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'KIOST-ESM', 'SImon', 'siconc')
+    assert fix == [Siconc(None)]
+
+
+def test_siconc_fix():
+    """Test fix for ``siconc``."""
+    assert Siconc is SiconcFixScalarCoord
+
+
 def test_get_tas_fix():
     fix = Fix.get_fixes('CMIP6', 'KIOST-ESM', 'Amon', 'tas')
     assert fix == [Tas(None)]
diff --git a/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py b/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py
index bf2398f79..09d35be72 100644
--- a/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py
+++ b/tests/integration/cmor/_fixes/cmip6/test_sam0_unicon.py
@@ -1,5 +1,9 @@
 """Test fixes for SAM0-UNICON."""
-from esmvalcore.cmor._fixes.cmip6.sam0_unicon import Cl, Cli, Clw
+import iris
+import numpy as np
+import pytest
+
+from esmvalcore.cmor._fixes.cmip6.sam0_unicon import Cl, Cli, Clw, Nbp
 from esmvalcore.cmor._fixes.common import ClFixHybridPressureCoord
 from esmvalcore.cmor._fixes.fix import Fix
 
@@ -35,3 +39,29 @@ def test_get_clw_fix():
 def test_clw_fix():
     """Test fix for ``clw``."""
     assert Clw is ClFixHybridPressureCoord
+
+
+def test_get_nbp_fix():
+    """Test getting of fix."""
+    fix = Fix.get_fixes('CMIP6', 'SAM0-UNICON', 'Lmon', 'nbp')
+    assert fix == [Nbp(None)]
+
+
+@pytest.fixture
+def nbp_cube():
+    """``nbp`` cube."""
+    cube = iris.cube.Cube(
+        [1.0],
+        var_name='nbp',
+        standard_name='surface_net_downward_mass_flux_of_carbon_dioxide'
+        '_expressed_as_carbon_due_to_all_land_processes',
+        units='kg m-2 s-1',
+    )
+    return cube
+
+
+def test_nbp_fix_data(nbp_cube):
+    """Test ``fix_data`` for ``nbp``."""
+    fix = Nbp(None)
+    out_cube = fix.fix_data(nbp_cube)
+    np.testing.assert_allclose(out_cube.data, [-1.0])
diff --git a/tests/integration/cmor/_fixes/test_common.py b/tests/integration/cmor/_fixes/test_common.py
index 42966fd0b..922f807fe 100644
--- a/tests/integration/cmor/_fixes/test_common.py
+++ b/tests/integration/cmor/_fixes/test_common.py
@@ -2,11 +2,13 @@
 import iris
 import numpy as np
 import pytest
+from cf_units import Unit
 
 from esmvalcore.cmor._fixes.common import (
     ClFixHybridHeightCoord,
     ClFixHybridPressureCoord,
     OceanFixGrid,
+    SiconcFixScalarCoord,
 )
 from esmvalcore.cmor.table import get_var_info
 from esmvalcore.iris_helpers import var_name_constraint
@@ -169,6 +171,49 @@ def test_cl_hybrid_height_coord_fix_metadata(test_data_path):
                                      ClFixHybridHeightCoord(vardef))
 
 
+@pytest.fixture
+def siconc_cubes():
+    """Sample cube."""
+    time_coord = iris.coords.DimCoord([0.0], standard_name='time',
+                                      var_name='time',
+                                      units='days since 6543-2-1')
+    lat_coord = iris.coords.DimCoord([-30.0], standard_name='latitude',
+                                     var_name='lat', units='degrees_north')
+    lon_coord = iris.coords.DimCoord([30.0], standard_name='longitude',
+                                     var_name='lon', units='degrees_east')
+    coords_specs = [(time_coord, 0), (lat_coord, 1), (lon_coord, 2)]
+    cube = iris.cube.Cube([[[22.0]]], standard_name='sea_ice_area_fraction',
+                          var_name='siconc', units='%',
+                          dim_coords_and_dims=coords_specs)
+    return iris.cube.CubeList([cube])
+
+
+def test_siconc_fix_metadata(siconc_cubes):
+    """Test ``fix_metadata`` for ``siconc``."""
+    assert len(siconc_cubes) == 1
+    siconc_cube = siconc_cubes[0]
+    assert siconc_cube.var_name == "siconc"
+
+    # Extract siconc cube
+    siconc_cube = siconc_cubes.extract_cube('sea_ice_area_fraction')
+    assert not siconc_cube.coords('typesi')
+
+    # Apply fix
+    vardef = get_var_info('CMIP6', 'SImon', 'siconc')
+    fix = SiconcFixScalarCoord(vardef)
+    fixed_cubes = fix.fix_metadata(siconc_cubes)
+    assert len(fixed_cubes) == 1
+    fixed_siconc_cube = fixed_cubes.extract_cube(
+        'sea_ice_area_fraction')
+    fixed_typesi_coord = fixed_siconc_cube.coord('area_type')
+    assert fixed_typesi_coord.points is not None
+    assert fixed_typesi_coord.bounds is None
+    np.testing.assert_equal(fixed_typesi_coord.points,
+                            ['sea_ice'])
+    np.testing.assert_equal(fixed_typesi_coord.units,
+                            Unit('No unit'))
+
+
 def get_tos_cubes(wrong_ij_names=False, ij_bounds=False):
     """Cubes containing tos variable."""
     if wrong_ij_names:
diff --git a/tests/integration/cmor/_fixes/test_shared.py b/tests/integration/cmor/_fixes/test_shared.py
index b185c7c92..69e9a0f09 100644
--- a/tests/integration/cmor/_fixes/test_shared.py
+++ b/tests/integration/cmor/_fixes/test_shared.py
@@ -12,6 +12,7 @@ from esmvalcore.cmor._fixes.shared import (
     add_scalar_height_coord,
     add_scalar_typeland_coord,
     add_scalar_typesea_coord,
+    add_scalar_typesi_coord,
     add_sigma_factory,
     cube_to_aux_coord,
     fix_bounds,
@@ -327,6 +328,33 @@ def test_add_scalar_typesea_coord(cube_in, typesea):
     assert coord == typesea_coord
 
 
+@pytest.mark.sequential
+@pytest.mark.parametrize('cube_in,typesi', TEST_ADD_SCALAR_COORD)
+def test_add_scalar_typesi_coord(cube_in, typesi):
+    """Test adding of scalar typesi coordinate."""
+    cube_in = cube_in.copy()
+    if typesi is None:
+        typesi = 'sea_ice'
+    typesi_coord = iris.coords.AuxCoord(typesi,
+                                        var_name='type',
+                                        standard_name='area_type',
+                                        long_name='Sea Ice area type',
+                                        units=Unit('no unit'))
+    with pytest.raises(iris.exceptions.CoordinateNotFoundError):
+        cube_in.coord('area_type')
+    if typesi == 'sea_ice':
+        cube_out = add_scalar_typesi_coord(cube_in)
+    else:
+        cube_out = add_scalar_typesi_coord(cube_in, typesi)
+    assert cube_out is cube_in
+    coord = cube_in.coord('area_type')
+    assert coord == typesi_coord
+    cube_out_2 = add_scalar_typesi_coord(cube_out)
+    assert cube_out_2 is cube_out
+    coord = cube_in.coord('area_type')
+    assert coord == typesi_coord
+
+
 PS_COORD = iris.coords.AuxCoord([[[101000.0]]], var_name='ps', units='Pa')
 PTOP_COORD = iris.coords.AuxCoord(1000.0, var_name='ptop', units='Pa')
 LEV_COORD = iris.coords.AuxCoord([0.5], bounds=[[0.2, 0.8]], var_name='lev',
diff --git a/tests/unit/preprocessor/_area/test_area.py b/tests/unit/preprocessor/_area/test_area.py
index 2a521dc68..96359502f 100644
--- a/tests/unit/preprocessor/_area/test_area.py
+++ b/tests/unit/preprocessor/_area/test_area.py
@@ -214,8 +214,14 @@ class Test(tests.Test):
 
 def create_irregular_grid_cube(data, lons, lats):
     """Create test cube on irregular grid."""
-    nlat = iris.coords.DimCoord(range(data.shape[0]), var_name='nlat')
-    nlon = iris.coords.DimCoord(range(data.shape[1]), var_name='nlon')
+    times = iris.coords.DimCoord(np.array([10, 20], dtype=np.float64),
+                                 standard_name='time',
+                                 units=Unit('days since 1950-01-01',
+                                            calendar='gregorian'))
+
+    # Construct cube
+    nlat = iris.coords.DimCoord(range(data.shape[1]), var_name='nlat')
+    nlon = iris.coords.DimCoord(range(data.shape[2]), var_name='nlon')
     lat = iris.coords.AuxCoord(lats,
                                var_name='lat',
                                standard_name='latitude',
@@ -225,12 +231,13 @@ def create_irregular_grid_cube(data, lons, lats):
                                standard_name='longitude',
                                units='degrees')
     dim_coord_spec = [
-        (nlat, 0),
-        (nlon, 1),
+        (times, 0),
+        (nlat, 1),
+        (nlon, 2),
     ]
     aux_coord_spec = [
-        (lat, [0, 1]),
-        (lon, [0, 1]),
+        (lat, [1, 2]),
+        (lon, [1, 2]),
     ]
     cube = iris.cube.Cube(
         data,
@@ -243,29 +250,58 @@ def create_irregular_grid_cube(data, lons, lats):
 
 
 IRREGULAR_EXTRACT_REGION_TESTS = [
+    {
+        'region': (100, 140, -10, 90),
+        'mask': np.array(
+            [
+                [False],
+                [False],
+            ],
+            dtype=bool,
+        ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, 1:3, 1:2]
+    },
+    {
+        'region': (100, 360, -60, 0),
+        'mask': np.array(
+            [
+                [True, False],
+                [False, False],
+            ],
+            dtype=bool,
+        ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, 0:2, 1:3]
+    },
     {
         'region': (10, 360, 0, 90),
-        'mask':
-        np.array(
+        'mask': np.array(
             [
-                [True, True, True],
-                [True, True, False],
-                [True, False, False],
+                [True, False],
+                [False, False],
             ],
             dtype=bool,
         ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, 1:, 1:]
+    },
+    {
+        'region': (0, 360, -90, -30),
+        'mask': np.array(
+            [
+                [False, False, False],
+            ],
+            dtype=bool,
+        ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, :1, :]
     },
     {
         'region': (200, 10, -90, -60),
-        'mask':
-        np.array(
+        'mask': np.array(
             [
                 [False, True, False],
-                [True, True, True],
-                [True, True, True],
             ],
             dtype=bool,
         ),
+        'data': np.arange(18, dtype=np.float32).reshape((2, 3, 3))[:, :1, :]
     },
     {
         'region': (-150, 50, 50, -50),
@@ -278,6 +314,8 @@ IRREGULAR_EXTRACT_REGION_TESTS = [
             ],
             dtype=bool,
         ),
+        'data':
+        np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     },
     {
         'region': (0, 0, -100, 0),
@@ -293,7 +331,7 @@ IRREGULAR_EXTRACT_REGION_TESTS = [
 @pytest.fixture
 def irregular_extract_region_cube():
     """Create a test cube on an irregular grid to test `extract_region`."""
-    data = np.arange(9, dtype=np.float32).reshape((3, 3))
+    data = np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     lons = np.array(
         [
             [0, 120, 240],
@@ -327,9 +365,9 @@ def test_extract_region_irregular(irregular_extract_region_cube, case):
             end_latitude=end_lat,
         )
 
-        data = np.arange(9, dtype=np.float32).reshape((3, 3))
-        np.testing.assert_array_equal(cube.data.mask, case['mask'])
-        np.testing.assert_array_equal(cube.data.data, data)
+        for i in range(2):
+            np.testing.assert_array_equal(cube.data[i].mask, case['mask'])
+        np.testing.assert_array_equal(cube.data.data, case['data'])
     else:
         with pytest.raises(ValueError) as exc:
             extract_region(
@@ -833,7 +871,7 @@ def test_extract_composite_shape_negative_bounds(make_testcube,
 @pytest.fixture
 def irreg_extract_shape_cube():
     """Create a test cube on an irregular grid to test `extract_shape`."""
-    data = np.arange(9, dtype=np.float32).reshape((3, 3))
+    data = np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     lats = np.array(
         [
             [0.0, 0.0, 0.1],
@@ -870,7 +908,7 @@ def test_extract_shape_irregular(irreg_extract_shape_cube, tmp_path, method):
 
     cube = extract_shape(irreg_extract_shape_cube, shapefile, method)
 
-    data = np.arange(9, dtype=np.float32).reshape((3, 3))
+    data = np.arange(18, dtype=np.float32).reshape((2, 3, 3))
     mask = np.array(
         [
             [True, True, True],
@@ -882,7 +920,8 @@ def test_extract_shape_irregular(irreg_extract_shape_cube, tmp_path, method):
     if method == 'representative':
         mask[1, 1] = True
     np.testing.assert_array_equal(cube.data, data)
-    np.testing.assert_array_equal(cube.data.mask, mask)
+    for i in range(2):
+        np.testing.assert_array_equal(cube.data[i].mask, mask)
 
 
 def test_extract_shape_wrong_method_raises():