示例#1
0
 def test_basic(self):
     """Test that the utility returns a list."""
     cube1 = self.cube.copy()
     cube2 = self.cube.copy()
     cubelist = iris.cube.CubeList([cube1, cube2])
     result = compare_coords(cubelist)
     self.assertIsInstance(result, list)
示例#2
0
def resolve_metadata_diff(cube1, cube2, warnings_on=False):
    """Resolve any differences in metadata between cubes.

    Args:
        cube1 (iris.cube.Cube):
            Cube containing data to be combined.
        cube2 (iris.cube.Cube):
            Cube containing data to be combined.

    Keyword Args:
        warnings_on (bool):
            If True output warnings for mismatching metadata.

    Returns:
        (tuple): tuple containing
            **result1** (iris.cube.Cube):
                Cube with corrected Metadata.
            **result2** (iris.cube.Cube):
                Cube with corrected Metadata.

    """
    result1 = cube1
    result2 = cube2
    cubes = iris.cube.CubeList([result1, result2])

    # Processing will be based on cube1 so any unmatching
    # attributes will be ignored

    # Find mismatching coords
    unmatching_coords = compare_coords(cubes)
    # If extra dim coord length 1 on cube1 then add to cube2
    for coord in unmatching_coords[0]:
        if coord not in unmatching_coords[1]:
            if len(result1.coord(coord).points) == 1:
                if result1.coord_dims(coord) is not None:
                    coord_dict = dict()
                    coord_dict['points'] = result1.coord(coord).points
                    coord_dict['bounds'] = result1.coord(coord).bounds
                    coord_dict['units'] = result1.coord(coord).units
                    coord_dict['metatype'] = 'DimCoord'
                    result2 = add_coord(result2,
                                        coord,
                                        coord_dict,
                                        warnings_on=warnings_on)
                    result2 = iris.util.as_compatible_shape(result2, result1)
    # If extra dim coord length 1 on cube2 then delete from cube2
    for coord in unmatching_coords[1]:
        if coord not in unmatching_coords[0]:
            if len(result2.coord(coord).points) == 1:
                result2 = update_coord(result2,
                                       coord,
                                       'delete',
                                       warnings_on=warnings_on)

    # If shapes still do not match Raise an error
    if result1.data.shape != result2.data.shape:
        msg = "Can not combine cubes, mismatching shapes"
        raise ValueError(msg)
    return result1, result2
示例#3
0
 def test_catch_warning(self, warning_list=None):
     """Test warning is raised if the input is cubelist of length 1."""
     cube = self.cube.copy()
     result = compare_coords(iris.cube.CubeList([cube]))
     self.assertTrue(any(item.category == UserWarning for item in warning_list))
     warning_msg = "Only a single cube so no differences will be found "
     self.assertTrue(any(warning_msg in str(item) for item in warning_list))
     self.assertEqual(result, [])
示例#4
0
    def process(self, wind_speed, wind_dir):
        """
        Convert wind speed and direction into u,v components along input cube
        projection axes.

        Args:
            wind_speed (iris.cube.Cube):
                Cube containing wind speed values
            wind_dir (iris.cube.Cube):
                Cube containing wind direction values relative to true North

        Returns:
            (tuple): tuple containing

                **ucube** (iris.cube.Cube):
                    Cube containing wind speeds in the positive projection
                    x-axis direction, with units and projection matching
                    wind_speed cube.

                **vcube** (iris.cube.Cube):
                    Cube containing wind speeds in the positive projection
                    y-axis direction, with units and projection matching
                    wind_speed cube.
        """
        # check cubes contain the correct data (assuming CF standard names)
        if "wind_speed" not in wind_speed.name():
            msg = '{} cube does not contain wind speeds'
            raise ValueError('{} {}'.format(wind_speed.name(), msg))

        if "wind" not in wind_dir.name() or "direction" not in wind_dir.name():
            msg = '{} cube does not contain wind directions'
            raise ValueError('{} {}'.format(wind_dir.name(), msg))

        # check input cube coordinates match
        unmatched_coords = compare_coords([wind_speed, wind_dir])
        if unmatched_coords != [{}, {}]:
            msg = 'Wind speed and direction cubes have unmatched coordinates'
            raise ValueError('{} {}'.format(msg, unmatched_coords))

        # calculate angle adjustments for wind direction
        wind_dir_slice = next(
            wind_dir.slices([
                wind_dir.coord(axis='y').name(),
                wind_dir.coord(axis='x').name()
            ]))
        adj = self.calc_true_north_offset(wind_dir_slice)

        # calculate grid eastward and northward speeds
        ucube, vcube = self.resolve_wind_components(wind_speed, wind_dir, adj)

        # relabel final cubes with CF compliant data names corresponding to
        # positive wind speeds along the x and y axes
        ucube.rename("grid_eastward_wind")
        vcube.rename("grid_northward_wind")

        return ucube, vcube
