def ensure_monotonic_increase_in_chosen_direction(self, cube):
        """Ensure that the chosen coordinate is monotonically increasing in
        the specified direction.

        Args:
            cube (Iris.cube.Cube):
                The cube containing the coordinate to check.
                Note that the input cube will be modified by this method.

        Returns:
            cube (Iris.cube.Cube):
                The cube containing a coordinate that is monotonically
                increasing in the desired direction.

        """
        coord_name = self.coord_name_to_integrate
        direction = self.direction_of_integration
        increasing_order = np.all(np.diff(cube.coord(coord_name).points) > 0)

        if increasing_order and direction == "positive":
            pass
        elif increasing_order and direction == "negative":
            cube = sort_coord_in_cube(cube, coord_name, order="descending")
        elif not increasing_order and direction == "positive":
            cube = sort_coord_in_cube(cube, coord_name)
        elif not increasing_order and direction == "negative":
            pass
        return cube
Exemplo n.º 2
0
    def process(
        self,
        cube: Cube,
        coord_name: str,
        inverse_ordering: bool = False,
    ) -> Cube:
        """
        Calculate nonlinear weights for a given cube and coord.

        Args:
            cube:
                Cube to be blended across the coord.
            coord_name:
                Name of coordinate in the cube to be blended.
            inverse_ordering:
                The input cube blend coordinate will be in ascending order,
                so that calculated blend weights decrease with increasing
                value.  For eg cycle blending by forecast reference time, we
                wish to weight more recent cubes more highly.  This flag gives
                the option to reverse the blend coordinate order so as to have
                higher weights for the higher values.

        Returns:
            1D cube of normalised (sum = 1.0) weights matching input
            dimension to be blended

        Raises:
            TypeError : input is not a cube
        """
        if not isinstance(cube, iris.cube.Cube):
            msg = ("The first argument must be an instance of "
                   "iris.cube.Cube but is"
                   " {0:s}".format(str(type(cube))))
            raise TypeError(msg)

        if inverse_ordering:
            # make a copy of the input cube from which to calculate weights
            inverted_cube = cube.copy()
            inverted_cube = sort_coord_in_cube(inverted_cube,
                                               coord_name,
                                               descending=True)
            cube = inverted_cube

        weights = self.nonlinear_weights(len(cube.coord(coord_name).points))
        weights_cube = WeightsUtilities.build_weights_cube(
            cube, weights, coord_name)

        if inverse_ordering:
            # re-sort the weights cube so that it is in ascending order of
            # blend coordinate (and hence matches the input cube)
            weights_cube = sort_coord_in_cube(weights_cube, coord_name)

        return weights_cube
Exemplo n.º 3
0
    def _regrid_variable(self, var_cube, unit):
        """
        Sorts spatial coordinates in ascending order, regrids the input
        variable onto the topography grid and converts to the required
        units.  This function does not modify the input variable cube.

        Args:
            var_cube (iris.cube.Cube):
                Cube containing input variable data
            unit (str):
                Required unit for this variable

        Returns:
            out_cube (iris.cube.Cube):
                Cube containing regridded variable data
        """
        for axis in ['x', 'y']:
            var_cube = sort_coord_in_cube(var_cube, var_cube.coord(axis=axis))

        var_cube = enforce_coordinate_ordering(
            var_cube,
            [var_cube.coord(axis='y').name(),
             var_cube.coord(axis='x').name()])

        regridder = iris.analysis.Linear()
        out_cube = (var_cube.copy(var_cube.data.astype(np.float32))).regrid(
            self.topography, regridder)
        out_cube.convert_units(unit)
        return out_cube
    def test_metadata(self):
        """Check output metadata on both cubes is as expected"""
        hi_res_attributes = self.temperature.attributes
        for key, val in self.plugin.topography.attributes.items():
            hi_res_attributes[key] = val

        tref = sort_coord_in_cube(self.temperature,
                                  self.temperature.coord(axis='y'))
        output, regridded_output = self.plugin._create_output_cubes(
            self.orogenh, self.temperature)

        for axis in ['x', 'y']:
            self.assertEqual(output.coord(axis=axis),
                             self.plugin.topography.coord(axis=axis))
            self.assertEqual(regridded_output.coord(axis=axis),
                             tref.coord(axis=axis))

        for cube in [output, regridded_output]:
            self.assertEqual(cube.name(), 'orographic_enhancement')
            self.assertEqual(cube.units, 'mm h-1')
            for t_coord in [
                    'time', 'forecast_period', 'forecast_reference_time'
            ]:
                self.assertEqual(cube.coord(t_coord),
                                 self.temperature.coord(t_coord))
        self.assertDictEqual(regridded_output.attributes,
                             self.temperature.attributes)
        self.assertDictEqual(output.attributes, hi_res_attributes)
 def test_warn_raised_for_circular_coordinate(self, warning_list=None):
     """Test that a warning is successfully raised when circular
     coordinates are sorted."""
     self.ascending_cube.data[:, 0, 0] = 6.0
     coord_name = "latitude"
     self.ascending_cube.coord(coord_name).circular = True
     result = sort_coord_in_cube(self.ascending_cube, coord_name, descending=True)
     self.assertTrue(any(item.category == UserWarning for item in warning_list))
     warning_msg = "The latitude coordinate is circular."
     self.assertTrue(any(warning_msg in str(item) for item in warning_list))
     self.assertIsInstance(result, iris.cube.Cube)
 def setUp(self):
     """Set up input cubes"""
     temperature = np.arange(6).reshape(2, 3)
     self.temperature_cube = set_up_variable_cube(temperature)
     orography = np.array([[20., 30., 40., 30., 25., 25.],
                           [30., 50., 80., 60., 50., 45.],
                           [50., 65., 90., 70., 60., 50.],
                           [45., 60., 85., 65., 55., 45.]])
     orography_cube = set_up_orography_cube(orography)
     self.plugin = OrographicEnhancement()
     self.plugin.topography = sort_coord_in_cube(
         orography_cube, orography_cube.coord(axis='y'))
