def test_process_none(self):
     """Test that the function ignores None types."""
     result1 = self.cube1
     result2 = None
     enforce_float32_precision([result1, result2])
     self.assertEqual(result1.dtype, np.float32)
     self.assertIsNone(result2)
Ejemplo n.º 2
0
    def __init__(self,
                 a_over_s_cube,
                 sigma_cube,
                 pporo_cube,
                 modoro_cube,
                 modres,
                 z0_cube=None,
                 height_levels_cube=None):
        """Initialise the RoughnessCorrection instance.

        Args:
            a_over_s_cube (iris.cube.Cube):
                2D - model silhouette roughness on pp grid. dimensionless
            sigma_cube (iris.cube.Cube):
                2D - standard deviation of model orography height on pp grid.
                In m.
            pporo_cube (iris.cube.Cube):
                2D - pp orography. In m
            modoro_cube (iris.cube.Cube):
                2D - model orography interpolated on pp grid. In m
            modres (float):
                original average model resolution in m
            height_levels_cube (iris.cube.Cube):
                3D or 1D - height of input velocity field.
                Can be position dependent
            z0_cube (iris.cube.Cube):
                2D - vegetative roughness length in m. If not given, do not do
                any RC
        """
        enforce_float32_precision([
            a_over_s_cube, sigma_cube, pporo_cube, modoro_cube, z0_cube,
            height_levels_cube
        ])

        # Standard Python 'float' type is either single or double depending on
        # system and there is no reliable method of finding which from the
        # variable. So force to numpy.float32 by default.
        modres = np.float32(modres)

        x_name, y_name, _, _ = self.find_coord_names(pporo_cube)
        # Some checking that all the grids match
        if not (self.check_ancils(a_over_s_cube, sigma_cube, z0_cube,
                                  pporo_cube, modoro_cube)):
            raise ValueError("ancillary grids are not consistent")
        # I want this ordering. Careful: iris.cube.Cube.slices is unreliable.
        self.a_over_s = next(a_over_s_cube.slices([y_name, x_name]))
        self.sigma = next(sigma_cube.slices([y_name, x_name]))
        try:
            self.z_0 = next(z0_cube.slices([y_name, x_name]))
        except AttributeError:
            self.z_0 = z0_cube
        self.pp_oro = next(pporo_cube.slices([y_name, x_name]))
        self.model_oro = next(modoro_cube.slices([y_name, x_name]))
        self.ppres = self.calc_av_ppgrid_res(pporo_cube)
        self.modres = modres
        self.height_levels = height_levels_cube
        self.x_name = None
        self.y_name = None
        self.z_name = None
        self.t_name = None
 def test_process_list(self):
     """Test that the function will return a list of cubes with
        float32 precision."""
     result1 = self.cube1
     result2 = self.cube2
     enforce_float32_precision([result1, result2])
     self.assertEqual(result1.dtype, np.float32)
     self.assertEqual(result2.dtype, np.float32)