示例#5
0
    def process(self, wind_speed: Cube, wind_dir: Cube) -> Tuple[Cube, Cube]:

        """
        Convert wind speed and direction into u,v components along input cube
        projection axes.

        Args:
            wind_speed:
                Cube containing wind speed values
            wind_dir:
                Cube containing wind direction values relative to true North

        Returns:
            - Cube containing wind speeds in the positive projection
              x-axis direction, with units and projection matching
              wind_speed cube.
            - Cube containing wind speeds in the positive projection
              y-axis direction, with units and projection matching
              wind_speed cube.
        """
        # check cubes contain the correct data (assuming CF standard names)
        if "wind_speed" not in wind_speed.name():
            msg = "{} cube does not contain wind speeds"
            raise ValueError("{} {}".format(wind_speed.name(), msg))

        if "wind" not in wind_dir.name() or "direction" not in wind_dir.name():
            msg = "{} cube does not contain wind directions"
            raise ValueError("{} {}".format(wind_dir.name(), msg))

        # check input cube coordinates match
        ignored_coords = ["wind_from_direction status_flag", "wind_speed status_flag"]
        unmatched_coords = compare_coords(
            [wind_speed, wind_dir], ignored_coords=ignored_coords
        )
        if unmatched_coords != [{}, {}]:
            msg = "Wind speed and direction cubes have unmatched coordinates"
            raise ValueError("{} {}".format(msg, unmatched_coords))

        # calculate angle adjustments for wind direction
        wind_dir_slice = next(
            wind_dir.slices(
                [wind_dir.coord(axis="y").name(), wind_dir.coord(axis="x").name()]
            )
        )
        adj = self.calc_true_north_offset(wind_dir_slice)

        # calculate grid eastward and northward speeds
        ucube, vcube = self.resolve_wind_components(wind_speed, wind_dir, adj)

        # relabel final cubes with CF compliant data names corresponding to
        # positive wind speeds along the x and y axes
        ucube.rename("grid_eastward_wind")
        vcube.rename("grid_northward_wind")

        return ucube, vcube
示例#6
0
 def test_second_cube_has_extra_ignored_coordinate(self):
     """Test for comparing coordinate between cubes, where the second
     cube in the list has an extra dimension coordinate which is
     explicitly ignored in the comparison."""
     cube1 = self.cube.copy()
     cube2 = self.cube.copy()
     cube2.add_aux_coord(self.extra_dim_coord)
     cube2 = iris.util.new_axis(cube2, "height")
     cubelist = iris.cube.CubeList([cube1, cube2])
     result = compare_coords(cubelist, ignored_coords=["height"])
     self.assertIsInstance(result, list)
     self.assertEqual(result, [{}, {}])
示例#7
0
 def test_second_cube_has_extra_auxiliary_coordinates(self):
     """Test for comparing coordinate between cubes, where the second
     cube in the list has extra auxiliary coordinates."""
     cube1 = self.cube.copy()
     cube2 = self.cube.copy()
     cube2.add_aux_coord(self.extra_aux_coord, data_dims=0)
     cubelist = iris.cube.CubeList([cube1, cube2])
     result = compare_coords(cubelist)
     self.assertIsInstance(result, list)
     self.assertEqual(len(result[0]), 0)
     self.assertEqual(len(result[1]), 1)
     self.assertEqual(result[1]["model"]["coord"], self.extra_aux_coord)
     self.assertEqual(result[1]["model"]["data_dims"], None)
     self.assertEqual(result[1]["model"]["aux_dims"], 0)