Exemplo n.º 7
0
 def test_ascending_then_ascending(self):
     """Test that the sorting successfully sorts the cube based
     on the points within the given coordinate. The points in the resulting
     cube should now be in ascending order."""
     expected_data = self.data
     coord_name = "height"
     result = sort_coord_in_cube(self.ascending_cube, coord_name)
     self.assertIsInstance(result, iris.cube.Cube)
     self.assertEqual(self.ascending_cube.coord_dims(coord_name),
                      result.coord_dims(coord_name))
     self.assertArrayAlmostEqual(self.ascending_height_points,
                                 result.coord(coord_name).points)
     self.assertArrayAlmostEqual(result.data, expected_data)
Exemplo n.º 8
0
def ensure_ascending_coord(cube: Cube) -> Cube:
    """
    Check if cube coordinates ascending. if not, make it ascending

    Args:
        cube:
            Input source cube.

    Returns:
        Cube with ascending coordinates
    """
    for ax in ("x", "y"):
        if cube.coord(axis=ax).points[0] > cube.coord(axis=ax).points[-1]:
            cube = sort_coord_in_cube(cube, cube.coord(axis=ax).standard_name)
    return cube
Exemplo n.º 9
0
    def ensure_monotonic_increase_in_chosen_direction(self,
                                                      cube: Cube) -> Cube:
        """Ensure that the chosen coordinate is monotonically increasing in
        the specified direction.

        Args:
            cube:
                The cube containing the coordinate to check.
                Note that the input cube will be modified by this method.

        Returns:
            The cube containing a coordinate that is monotonically
            increasing in the desired direction.
        """
        coord_name = self.coord_name_to_integrate
        increasing_order = np.all(np.diff(cube.coord(coord_name).points) > 0)

        if increasing_order and not self.positive_integration:
            cube = sort_coord_in_cube(cube, coord_name, descending=True)

        if not increasing_order and self.positive_integration:
            cube = sort_coord_in_cube(cube, coord_name)

        return cube
Exemplo n.º 10
0
 def test_auxcoord(self):
     """Test that the above sorting is successful when an AuxCoord is
     used."""
     expected_data = self.data
     coord_name = "height_aux"
     height_coord = self.ascending_cube.coord("height")
     (height_coord_index, ) = self.ascending_cube.coord_dims("height")
     new_coord = AuxCoord(height_coord.points, long_name=coord_name)
     self.ascending_cube.add_aux_coord(new_coord, height_coord_index)
     result = sort_coord_in_cube(self.ascending_cube, coord_name)
     self.assertIsInstance(result, iris.cube.Cube)
     self.assertEqual(self.ascending_cube.coord_dims(coord_name),
                      result.coord_dims(coord_name))
     self.assertArrayAlmostEqual(self.ascending_height_points,
                                 result.coord(coord_name).points)
     self.assertArrayAlmostEqual(result.data, expected_data)
def _set_up_height_cube(height_points, ascending=True):
    """Create cube of temperatures decreasing with height"""
    data = 280 * np.ones((3, 3, 3), dtype=np.float32)
    data[1, :] = 278
    data[2, :] = 276

    cube = set_up_variable_cube(data[0].astype(np.float32))
    height_points = np.sort(height_points)
    cube = add_coordinate(cube, height_points, "height", coord_units="m")
    cube.coord("height").attributes["positive"] = "up"
    cube.data = data.astype(np.float32)

    if not ascending:
        cube = sort_coord_in_cube(cube, "height", descending=True)
        cube.coord("height").attributes["positive"] = "down"

    return cube
    def setUp(self):
        """Set up a plugin instance, data array and cubes"""
        self.plugin = OrographicEnhancement()
        topography = set_up_orography_cube(np.zeros((3, 4), dtype=np.float32))
        self.plugin.topography = sort_coord_in_cube(topography,
                                                    topography.coord(axis='y'))

        self.temperature = set_up_variable_cube(np.full((2, 4), 280.15),
                                                units='kelvin',
                                                xo=398000.)
        self.temperature.attributes['institution'] = 'Met Office'
        self.temperature.attributes['source'] = 'Met Office Unified Model'
        self.temperature.attributes['mosg__grid_type'] = 'standard'
        self.temperature.attributes['mosg__grid_version'] = '1.2.0'
        self.temperature.attributes['mosg__grid_domain'] = 'uk_extended'
        self.temperature.attributes['mosg__model_configuration'] = 'uk_det'

        self.orogenh = np.array([[1.1, 1.2, 1.5, 1.4], [1.0, 1.3, 1.4, 1.6],
                                 [0.8, 0.9, 1.2, 0.9]])
Exemplo n.º 13
0
 def test_descending_then_ascending(self):
     """Test that the sorting successfully sorts the cube based
     on the points within the given coordinate. The points in the resulting
     cube should now be in ascending order."""
     expected_data = np.array([[[[3.00, 3.00, 3.00], [3.00, 3.00, 3.00],
                                 [3.00, 3.00, 3.00]]],
                               [[[2.00, 2.00, 2.00], [2.00, 2.00, 2.00],
                                 [2.00, 2.00, 2.00]]],
                               [[[1.00, 1.00, 1.00], [1.00, 1.00, 1.00],
                                 [1.00, 1.00, 1.00]]]])
     coord_name = "height"
     result = sort_coord_in_cube(self.descending_cube, coord_name)
     self.assertIsInstance(result, iris.cube.Cube)
     self.assertEqual(self.ascending_cube.coord_dims(coord_name),
                      result.coord_dims(coord_name))
     self.assertArrayAlmostEqual(self.ascending_height_points,
                                 result.coord(coord_name).points)
     self.assertDictEqual(
         result.coord(coord_name).attributes, {"positive": "up"})
     self.assertArrayAlmostEqual(result.data, expected_data)