Ejemplo n.º 4
0
    def process(self, input_cube):
        """Adjust the 4d wind field - cube - (x, y, z including times).

        Args:
            input_cube (iris.cube.Cube):
                The wind cube to be operated upon. Should be wind speed on
                height_levels for all desired forecast times.

        Returns:
            output_cube (iris.cube.Cube):
                The 4d wind field with roughness and height correction
                applied in the same order as the input cube.

        Raises
        ------
        TypeError: If input_cube is not a cube.

        """
        if not isinstance(input_cube, iris.cube.Cube):
            msg = "wind input is not a cube, but {}"
            raise TypeError(msg.format(type(input_cube)))
        enforce_float32_precision(input_cube)
        (self.x_name, self.y_name, self.z_name,
         self.t_name) = self.find_coord_names(input_cube)
        xwp, ywp, zwp, twp = self.find_coord_order(input_cube)
        if np.isnan(twp):
            input_cube.transpose([ywp, xwp, zwp])
        else:
            input_cube.transpose([ywp, xwp, zwp, twp])  # problems with slices
        rchc_list = iris.cube.CubeList()
        if self.z_0 is None:
            z0_data = None
        else:
            z0_data = self.z_0.data
        roughness_correction = RoughnessCorrectionUtilities(
            self.a_over_s.data, self.sigma.data, z0_data, self.pp_oro.data,
            self.model_oro.data, self.ppres, self.modres)
        self.check_wind_ancil(xwp, ywp)
        hld = self.find_heightgrid(input_cube)
        for time_slice in input_cube.slices_over("time"):
            if np.isnan(time_slice.data).any() or (time_slice.data < 0.).any():
                msg = ('{} has invalid wind data')
                raise ValueError(msg.format(time_slice.coord(self.t_name)))
            rc_hc = copy.deepcopy(time_slice)
            rc_hc.data = roughness_correction.do_rc_hc_all(
                hld, time_slice.data)
            rchc_list.append(rc_hc)
        output_cube = rchc_list.merge_cube()
        # reorder input_cube and output_cube as original
        if np.isnan(twp):
            input_cube.transpose(np.argsort([ywp, xwp, zwp]))
            output_cube.transpose(np.argsort([ywp, xwp, zwp]))
        else:
            input_cube.transpose(np.argsort([ywp, xwp, zwp, twp]))
            output_cube.transpose(np.argsort([twp, ywp, xwp, zwp]))
        return output_cube
Ejemplo n.º 5
0
    def process(self, cube_ens_wdir):
        """Create a cube containing the wind direction averaged over the
        ensemble realizations.

        Args:
            cube_ens_wdir (iris.cube.Cube):
                Cube containing wind direction from multiple ensemble
                realizations.

        Returns:
            cube_mean_wdir (iris.cube.Cube):
                Cube containing the wind direction averaged from the
                ensemble realizations.
            cube_r_vals (np.ndarray):
                3D array - Radius taken from average complex wind direction
                angle.
            cube_confidence_measure (np.ndarray):
                3D array - The average distance from mean normalised - used
                as a confidence value.

        Raises
        ------
        TypeError: If cube_wdir is not a cube.

        """

        if not isinstance(cube_ens_wdir, iris.cube.Cube):
            msg = "Wind direction input is not a cube, but {}"
            raise TypeError(msg.format(type(cube_ens_wdir)))

        try:
            cube_ens_wdir.convert_units("degrees")
        except ValueError as err:
            msg = "Input cube cannot be converted to degrees: {}".format(err)
            raise ValueError(msg)

        # Force input cube to float32.
        enforce_float32_precision(cube_ens_wdir)

        self.n_realizations = len(cube_ens_wdir.coord('realization').points)
        y_coord_name = cube_ens_wdir.coord(axis="y").name()
        x_coord_name = cube_ens_wdir.coord(axis="x").name()
        for wdir_slice in cube_ens_wdir.slices(
            ["realization", y_coord_name, x_coord_name]):
            self._reset()
            # Extract wind direction data.
            self.wdir_complex = self.deg_to_complex(wdir_slice.data)
            self.realization_axis, = wdir_slice.coord_dims("realization")

            # Copies input cube and remove realization dimension to create
            # cubes for storing results.
            self.wdir_slice_mean = next(wdir_slice.slices_over("realization"))
            self.wdir_slice_mean.remove_coord("realization")

            # Derive average wind direction.
            self.calc_wind_dir_mean()

            # Find radius values for wind direction average.
            self.find_r_values()

            # Calculate the confidence measure based on the difference
            # between the complex average and the individual ensemble
            # realizations.
            self.calc_confidence_measure()

            # Finds any meaningless averages and substitute with
            # the wind direction taken from the first ensemble realization.
            # Mask True if r values below threshold.
            where_low_r = np.where(self.r_vals_slice.data < self.r_thresh,
                                   True, False)
            # If the any point in the array contains poor r-values,
            # trigger decider function.
            if where_low_r.any():
                self.wind_dir_decider(where_low_r, wdir_slice)

            # Append to cubelists.
            self.wdir_cube_list.append(self.wdir_slice_mean)
            self.r_vals_cube_list.append(self.r_vals_slice)
            self.confidence_measure_cube_list.append(self.confidence_slice)

        # Combine cubelists into cube.
        cube_mean_wdir = self.wdir_cube_list.merge_cube()
        cube_r_vals = self.r_vals_cube_list.merge_cube()
        cube_confidence_measure = (
            self.confidence_measure_cube_list.merge_cube())

        # Check that the dimensionality of coordinates of the output cube
        # matches the input cube.
        first_slice = next(cube_ens_wdir.slices_over(["realization"]))
        cube_mean_wdir = check_cube_coordinates(first_slice, cube_mean_wdir)

        # Change cube identifiers.
        cube_mean_wdir.add_cell_method(CellMethod("mean",
                                                  coords="realization"))
        cube_r_vals.long_name = "radius_of_complex_average_wind_from_direction"
        cube_r_vals.units = None
        cube_confidence_measure.long_name = (
            "confidence_measure_of_wind_from_direction")
        cube_confidence_measure.units = None

        return cube_mean_wdir, cube_r_vals, cube_confidence_measure