示例#8
0
 def test_second_cube_has_extra_dimension_coordinates(self):
     """Test for comparing coordinate between cubes, where the second
     cube in the list has extra dimension coordinates."""
     cube1 = self.cube.copy()
     cube2 = self.cube.copy()
     cube2.add_aux_coord(self.extra_dim_coord)
     cube2 = iris.util.new_axis(cube2, "height")
     cubelist = iris.cube.CubeList([cube1, cube2])
     result = compare_coords(cubelist)
     self.assertIsInstance(result, list)
     self.assertEqual(len(result[0]), 0)
     self.assertEqual(len(result[1]), 1)
     self.assertEqual(result[1]["height"]["coord"], self.extra_dim_coord)
     self.assertEqual(result[1]["height"]["data_dims"], 0)
     self.assertEqual(result[1]["height"]["aux_dims"], None)
 def test_second_cube_has_extra_dimension_coordinates(self):
     """Test for comparing coordinate between cubes, where the second
     cube in the list has extra dimension coordinates."""
     cube1 = self.cube.copy()
     cube2 = self.cube.copy()
     height_coord = DimCoord([5.0], standard_name="height", units="m")
     cube2.add_aux_coord(height_coord)
     cube2 = iris.util.new_axis(cube2, "height")
     cubelist = iris.cube.CubeList([cube1, cube2])
     result = compare_coords(cubelist)
     self.assertIsInstance(result, list)
     self.assertEqual(len(result[0]), 0)
     self.assertEqual(len(result[1]), 1)
     self.assertEqual(result[1]["height"]["coord"].points, np.array([5.]))
     self.assertEqual(result[1]["height"]["coord"].standard_name, "height")
     self.assertEqual(result[1]["height"]["coord"].units, Unit("m"))
     self.assertEqual(result[1]["height"]["data_dims"], 0)
     self.assertEqual(result[1]["height"]["aux_dims"], None)
 def test_second_cube_has_extra_auxiliary_coordinates(self):
     """Test for comparing coordinate between cubes, where the second
     cube in the list has extra auxiliary coordinates."""
     cube1 = self.cube.copy()
     cube2 = self.cube.copy()
     fp_coord = AuxCoord(
         [3.0], standard_name="forecast_period", units="hours")
     cube2.add_aux_coord(fp_coord, data_dims=1)
     cubelist = iris.cube.CubeList([cube1, cube2])
     result = compare_coords(cubelist)
     self.assertIsInstance(result, list)
     self.assertEqual(len(result[0]), 0)
     self.assertEqual(len(result[1]), 1)
     self.assertEqual(result[1]["forecast_period"]["coord"].points,
                      np.array([3.0]))
     self.assertEqual(result[1]["forecast_period"]["coord"].standard_name,
                      "forecast_period")
     self.assertEqual(result[1]["forecast_period"]["coord"].units,
                      Unit("hours"))
     self.assertEqual(result[1]["forecast_period"]["data_dims"], None)
     self.assertEqual(result[1]["forecast_period"]["aux_dims"], 1)
示例#11
0
    def process(self, temperature, humidity, pressure, uwind, vwind,
                topography):
        """
        Calculate precipitation enhancement over orography on standard and
        high resolution grids.  Input variables are expected to be on the same
        grid (either standard or high resolution).

        Args:
            temperature (iris.cube.Cube):
                Temperature at top of boundary layer
            humidity (iris.cube.Cube):
                Relative humidity at top of boundary layer
            pressure (iris.cube.Cube):
                Pressure at top of boundary layer
            uwind (iris.cube.Cube):
                Positive eastward wind vector component at top of boundary
                layer
            vwind (iris.cube.Cube):
                Positive northward wind vector component at top of boundary
                layer
            topography (iris.cube.Cube):
                Height of topography above sea level on high resolution (1 km)
                UKPP domain grid

        Returns:
            (tuple): tuple containing:
                **orogenh** (iris.cube.Cube):
                    Precipitation enhancement due to orography in mm/h on the
                    1 km Transverse Mercator UKPP grid domain
                **orogenh_standard_grid** (iris.cube.Cube):
                    Precipitation enhancement due to orography in mm/h on the
                    UK standard grid, padded with masked np.nans where outside
                    the UKPP domain
        """
        # check input variable cube coordinates match
        unmatched_coords = compare_coords(
            [temperature, pressure, humidity, uwind, vwind])

        if any(item.keys() for item in unmatched_coords):
            msg = 'Input cube coordinates {} are unmatched'
            raise ValueError(msg.format(unmatched_coords))

        # check one of the input variable cubes is a 2D spatial field (this is
        # equivalent to checking all cubes whose coords are matched above)
        msg = 'Require 2D fields as input; found {} dimensions'
        if temperature.ndim > 2:
            raise ValueError(msg.format(temperature.ndim))
        check_for_x_and_y_axes(temperature)

        # check the topography cube is a 2D spatial field
        if topography.ndim > 2:
            raise ValueError(msg.format(topography.ndim))
        check_for_x_and_y_axes(topography)

        # regrid variables to match topography and populate class instance
        self._regrid_and_populate(temperature, humidity, pressure, uwind,
                                  vwind, topography)

        # calculate saturation vapour pressure
        wbt = WetBulbTemperature()
        self.svp = wbt.pressure_correct_svp(wbt.lookup_svp(self.temperature),
                                            self.temperature, self.pressure)

        # calculate site-specific orographic enhancement
        point_orogenh_data = self._point_orogenh()

        # integrate upstream component
        grid_coord_km = self.topography.coord(axis='x').copy()
        grid_coord_km.convert_units('km')
        self.grid_spacing_km = (grid_coord_km.points[1] -
                                grid_coord_km.points[0])

        orogenh_data = self._add_upstream_component(point_orogenh_data)

        # create data cubes on the two required output grids
        orogenh, orogenh_standard_grid = self._create_output_cubes(
            orogenh_data, temperature)

        return orogenh, orogenh_standard_grid