Exemplo n.º 14
0
 def test_latitude(self):
     """Test that the sorting successfully sorts the cube based
     on the points within the given coordinate (latitude).
     The points in the resulting cube should now be in descending order."""
     expected_data = np.array([
         [[1.00, 1.00, 1.00], [1.00, 1.00, 1.00], [6.00, 1.00, 1.00]],
         [[2.00, 2.00, 2.00], [2.00, 2.00, 2.00], [6.00, 2.00, 2.00]],
         [[3.00, 3.00, 3.00], [3.00, 3.00, 3.00], [6.00, 3.00, 3.00]],
     ])
     self.ascending_cube.data[:, 0, 0] = 6.0
     expected_points = np.flip(self.ascending_cube.coord("latitude").points)
     coord_name = "latitude"
     result = sort_coord_in_cube(self.ascending_cube,
                                 coord_name,
                                 descending=True)
     self.assertIsInstance(result, iris.cube.Cube)
     self.assertEqual(self.ascending_cube.coord_dims(coord_name),
                      result.coord_dims(coord_name))
     self.assertArrayAlmostEqual(expected_points,
                                 result.coord(coord_name).points)
     self.assertArrayAlmostEqual(result.data, expected_data)
Exemplo n.º 15
0
    def setUp(self):
        """Set up a plugin instance, data array and cubes"""
        self.plugin = OrographicEnhancement()
        topography = set_up_orography_cube(np.zeros((3, 4), dtype=np.float32))
        self.plugin.topography = sort_coord_in_cube(topography,
                                                    topography.coord(axis="y"))

        t_attributes = {
            "institution": "Met Office",
            "source": "Met Office Unified Model",
            "mosg__grid_type": "standard",
            "mosg__grid_version": "1.2.0",
            "mosg__grid_domain": "uk_extended",
            "mosg__model_configuration": "uk_det",
        }
        self.temperature = set_up_variable_cube(
            np.full((2, 4), 280.15, dtype=np.float32),
            units="kelvin",
            xo=398000.0,
            attributes=t_attributes,
        )

        self.orogenh = np.array([[1.1, 1.2, 1.5, 1.4], [1.0, 1.3, 1.4, 1.6],
                                 [0.8, 0.9, 1.2, 0.9]])
Exemplo n.º 16
0
    def process(self, cube, weights=None):
        """Calculate weighted blend across the chosen coord, for either
           probabilistic or percentile data. If there is a percentile
           coordinate on the cube, it will blend using the
           PercentileBlendingAggregator but the percentile coordinate must
           have at least two points.

        Args:
            cube (iris.cube.Cube):
                Cube to blend across the coord.
            weights (iris.cube.Cube):
                Cube of blending weights. This will have 1 or 3 dimensions,
                corresponding either to blend dimension on the input cube with or
                without and additional 2 spatial dimensions. If None, the input cube
                is blended with equal weights across the blending dimension.

        Returns:
            iris.cube.Cube:
                Containing the weighted blend across the chosen coordinate (typically
                forecast reference time or model).

        Raises:
            TypeError : If the first argument not a cube.
            CoordinateNotFoundError : If coordinate to be collapsed not found
                                      in cube.
            CoordinateNotFoundError : If coordinate to be collapsed not found
                                      in provided weights cube.
            ValueError : If coordinate to be collapsed is not a dimension.
        """
        if not isinstance(cube, iris.cube.Cube):
            msg = ("The first argument must be an instance of iris.cube.Cube "
                   "but is {}.".format(type(cube)))
            raise TypeError(msg)

        if not cube.coords(self.blend_coord):
            msg = "Coordinate to be collapsed not found in cube."
            raise CoordinateNotFoundError(msg)

        output_dims = get_dim_coord_names(
            next(cube.slices_over(self.blend_coord)))
        self.blend_coord = find_blend_dim_coord(cube, self.blend_coord)

        # Ensure input cube and weights cube are ordered equivalently along
        # blending coordinate.
        cube = sort_coord_in_cube(cube, self.blend_coord)
        if weights is not None:
            if not weights.coords(self.blend_coord):
                msg = "Coordinate to be collapsed not found in weights cube."
                raise CoordinateNotFoundError(msg)
            weights = sort_coord_in_cube(weights, self.blend_coord)

        # Check that the time coordinate is single valued if required.
        self.check_compatible_time_points(cube)

        # Do blending and update metadata
        if self.check_percentile_coord(cube):
            enforce_coordinate_ordering(cube, [self.blend_coord, "percentile"])
            result = self.percentile_weighted_mean(cube, weights)
        else:
            enforce_coordinate_ordering(cube, [self.blend_coord])
            result = self.weighted_mean(cube, weights)

        # Reorder resulting dimensions to match input
        enforce_coordinate_ordering(result, output_dims)

        return result