Ejemplo n.º 6
0
    def process(cube_ens_wdir):
        """Create a cube containing the wind direction averaged over the
        ensemble realizations.

        Args:
            cube_ens_wdir (iris.cube.Cube):
                Cube containing wind direction from multiple ensemble
                realizations.

        Returns:
            cube_mean_wdir (iris.cube.Cube):
                Cube containing the wind direction averaged from the
                ensemble realizations.
            cube_r_vals (np.ndarray):
                3D array - Radius taken from average complex wind direction
                angle.
            cube_confidence_measure (np.ndarray):
                3D array - The average distance from mean normalised - used
                as a confidence value.

        Raises
        ------
        TypeError: If cube_wdir is not a cube.

        """

        # Any points where the r-values are below the threshold is regarded as
        # containing ambigous data.
        r_thresh = 0.01

        if not isinstance(cube_ens_wdir, iris.cube.Cube):
            msg = "Wind direction input is not a cube, but {}"
            raise TypeError(msg.format(type(cube_ens_wdir)))

        try:
            cube_ens_wdir.convert_units("degrees")
        except ValueError as err:
            msg = "Input cube cannot be converted to degrees: {}".format(err)
            raise ValueError(msg)

        # Force input cube to float32.
        enforce_float32_precision(cube_ens_wdir)

        # Creates cubelists to hold data.
        wdir_cube_list = iris.cube.CubeList()
        r_vals_cube_list = iris.cube.CubeList()
        confidence_measure_cube_list = iris.cube.CubeList()

        y_coord_name = cube_ens_wdir.coord(axis="y").name()
        x_coord_name = cube_ens_wdir.coord(axis="x").name()
        for slice_ens_wdir in cube_ens_wdir.slices(["realization",
                                                    y_coord_name,
                                                    x_coord_name]):
            # Extract wind direction data.
            wind_dir_deg = slice_ens_wdir.data
            realization_axis = slice_ens_wdir.coord_dims("realization")[0]

            # Copies input cube and remove realization dimension to create
            # cubes for storing results.
            slice_mean_wdir = next(slice_ens_wdir.slices_over("realization"))
            slice_mean_wdir.remove_coord("realization")

            # Convert wind direction from degrees to complex numbers.
            wind_dir_complex = WindDirection.deg_to_complex(wind_dir_deg)

            # Find the complex average -  which actually signifies a point
            # between all of the data points in POLAR coordinates.
            # NOT the average DEGREE ANGLE.
            wind_dir_complex_mean = np.mean(wind_dir_complex,
                                            axis=realization_axis)

            # Convert complex average values to degrees to produce average
            # wind direction.
            wind_dir_deg_mean = WindDirection.complex_to_deg(
                wind_dir_complex_mean)

            # Find radius values for wind direction average.
            r_vals = WindDirection.find_r_values(wind_dir_complex_mean)

            # Calculate the confidence measure based on the difference
            # between the complex average and the individual ensemble
            # realizations.
            # TODO: This will still need some further investigation.
            #        This is will be the subject of another ticket.
            confidence_measure = WindDirection.calc_confidence_measure(
                wind_dir_complex, wind_dir_deg_mean, r_vals, r_thresh,
                realization_axis)

            # Finds any meaningless averages and substitute with
            # the wind direction taken from the first ensemble realization.
            wind_dir_deg_mean = WindDirection.wind_dir_decider(
                wind_dir_deg, wind_dir_deg_mean, r_vals, r_thresh)

            # Save data into cubes (create new cubes for r and
            # confidence measure data).
            slice_mean_wdir.data = wind_dir_deg_mean
            slice_r_vals = slice_mean_wdir.copy(data=r_vals)
            slice_confidence_measure = (
                slice_mean_wdir.copy(data=confidence_measure))
            # Append to cubelists.
            wdir_cube_list.append(slice_mean_wdir)
            r_vals_cube_list.append(slice_r_vals)
            confidence_measure_cube_list.append(slice_confidence_measure)

        # Combine cubelists into cube.
        cube_mean_wdir = wdir_cube_list.merge_cube()
        cube_r_vals = r_vals_cube_list.merge_cube()
        cube_confidence_measure = confidence_measure_cube_list.merge_cube()

        # Check that the dimensionality of coordinates of the output cube
        # matches the input cube.
        first_slice = next(cube_ens_wdir.slices_over(["realization"]))
        cube_mean_wdir = check_cube_coordinates(first_slice, cube_mean_wdir)

        # Change cube identifiers.
        cube_mean_wdir.add_cell_method(CellMethod("mean",
                                                  coords="realization"))
        cube_r_vals.long_name = "radius_of_complex_average_wind_from_direction"
        cube_r_vals.units = None
        cube_confidence_measure.long_name = (
            "confidence_measure_of_wind_from_direction")
        cube_confidence_measure.units = None

        return cube_mean_wdir, cube_r_vals, cube_confidence_measure
 def test_basic(self):
     """Test that the function will return a single iris.cube.Cube with
        float32 precision."""
     result1 = self.cube1
     enforce_float32_precision(result1)
     self.assertEqual(result1.dtype, np.float32)