示例#12
0
    def process(self, temperature, humidity, pressure, uwind, vwind,
                topography):
        """
        Calculate precipitation enhancement over orography on high resolution
        grid. Input diagnostics are all expected to be on the same grid, and
        are regridded to match the orography.

        Args:
            temperature (iris.cube.Cube):
                Temperature at top of boundary layer
            humidity (iris.cube.Cube):
                Relative humidity at top of boundary layer
            pressure (iris.cube.Cube):
                Pressure at top of boundary layer
            uwind (iris.cube.Cube):
                Positive eastward wind vector component at top of boundary
                layer
            vwind (iris.cube.Cube):
                Positive northward wind vector component at top of boundary
                layer
            topography (iris.cube.Cube):
                Height of topography above sea level on high resolution (1 km)
                UKPP domain grid

        Returns:
            iris.cube.Cube:
                Precipitation enhancement due to orography in m/s.
        """
        # check input variable cube coordinates match
        unmatched_coords = compare_coords(
            [temperature, pressure, humidity, uwind, vwind])

        if any(item.keys() for item in unmatched_coords):
            msg = 'Input cube coordinates {} are unmatched'
            raise ValueError(msg.format(unmatched_coords))

        # check one of the input variable cubes is a 2D spatial field (this is
        # equivalent to checking all cubes whose coords are matched above)
        msg = 'Require 2D fields as input; found {} dimensions'
        if temperature.ndim > 2:
            raise ValueError(msg.format(temperature.ndim))
        check_for_x_and_y_axes(temperature)

        # check the topography cube is a 2D spatial field
        if topography.ndim > 2:
            raise ValueError(msg.format(topography.ndim))
        check_for_x_and_y_axes(topography)

        # regrid variables to match topography and populate class instance
        self._regrid_and_populate(temperature, humidity, pressure,
                                  uwind, vwind, topography)

        # calculate saturation vapour pressure
        self.svp = calculate_svp_in_air(
            self.temperature.data, self.pressure.data)

        # calculate site-specific orographic enhancement
        point_orogenh_data = self._point_orogenh()

        # integrate upstream component
        grid_coord_km = self.topography.coord(axis='x').copy()
        grid_coord_km.convert_units('km')
        self.grid_spacing_km = (
            grid_coord_km.points[1] - grid_coord_km.points[0])

        orogenh_data = self._add_upstream_component(point_orogenh_data)

        # create data cubes on the two required output grids
        orogenh = self._create_output_cube(orogenh_data, temperature)

        return orogenh