Exemplo n.º 17
0
 def setup_cubes_for_process(self, spatial_grid="equalarea"):
     data = np.ones((5, 5), dtype=np.float32)
     data[2, 2] = 100.0
     self.orog = set_up_variable_cube(
         data, name="surface_altitude", units="m", spatial_grid=spatial_grid
     )
     self.land_sea = set_up_variable_cube(
         np.ones_like(data, dtype=np.int8),
         name="land_binary_mask",
         units=1,
         spatial_grid=spatial_grid,
     )
     # Note the values below are ordered at [5, 195, 200] m.
     wbt_0 = np.full_like(data, fill_value=271.46216)
     wbt_0[2, 2] = 270.20343
     wbt_1 = np.full_like(data, fill_value=274.4207)
     wbt_1[2, 2] = 271.46216
     wbt_2 = np.full_like(data, fill_value=275.0666)
     wbt_2[2, 2] = 274.4207
     wbt_data = np.array(
         [
             np.broadcast_to(wbt_0, (3, 5, 5)),
             np.broadcast_to(wbt_1, (3, 5, 5)),
             np.broadcast_to(wbt_2, (3, 5, 5)),
         ],
         dtype=np.float32,
     )
     # Note the values below are ordered at [5, 195] m.
     wbti_0 = np.full_like(data, fill_value=128.68324)
     wbti_0[2, 2] = 3.1767120
     wbti_0[1:4, 1:4] = 100.0
     wbti_1 = np.full_like(data, fill_value=7.9681854)
     wbti_1[2, 2] = 3.1767120
     wbti_data = np.array(
         [np.broadcast_to(wbti_0, (3, 5, 5)), np.broadcast_to(wbti_1, (3, 5, 5))],
         dtype=np.float32,
     )
     height_points = [5.0, 195.0, 200.0]
     height_attribute = {"positive": "up"}
     wet_bulb_temperature = set_up_variable_cube(
         data, spatial_grid=spatial_grid, name="wet_bulb_temperature"
     )
     wet_bulb_temperature = add_coordinate(
         wet_bulb_temperature, [0, 1, 2], "realization"
     )
     self.wet_bulb_temperature_cube = add_coordinate(
         wet_bulb_temperature,
         height_points,
         "height",
         coord_units="m",
         attributes=height_attribute,
     )
     self.wet_bulb_temperature_cube.data = wbt_data
     # Note that the iris cubelist merge_cube operation sorts the coordinate
     # being merged into ascending order. The cube created below is thus
     # in the incorrect height order, i.e. [5, 195] instead of [195, 5].
     # There is a function in the the PhaseChangeLevel plugin that ensures
     # the height coordinate is in descending order. This is tested here by
     # creating test cubes with both orders.
     height_attribute = {"positive": "down"}
     wet_bulb_integral = set_up_variable_cube(
         data,
         spatial_grid=spatial_grid,
         name="wet_bulb_temperature_integral",
         units="K m",
     )
     wet_bulb_integral = add_coordinate(wet_bulb_integral, [0, 1, 2], "realization")
     self.wet_bulb_integral_cube_inverted = add_coordinate(
         wet_bulb_integral,
         height_points[0:2],
         "height",
         coord_units="m",
         attributes=height_attribute,
     )
     self.wet_bulb_integral_cube_inverted.data = wbti_data
     self.wet_bulb_integral_cube = sort_coord_in_cube(
         self.wet_bulb_integral_cube_inverted, "height", descending=True
     )
     self.expected_snow_sleet = np.full(
         (3, 5, 5), fill_value=66.88566, dtype=np.float32
     )
     self.expected_snow_sleet[:, 1:4, 1:4] = 26.645035
     self.expected_snow_sleet[:, 2, 2] = 124.623375
Exemplo n.º 18
0
    def process(self,
                cube,
                weights=None,
                cycletime=None,
                attributes_dict=None):
        """Calculate weighted blend across the chosen coord, for either
           probabilistic or percentile data. If there is a percentile
           coordinate on the cube, it will blend using the
           PercentileBlendingAggregator but the percentile coordinate must
           have at least two points.

        Args:
            cube (iris.cube.Cube):
                Cube to blend across the coord.
            weights (iris.cube.Cube):
                Cube of blending weights. If None, the diagnostic cube is
                blended with equal weights across the blending dimension.
            cycletime (str):
                The cycletime in a YYYYMMDDTHHMMZ format e.g. 20171122T0100Z.
                This can be used to manually set the forecast reference time
                on the output blended cube. If not set, the most recent
                forecast reference time from the contributing cubes is used.
            attributes_dict (dict or None):
                Changes to cube attributes to be applied after blending. See
                :func:`~improver.metadata.amend.amend_attributes` for required
                format. If mandatory attributes are not set here, default
                values are used.

        Returns:
            iris.cube.Cube:
                containing the weighted blend across the chosen coord.
        Raises:
            TypeError : If the first argument not a cube.
            CoordinateNotFoundError : If coordinate to be collapsed not found
                                      in cube.
            CoordinateNotFoundError : If coordinate to be collapsed not found
                                      in provided weights cube.
            ValueError : If coordinate to be collapsed is not a dimension.
        """
        if not isinstance(cube, iris.cube.Cube):
            msg = ("The first argument must be an instance of iris.cube.Cube "
                   "but is {}.".format(type(cube)))
            raise TypeError(msg)

        if not cube.coords(self.blend_coord):
            msg = "Coordinate to be collapsed not found in cube."
            raise CoordinateNotFoundError(msg)

        blend_coord_dims = cube.coord_dims(self.blend_coord)
        if not blend_coord_dims:
            raise ValueError("Blending coordinate {} has no associated "
                             "dimension".format(self.blend_coord))

        # Ensure input cube and weights cube are ordered equivalently along
        # blending coordinate.
        cube = sort_coord_in_cube(cube, self.blend_coord)
        if weights is not None:
            if not weights.coords(self.blend_coord):
                msg = "Coordinate to be collapsed not found in weights cube."
                raise CoordinateNotFoundError(msg)
            weights = sort_coord_in_cube(weights, self.blend_coord)

        # Check that the time coordinate is single valued if required.
        self.check_compatible_time_points(cube)

        # Check to see if the data is percentile data
        perc_coord = self.check_percentile_coord(cube)

        # Establish metadata changes to be made after blending
        self.cycletime_point = (
            self._get_cycletime_point(cube, cycletime) if self.blend_coord
            in ["forecast_reference_time", "model_id"] else None)
        self._set_coords_to_remove(cube)

        # Do blending and update metadata
        if perc_coord:
            result = self.percentile_weighted_mean(cube, weights, perc_coord)
        else:
            result = self.weighted_mean(cube, weights)
        self._update_blended_metadata(result, attributes_dict)

        # Checks the coordinate dimensions match the first relevant cube in the unblended cubeList.
        result = check_cube_coordinates(
            next(cube.slices_over(self.blend_coord)), result)

        return result