Ejemplo n.º 8
0
    def process(self, temperature_cube, orography_cube, land_sea_mask_cube):
        """Calculates the lapse rate from the temperature and orography cubes.

        Args:
            temperature_cube (iris.cube.Cube):
                Cube of air temperatures (K).

            orography_cube (Iris.cube.Cube):
                Cube containing orography data (metres)

            land_sea_mask_cube (iris.cube.Cube):
                Cube containing a binary land-sea mask. True for land-points
                and False for Sea.

        Returns:
            lapse_rate_cube (iris.cube.Cube):
                Cube containing lapse rate (Km-1)

        Raises
        ------
        TypeError: If input cubes are not cubes
        ValueError: If input cubes are the wrong units.

        """

        if not isinstance(temperature_cube, iris.cube.Cube):
            msg = "Temperature input is not a cube, but {}"
            raise TypeError(msg.format(type(temperature_cube)))

        if not isinstance(orography_cube, iris.cube.Cube):
            msg = "Orography input is not a cube, but {}"
            raise TypeError(msg.format(type(orography_cube)))

        if not isinstance(land_sea_mask_cube, iris.cube.Cube):
            msg = "Land/Sea mask input is not a cube, but {}"
            raise TypeError(msg.format(type(land_sea_mask_cube)))

        # Converts cube units.
        temperature_cube.convert_units('K')
        orography_cube.convert_units('metres')

        enforce_float32_precision([temperature_cube])

        # Extract x/y co-ordinates.
        x_coord = temperature_cube.coord(axis='x').name()
        y_coord = temperature_cube.coord(axis='y').name()

        # Extract orography and land/sea mask data.
        orography_data = next(orography_cube.slices([y_coord, x_coord])).data
        land_sea_mask = next(land_sea_mask_cube.slices([y_coord,
                                                        x_coord])).data
        # Fill sea points with NaN values.
        orography_data = np.where(land_sea_mask, orography_data, np.nan)

        # Extract data array dimensions to define output arrays.
        dataarray_shape = next(temperature_cube.slices([y_coord,
                                                        x_coord])).shape
        dataarray_size = dataarray_shape[0] * dataarray_shape[1]

        # Array containing all of the subsections extracted from data array.
        # Also enforce single precision to speed up calculations.
        all_temp_subsections = np.zeros(
            (dataarray_size, self.nbhoodarray_size), dtype=np.float32)
        all_orog_subsections = np.zeros(
            (dataarray_size, self.nbhoodarray_size), dtype=np.float32)

        # Attempts to extract realizations. If cube doesn't contain the
        # dimension then place within list.
        try:
            slices_over_realization = temperature_cube.slices_over(
                "realization")
        except iris.exceptions.CoordinateNotFoundError:
            slices_over_realization = [temperature_cube]

        # Creates cube list to hold lapse rate data.
        lapse_rate_cube_list = iris.cube.CubeList([])

        for temp_slice in slices_over_realization:

            # Create slice to store lapse rate values.
            lapse_rate_slice = temp_slice

            temperature_data = temp_slice.data

            # Fill sea points with NaN values. Can't use Numpy mask since not
            # recognised by "generic_filter" function.
            temperature_data = np.where(land_sea_mask, temperature_data,
                                        np.nan)

            # Saves all neighbourhoods into "all_temp_subsections".
            # cval is value given to points outside the array.
            fnc = SaveNeighbourhood(allbuffers=all_temp_subsections)
            generic_filter(temperature_data,
                           fnc.filter,
                           size=self.nbhood_size,
                           mode='constant',
                           cval=np.nan)

            fnc = SaveNeighbourhood(allbuffers=all_orog_subsections)
            generic_filter(orography_data,
                           fnc.filter,
                           size=self.nbhood_size,
                           mode='constant',
                           cval=np.nan)

            # height_diff_mask is True for points where the height
            # difference between the central point and its neighbours
            # is > max_height_diff.
            height_diff_mask = self._create_heightdiff_mask(
                all_orog_subsections)

            # Mask points with extreme height differences as NaN.
            all_orog_subsections = np.where(height_diff_mask, np.nan,
                                            all_orog_subsections)
            all_temp_subsections = np.where(height_diff_mask, np.nan,
                                            all_temp_subsections)

            # Loop through both arrays and find gradient of each subsection.
            # The gradient indicates lapse rate - save into another array.
            # TODO: This for loop is the bottleneck in the code and needs to
            # be parallelised.
            lapse_rate_array = [
                self._calc_lapse_rate(temp, orog) for temp, orog in zip(
                    all_temp_subsections, all_orog_subsections)
            ]

            lapse_rate_array = np.array(
                lapse_rate_array, dtype=np.float32).reshape(dataarray_shape)

            # Enforces upper and lower limits on lapse rate values.
            lapse_rate_array = np.where(lapse_rate_array < self.min_lapse_rate,
                                        self.min_lapse_rate, lapse_rate_array)
            lapse_rate_array = np.where(lapse_rate_array > self.max_lapse_rate,
                                        self.max_lapse_rate, lapse_rate_array)

            lapse_rate_slice.data = lapse_rate_array
            lapse_rate_cube_list.append(lapse_rate_slice)

        lapse_rate_cube = lapse_rate_cube_list.merge_cube()
        lapse_rate_cube.rename('temperature_lapse_rate')
        lapse_rate_cube.long_name = "lapse_rate"

        return lapse_rate_cube