示例#13
0
def resolve_metadata_diff(cube1, cube2, warnings_on=False):
    """Resolve any differences in metadata between cubes. This involves
    identifying coordinates that are mismatching between the cubes and
    attempting to add this coordinate where it is missing. This makes use of
    the points, bounds, units and attributes, as well as the coordinate type
    i.e. DimCoord or AuxCoord.

    Args:
        cube1 (iris.cube.Cube):
            Cube containing data to be combined.
        cube2 (iris.cube.Cube):
            Cube containing data to be combined.
        warnings_on (bool):
            If True output warnings for mismatching metadata.

    Returns:
        (tuple): tuple containing:
            **result1** (iris.cube.Cube):
                Cube with corrected Metadata.
            **result2** (iris.cube.Cube):
                Cube with corrected Metadata.

    """
    result1 = cube1
    result2 = cube2
    cubes = iris.cube.CubeList([result1, result2])

    # Processing will be based on cube1 so any unmatching
    # attributes will be ignored

    # Find mismatching coords
    unmatching_coords = compare_coords(cubes)
    # If extra dim coord length 1 on cube1 then add to cube2
    for coord in unmatching_coords[0]:
        if coord not in unmatching_coords[1]:
            if len(result1.coord(coord).points) == 1:
                if len(result1.coord_dims(coord)) > 0:
                    coord_dict = dict()
                    coord_dict['points'] = result1.coord(coord).points
                    coord_dict['bounds'] = result1.coord(coord).bounds
                    coord_dict['units'] = result1.coord(coord).units
                    coord_dict['attributes'] = result1.coord(coord).attributes
                    coord_dict['metatype'] = 'DimCoord'
                    if result1.coord(coord).var_name is not None:
                        coord_dict['var_name'] = result1.coord(coord).var_name
                    result2 = add_coord(result2,
                                        coord,
                                        coord_dict,
                                        warnings_on=warnings_on)
                    result2 = iris.util.as_compatible_shape(result2, result1)
    # If extra dim coord length 1 on cube2 then delete from cube2
    for coord in unmatching_coords[1]:
        if coord not in unmatching_coords[0]:
            if len(result2.coord(coord).points) == 1:
                result2 = _update_coord(result2,
                                        coord,
                                        'delete',
                                        warnings_on=warnings_on)

    # If shapes still do not match Raise an error
    if result1.data.shape != result2.data.shape:
        msg = "Can not combine cubes, mismatching shapes"
        raise ValueError(msg)
    return result1, result2
示例#14
0
    def _align_feature_variables(self, feature_cubes: CubeList,
                                 forecast_cube: Cube) -> Tuple[CubeList, Cube]:
        """Ensure that feature cubes have consistent dimension coordinates. If realization
        dimension present in any cube, all cubes lacking this dimension will have realization
        dimension added and broadcast along this new dimension.

        This situation occurs when derived fields (such as accumulated solar radiation)
        are used as predictors. As these fields do not contain a realization dimension,
        they must be broadcast to match the NWP fields that do contain realization, so that
        all features have consistent shape.

        In the case of deterministic models (those without a realization dimension), a
        realization dimension is added to allow consistent behaviour between ensemble and
        deterministic models.

        Args:
            feature_cubes:
                Cubelist containing feature variables to align.
            forecast_cube:
                Cube containing the forecast variable to align.

        Returns:
            - feature_cubes with realization coordinate added to each cube if absent
            - forecast_cube with realization coordinate added if absent

        Raises:
            ValueError:
                if feature/forecast variables have inconsistent dimension coordinates
                (excluding realization dimension), or if feature/forecast variables have
                different length realization coordinate over cubes containing a realization
                coordinate.
        """
        combined_cubes = CubeList(list([*feature_cubes, forecast_cube]))

        # Compare feature cube coordinates, raise error if dim-coords don't match
        compare_feature_coords = compare_coords(combined_cubes,
                                                ignored_coords=["realization"])
        for misaligned_coords in compare_feature_coords:
            for coord_info in misaligned_coords.values():
                if coord_info["data_dims"] is not None:
                    raise ValueError(
                        "Mismatch between non-realization dimension coords.")

        # Compare realization coordinates across cubes where present;
        # raise error if realization coordinates don't match, otherwise set
        # common_realization_coord to broadcast over.
        realization_coords = {
            variable.name(): variable.coord("realization")
            for variable in combined_cubes if variable.coords("realization")
        }
        if not realization_coords:
            # Case I: realization_coords is empty. Add single realization dim to all cubes.
            common_realization_coord = DimCoord([0],
                                                standard_name="realization",
                                                units=1)
        else:
            # Case II: realization_coords is not empty.
            # Note: In future, another option here could be to filter to common realization
            # values using filter_realizations() in utilities.cube_manipulation.
            variables_with_realization = list(realization_coords.keys())
            sample_realization = realization_coords[
                variables_with_realization[0]]
            for feature in variables_with_realization[1:]:
                if realization_coords[feature] != sample_realization:
                    raise ValueError(
                        "Mismatch between realization dimension coords.")
            common_realization_coord = sample_realization

        # Add realization coord to cubes where absent by broadcasting along this dimension
        aligned_cubes = CubeList()
        for cube in combined_cubes:
            if not cube.coords("realization"):
                expanded_cube = add_coordinate_to_cube(
                    cube, new_coord=common_realization_coord)
                aligned_cubes.append(expanded_cube)
            else:
                aligned_cubes.append(cube)

        return aligned_cubes[:-1], aligned_cubes[-1]