Exemplo n.º 19
0
    def process(self, wet_bulb_temperature, wet_bulb_integral, orog,
                land_sea_mask):
        """
        Use the wet bulb temperature integral to find the altitude at which a
        phase change occurs (e.g. snow to sleet). This is achieved by finding
        the height above sea level at which the integral matches an empirical
        threshold that is expected to correspond with the phase change. This
        empirical threshold is the falling_level_threshold. Fill in missing
        data appropriately.

        Args:
            wet_bulb_temperature (iris.cube.Cube):
                Cube of wet bulb temperatures on height levels.
            wet_bulb_integral (iris.cube.Cube):
                Cube of wet bulb temperature integral (Kelvin-metres).
            orog (iris.cube.Cube):
                Cube of orography (m).
            land_sea_mask (iris.cube.Cube):
                Cube containing a binary land-sea mask.

        Returns:
            iris.cube.Cube:
                Cube of phase change level above sea level (asl).
        """
        wet_bulb_temperature.convert_units('celsius')
        wet_bulb_integral.convert_units('K m')

        # Ensure the wet bulb integral cube's height coordinate is in
        # descending order
        wet_bulb_integral = sort_coord_in_cube(wet_bulb_integral,
                                               'height',
                                               order='descending')

        # Find highest height from height bounds.
        height_bounds = wet_bulb_integral.coord('height').bounds
        heights = wet_bulb_temperature.coord('height').points
        if height_bounds is None:
            highest_height = heights[-1]
        else:
            highest_height = height_bounds[0][-1]

        # Firstly we need to slice over height, x and y
        x_coord = wet_bulb_integral.coord(axis='x').name()
        y_coord = wet_bulb_integral.coord(axis='y').name()
        orography = next(orog.slices([y_coord, x_coord]))
        orog_data = orography.data
        land_sea_data = next(land_sea_mask.slices([y_coord, x_coord])).data

        phase_change = iris.cube.CubeList([])
        slice_list = ['height', y_coord, x_coord]
        for wb_integral, wet_bulb_temp in zip(
                wet_bulb_integral.slices(slice_list),
                wet_bulb_temperature.slices(slice_list)):
            height_points = wb_integral.coord('height').points
            # Calculate phase change level above sea level.
            phase_change_cube = wb_integral[0]
            phase_change_cube.rename('altitude_of_{}_level'.format(
                self.phase_change_name))
            phase_change_cube.units = 'm'
            phase_change_cube.remove_coord('height')

            phase_change_cube.data = self.find_falling_level(
                wb_integral.data, orog_data, height_points)
            # Fill in missing data
            self.fill_in_high_phase_change_falling_levels(
                phase_change_cube.data, orog_data,
                wb_integral.data.max(axis=0), highest_height)
            self.fill_in_sea_points(phase_change_cube.data, land_sea_data,
                                    wb_integral.data.max(axis=0),
                                    wet_bulb_temp.data, heights)
            max_nbhood_orog = self.find_max_in_nbhood_orography(orography)
            updated_phase_cl = self.fill_in_by_horizontal_interpolation(
                phase_change_cube.data, max_nbhood_orog.data, orog_data)
            points = np.where(~np.isfinite(phase_change_cube.data))
            phase_change_cube.data[points] = updated_phase_cl[points]
            # Fill in any remaining points with missing data:
            remaining_points = np.where(np.isnan(phase_change_cube.data))
            phase_change_cube.data[remaining_points] = self.missing_data
            phase_change.append(phase_change_cube)

        phase_change_level = phase_change.merge_cube()
        return phase_change_level
Exemplo n.º 20
0
    def process(self, cubes):
        """
        Use the wet bulb temperature integral to find the altitude at which a
        phase change occurs (e.g. snow to sleet). This is achieved by finding
        the height above sea level at which the integral matches an empirical
        threshold that is expected to correspond with the phase change. This
        empirical threshold is the falling_level_threshold. Fill in missing
        data appropriately.

        Args:
            cubes (iris.cube.CubeList or list of iris.cube.Cube) containing:
                wet_bulb_temperature (iris.cube.Cube):
                    Cube of wet bulb temperatures on height levels.
                wet_bulb_integral (iris.cube.Cube):
                    Cube of wet bulb temperature integral (Kelvin-metres).
                orog (iris.cube.Cube):
                    Cube of orography (m).
                land_sea_mask (iris.cube.Cube):
                    Cube containing a binary land-sea mask, with land points
                    set to one and sea points set to zero.

        Returns:
            iris.cube.Cube:
                Cube of phase change level above sea level (asl).
        """

        names_to_extract = [
            "wet_bulb_temperature",
            "wet_bulb_temperature_integral",
            "surface_altitude",
            "land_binary_mask",
        ]
        if len(cubes) != len(names_to_extract):
            raise ValueError(
                f"Expected {len(names_to_extract)} cubes, found {len(cubes)}")

        wet_bulb_temperature, wet_bulb_integral, orog, land_sea_mask = tuple(
            CubeList(cubes).extract_strict(n) for n in names_to_extract)

        wet_bulb_temperature.convert_units("celsius")
        wet_bulb_integral.convert_units("K m")

        # Ensure the wet bulb integral cube's height coordinate is in
        # descending order
        wet_bulb_integral = sort_coord_in_cube(wet_bulb_integral,
                                               "height",
                                               descending=True)

        # Find highest height from height bounds.
        wbt_height_points = wet_bulb_temperature.coord("height").points
        if wet_bulb_integral.coord("height").bounds is None:
            highest_height = wbt_height_points[-1]
        else:
            highest_height = wet_bulb_integral.coord("height").bounds[0][-1]

        # Firstly we need to slice over height, x and y
        x_coord = wet_bulb_integral.coord(axis="x").name()
        y_coord = wet_bulb_integral.coord(axis="y").name()
        orography = next(orog.slices([y_coord, x_coord]))
        land_sea_data = next(land_sea_mask.slices([y_coord, x_coord])).data
        max_nbhood_orog = self.find_max_in_nbhood_orography(orography)

        phase_change = None
        slice_list = ["height", y_coord, x_coord]
        for wb_integral, wet_bulb_temp in zip(
                wet_bulb_integral.slices(slice_list),
                wet_bulb_temperature.slices(slice_list),
        ):
            phase_change_data = self._calculate_phase_change_level(
                wet_bulb_temp.data,
                wb_integral.data,
                orography.data,
                max_nbhood_orog.data,
                land_sea_data,
                wbt_height_points,
                wb_integral.coord("height").points,
                highest_height,
            )

            # preserve dimensionality of input cube (in case of scalar or
            # length 1 dimensions)
            if phase_change is None:
                phase_change = phase_change_data
            elif not isinstance(phase_change, list):
                phase_change = [phase_change]
                phase_change.append(phase_change_data)
            else:
                phase_change.append(phase_change_data)

        phase_change_level = self.create_phase_change_level_cube(
            wet_bulb_temperature,
            np.ma.masked_array(phase_change, dtype=np.float32))

        return phase_change_level
Exemplo n.º 21
0
    def setUp(self):
        """Set up orography and land-sea mask cubes. Also create temperature,
        pressure, and relative humidity cubes that contain multiple height
        levels."""

        data = np.ones((3, 3), dtype=np.float32)

        self.orog = set_up_variable_cube(
            data, name='surface_altitude', units='m', spatial_grid='equalarea')
        self.land_sea = set_up_variable_cube(
            data, name='land_binary_mask', units=1, spatial_grid='equalarea')

        wbt_data = np.array(
            [[[[271.46216, 271.46216, 271.46216],
               [271.46216, 270.20343, 271.46216],
               [271.46216, 271.46216, 271.46216]],
              [[271.46216, 271.46216, 271.46216],
               [271.46216, 270.20343, 271.46216],
               [271.46216, 271.46216, 271.46216]]],
             [[[274.4207, 274.4207, 274.4207],
               [274.4207, 271.46216, 274.4207],
               [274.4207, 274.4207, 274.4207]],
              [[274.4207, 274.4207, 274.4207],
               [274.4207, 271.46216, 274.4207],
               [274.4207, 274.4207, 274.4207]]],
             [[[275.0666, 275.0666, 275.0666],
               [275.0666, 274.4207, 275.0666],
               [275.0666, 275.0666, 275.0666]],
              [[275.0666, 275.0666, 275.0666],
               [275.0666, 274.4207, 275.0666],
               [275.0666, 275.0666, 275.0666]]]], dtype=np.float32)

        # Note the values below are ordered at [5, 195] m.

        wbti_data = np.array(
            [[[[128.68324, 128.68324, 128.68324],
               [128.68324, 3.176712, 128.68324],
               [128.68324, 128.68324, 128.68324]],
              [[128.68324, 128.68324, 128.68324],
               [128.68324, 3.176712, 128.68324],
               [128.68324, 128.68324, 128.68324]]],
             [[[7.9681854, 7.9681854, 7.9681854],
               [7.9681854, 3.176712, 7.9681854],
               [7.9681854, 7.9681854, 7.9681854]],
              [[7.9681854, 7.9681854, 7.9681854],
               [7.9681854, 3.176712, 7.9681854],
               [7.9681854, 7.9681854, 7.9681854]]]], dtype=np.float32)

        height_points = [5., 195., 200.]
        height_attribute = {"positive": "up"}

        wet_bulb_temperature = set_up_variable_cube(
            data, spatial_grid='equalarea', name='wet_bulb_temperature')
        wet_bulb_temperature = add_coordinate(
            wet_bulb_temperature, [0, 1], 'realization')
        self.wet_bulb_temperature_cube = add_coordinate(
            wet_bulb_temperature, height_points, 'height',
            coord_units='m', attributes=height_attribute)
        self.wet_bulb_temperature_cube.data = wbt_data

        # Note that the iris cubelist merge_cube operation sorts the coordinate
        # being merged into ascending order. The cube created below is thus
        # in the incorrect height order, i.e. [5, 195] instead of [195, 5].
        # There is a function in the the PhaseChangeLevel plugin that ensures
        # the height coordinate is in descending order. This is tested here by
        # creating test cubes with both orders.

        height_attribute = {"positive": "down"}

        wet_bulb_integral = set_up_variable_cube(
            data, spatial_grid='equalarea',
            name='wet_bulb_temperature_integral', units='K m',)
        wet_bulb_integral = add_coordinate(
            wet_bulb_integral, [0, 1], 'realization')
        self.wet_bulb_integral_cube_inverted = add_coordinate(
            wet_bulb_integral, height_points[0:2], 'height',
            coord_units='m', attributes=height_attribute)
        self.wet_bulb_integral_cube_inverted.data = wbti_data
        self.wet_bulb_integral_cube = sort_coord_in_cube(
            self.wet_bulb_integral_cube_inverted, 'height', order='descending')
    def setUp(self):
        """Set up orography and land-sea mask cubes. Also create temperature,
        pressure, and relative humidity cubes that contain multiple height
        levels."""

        data = np.ones((3, 3), dtype=np.float32)

        self.orog = set_up_variable_cube(data,
                                         name="surface_altitude",
                                         units="m",
                                         spatial_grid="equalarea")
        self.land_sea = set_up_variable_cube(data,
                                             name="land_binary_mask",
                                             units=1,
                                             spatial_grid="equalarea")

        wbt_0 = np.array([
            [271.46216, 271.46216, 271.46216],
            [271.46216, 270.20343, 271.46216],
            [271.46216, 271.46216, 271.46216],
        ])
        wbt_1 = np.array([
            [274.4207, 274.4207, 274.4207],
            [274.4207, 271.46216, 274.4207],
            [274.4207, 274.4207, 274.4207],
        ])
        wbt_2 = np.array([
            [275.0666, 275.0666, 275.0666],
            [275.0666, 274.4207, 275.0666],
            [275.0666, 275.0666, 275.0666],
        ])
        wbt_data = np.array(
            [
                np.broadcast_to(wbt_0, (3, 3, 3)),
                np.broadcast_to(wbt_1, (3, 3, 3)),
                np.broadcast_to(wbt_2, (3, 3, 3)),
            ],
            dtype=np.float32,
        )

        # Note the values below are ordered at [5, 195] m.
        wbti_0 = np.array([
            [128.68324, 128.68324, 128.68324],
            [128.68324, 3.176712, 128.68324],
            [128.68324, 128.68324, 128.68324],
        ])
        wbti_1 = np.array([
            [7.9681854, 7.9681854, 7.9681854],
            [7.9681854, 3.176712, 7.9681854],
            [7.9681854, 7.9681854, 7.9681854],
        ])
        wbti_data = np.array(
            [
                np.broadcast_to(wbti_0, (3, 3, 3)),
                np.broadcast_to(wbti_1, (3, 3, 3))
            ],
            dtype=np.float32,
        )

        height_points = [5.0, 195.0, 200.0]
        height_attribute = {"positive": "up"}

        wet_bulb_temperature = set_up_variable_cube(
            data, spatial_grid="equalarea", name="wet_bulb_temperature")
        wet_bulb_temperature = add_coordinate(wet_bulb_temperature, [0, 1, 2],
                                              "realization")
        self.wet_bulb_temperature_cube = add_coordinate(
            wet_bulb_temperature,
            height_points,
            "height",
            coord_units="m",
            attributes=height_attribute,
        )
        self.wet_bulb_temperature_cube.data = wbt_data

        # Note that the iris cubelist merge_cube operation sorts the coordinate
        # being merged into ascending order. The cube created below is thus
        # in the incorrect height order, i.e. [5, 195] instead of [195, 5].
        # There is a function in the the PhaseChangeLevel plugin that ensures
        # the height coordinate is in descending order. This is tested here by
        # creating test cubes with both orders.

        height_attribute = {"positive": "down"}

        wet_bulb_integral = set_up_variable_cube(
            data,
            spatial_grid="equalarea",
            name="wet_bulb_temperature_integral",
            units="K m",
        )
        wet_bulb_integral = add_coordinate(wet_bulb_integral, [0, 1, 2],
                                           "realization")
        self.wet_bulb_integral_cube_inverted = add_coordinate(
            wet_bulb_integral,
            height_points[0:2],
            "height",
            coord_units="m",
            attributes=height_attribute,
        )
        self.wet_bulb_integral_cube_inverted.data = wbti_data
        self.wet_bulb_integral_cube = sort_coord_in_cube(
            self.wet_bulb_integral_cube_inverted, "height", descending=True)

        self.expected_snow_sleet = np.ones(
            (3, 3, 3), dtype=np.float32) * 66.88566
Exemplo n.º 23
0
    def _create_output_cubes(self, orogenh_data, reference_cube):
        """
        Create two output cubes of orographic enhancement on different grids.
        Casts coordinate points and bounds explicitly to np.float32.

        Args:
            orogenh_data (numpy.ndarray):
                Orographic enhancement value in mm h-1
            reference_cube (iris.cube.Cube):
                Cube with the correct time and forecast period coordinates on
                the UK standard grid

        Returns:
            (tuple): tuple containing:
                **orogenh** (iris.cube.Cube):
                    Orographic enhancement cube on 1 km UKPP grid (m s-1)
                **orogenh_standard_grid** (iris.cube.Cube):
                    Orographic enhancement cube on the UK standard grid, padded
                    with masked np.nans where outside the UKPP domain (m s-1)
        """
        # create cube containing high resolution data in mm/h
        x_coord = self.topography.coord(axis='x')
        y_coord = self.topography.coord(axis='y')
        for coord in [x_coord, y_coord]:
            coord.points = coord.points.astype(np.float32)
            if coord.bounds is not None:
                coord.bounds = coord.bounds.astype(np.float32)

        aux_coords = []
        for coord in ['time', 'forecast_reference_time', 'forecast_period']:
            aux_coords.append((reference_cube.coord(coord), None))

        attributes = {}
        for attr in ['institution', 'source', 'mosg__model_configuration']:
            try:
                attributes[attr] = reference_cube.attributes[attr]
            except KeyError:
                continue

        orogenh = iris.cube.Cube(orogenh_data,
                                 long_name="orographic_enhancement",
                                 units="mm h-1",
                                 attributes=attributes,
                                 dim_coords_and_dims=[(y_coord, 0),
                                                      (x_coord, 1)],
                                 aux_coords_and_dims=aux_coords)
        orogenh.convert_units("m s-1")

        # regrid the orographic enhancement cube onto the standard grid and
        # mask extrapolated points
        orogenh_standard_grid = orogenh.regrid(
            reference_cube, iris.analysis.Linear(extrapolation_mode='mask'))

        for axis in ['x', 'y']:
            orogenh_standard_grid = sort_coord_in_cube(
                orogenh_standard_grid, orogenh_standard_grid.coord(axis=axis))
            orogenh_standard_grid.coord(
                axis=axis).points = (orogenh_standard_grid.coord(
                    axis=axis).points.astype(np.float32))
            if orogenh_standard_grid.coord(axis=axis).bounds is not None:
                orogenh_standard_grid.coord(
                    axis=axis).bounds = (orogenh_standard_grid.coord(
                        axis=axis).bounds.astype(np.float32))

        # add any relevant grid definition attributes
        for data_cube, grid_cube in zip([orogenh, orogenh_standard_grid],
                                        [self.topography, reference_cube]):
            for key, val in grid_cube.attributes.items():
                if 'mosg__grid' in key:
                    data_cube.attributes[key] = val

        return orogenh, orogenh_standard_grid
Exemplo n.º 24
0
    def _regrid_and_populate(self, temperature, humidity, pressure, uwind,
                             vwind, topography):
        """
        Regrids input variables onto the high resolution orography field, then
        populates the class instance with regridded variables before converting
        to SI units.  Also calculates V.gradZ as a class member.

        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 1 km UKPP domain grid
        """
        # convert topography grid, datatype and units
        for axis in ['x', 'y']:
            topography = sort_coord_in_cube(topography,
                                            topography.coord(axis=axis))
        topography = enforce_coordinate_ordering(topography, [
            topography.coord(axis='y').name(),
            topography.coord(axis='x').name()
        ])
        self.topography = topography.copy(
            data=topography.data.astype(np.float32))
        self.topography.convert_units('m')

        # rotate winds
        try:
            uwind, vwind = rotate_winds(uwind, vwind,
                                        topography.coord_system())
        except ValueError as err:
            if 'Duplicate coordinates are not permitted' in str(err):
                # ignore error raised if uwind and vwind do not need rotating
                pass
            else:
                raise ValueError(str(err))
        else:
            # remove auxiliary spatial coordinates from rotated winds
            for cube in [uwind, vwind]:
                for axis in ['x', 'y']:
                    cube.remove_coord(cube.coord(axis=axis, dim_coords=False))

        # regrid and convert input variables
        self.temperature = self._regrid_variable(temperature, 'kelvin')
        self.humidity = self._regrid_variable(humidity, '1')
        self.pressure = self._regrid_variable(pressure, 'Pa')
        self.uwind = self._regrid_variable(uwind, 'm s-1')
        self.vwind = self._regrid_variable(vwind, 'm s-1')

        # calculate orography gradients
        gradx, grady = self._orography_gradients()

        # calculate v.gradZ
        self.vgradz = (np.multiply(gradx.data, self.uwind.data) +
                       np.multiply(grady.data, self.vwind.data))
Exemplo n.º 25
0
    def process(self, cube, weights=None):
        """Calculate weighted blend across the chosen coord, for either
           probabilistic or percentile data. If there is a percentile
           coordinate on the cube, it will blend using the
           PercentileBlendingAggregator but the percentile coordinate must
           have at least two points.

        Args:
            cube (iris.cube.Cube):
                Cube to blend across the coord.
            weights (iris.cube.Cube):
                Cube of blending weights. If None, the diagnostic cube is
                blended with equal weights across the blending dimension.
        Returns:
            result (iris.cube.Cube):
                containing the weighted blend across the chosen coord.
        Raises:
            TypeError : If the first argument not a cube.
            CoordinateNotFoundError : If coordinate to be collapsed not found
                                      in cube.
            CoordinateNotFoundError : If coordinate to be collapsed not found
                                      in provided weights cube.
            ValueError : If coordinate to be collapsed is not a dimension.
        """
        if not isinstance(cube, iris.cube.Cube):
            msg = ('The first argument must be an instance of iris.cube.Cube '
                   'but is {}.'.format(type(cube)))
            raise TypeError(msg)

        if not cube.coords(self.coord):
            msg = 'Coordinate to be collapsed not found in cube.'
            raise CoordinateNotFoundError(msg)

        coord_dim = cube.coord_dims(self.coord)
        if not coord_dim:
            raise ValueError('Blending coordinate {} has no associated '
                             'dimension'.format(self.coord))

        # Ensure input cube and weights cube are ordered equivalently along
        # blending coordinate.
        cube = sort_coord_in_cube(cube, self.coord, order="ascending")
        if weights is not None:
            if not weights.coords(self.coord):
                msg = 'Coordinate to be collapsed not found in weights cube.'
                raise CoordinateNotFoundError(msg)
            weights = sort_coord_in_cube(weights,
                                         self.coord,
                                         order="ascending")

        # Check that the time coordinate is single valued if required.
        self.check_compatible_time_points(cube)

        # Check to see if the data is percentile data
        perc_coord = self.check_percentile_coord(cube)

        # Percentile aggregator
        if perc_coord:
            cube_new = self.percentile_weighted_mean(cube, weights, perc_coord)
        # Weighted mean
        else:
            cube_new = self.weighted_mean(cube, weights)

        # Modify the cube metadata and add to the cubelist.
        result = conform_metadata(cube_new,
                                  cube,
                                  coord=self.coord,
                                  cycletime=self.cycletime)

        if isinstance(cube.data, np.ma.core.MaskedArray):
            result.data = np.ma.array(result.data)

        return result
Exemplo n.º 26
0
def calculate_blending_weights(cube,
                               blend_coord,
                               method,
                               wts_dict=None,
                               weighting_coord=None,
                               coord_unit=None,
                               y0val=None,
                               ynval=None,
                               cval=None,
                               dict_coord=None):
    """
    Wrapper for plugins to calculate blending weights using the command line
    options specified.

    Args:
        cube (iris.cube.Cube):
            Cube of input data to be blended
        blend_coord (str):
            Coordinate over which blending will be performed (eg "model" for
            grid blending)
        method (str):
            Weights calculation method ("linear", "nonlinear", "dict" or
            "mask")

    Kwargs:
        wts_dict (str):
            File path to json file with parameters for linear weights
            calculation
        weighting_coord (str):
            Coordinate over which linear weights should be calculated from dict
        coord_unit (str or cf_units.Unit):
            Unit of blending coordinate (for default weights plugins)
        y0val (float):
            Intercept parameter for default linear weights plugin
        ynval (float):
            Gradient parameter for default linear weights plugin
        cval (float):
            Parameter for default non-linear weights plugin
        dict_coord (str):
            The coordinate that will be used when accessing the weights from
            the weights dictionary.

    Returns:
        weights (np.ndarray):
            1D array of weights corresponding to slices in ascending order
            of blending coordinate.  (Note: ChooseLinearWeights has the
            option to create a 3D array of spatially-varying weights with the
            "mask" option, however this is not currently supported by the
            blending plugin.)
    """
    # sort input cube by blending coordinate
    cube = sort_coord_in_cube(cube, blend_coord, order="ascending")

    # calculate blending weights
    if method == "dict":
        # calculate linear weights from a dictionary
        with open(wts_dict, 'r') as wts:
            weights_dict = json.load(wts)
        weights_cube = ChooseWeightsLinear(
            weighting_coord, weights_dict,
            config_coord_name=dict_coord).process(cube)

        # sort weights cube by blending coordinate
        weights = sort_coord_in_cube(weights_cube,
                                     blend_coord,
                                     order="ascending")

    elif method == "linear":
        weights = ChooseDefaultWeightsLinear(y0val=y0val, ynval=ynval).process(
            cube, blend_coord, coord_unit=coord_unit)

    elif method == "nonlinear":
        # this is set here rather than in the CLI arguments in order to check
        # for invalid argument combinations
        cvalue = cval if cval else 0.85
        weights = ChooseDefaultWeightsNonLinear(cvalue).process(
            cube, blend_coord, coord_unit=coord_unit)

    return weights