def calculate_sleet_probability(prob_of_snow, prob_of_rain):
    """
    This calculates the probability of sleet using the calculation:
    prob(sleet) = 1 - (prob(snow) + prob(rain))

    Args:
      prob_of_snow (iris.cube.Cube):
        Cube of the probability of snow. This can be a fraction (0 <= x <= 1) or
        categorical (0 or 1)
      prob_of_rain (iris.cube.Cube):
        Cube of the probability of rain. This can be a fraction (0 <= x <= 1) or
        categorical (0 or 1)

    Returns:
      iris.cube.Cube:
        Cube of the probability of sleet. This will be fractional or categorical,
        matching the highest precision of the inputs.

    Raises:
        ValueError: If the cube contains negative values for the the
                    probability of sleet.
    """
    sleet_prob = 1 - (prob_of_snow.data + prob_of_rain.data)
    if np.any(sleet_prob < 0):
        msg = "Negative values of sleet probability have been calculated."
        raise ValueError(msg)

    # Copy all of the attributes from the prob_of_snow cube
    mandatory_attributes = generate_mandatory_attributes([prob_of_rain, prob_of_snow])
    probability_of_sleet = create_new_diagnostic_cube(
        "probability_of_sleet", "1", prob_of_snow, mandatory_attributes, data=sleet_prob
    )
    return probability_of_sleet
Beispiel #2
0
    def _calculate_snow_fraction(self):
        """
        Calculates the snow fraction data and interpolates to fill in the missing points.

        Returns:
            iris.cube.Cube:
                Snow fraction cube.

        """
        with np.errstate(divide="ignore", invalid="ignore"):
            snow_fraction = self.snow.data / (self.rain.data + self.snow.data)
        snow_fraction_cube = create_new_diagnostic_cube(
            "snow_fraction",
            "1",
            template_cube=self.rain,
            mandatory_attributes=generate_mandatory_attributes(
                iris.cube.CubeList([self.rain, self.snow]),
                model_id_attr=self.model_id_attr,
            ),
            data=snow_fraction,
        )

        spatial_dims = [snow_fraction_cube.coord(axis=n).name() for n in ["y", "x"]]
        snow_fraction_interpolated = iris.cube.CubeList()
        for snow_fraction_slice in snow_fraction_cube.slices(spatial_dims):
            snow_fraction_interpolated.append(
                snow_fraction_slice.copy(
                    interpolate_missing_data(snow_fraction_slice.data, method="nearest")
                )
            )
        return snow_fraction_interpolated.merge_cube()
    def create_wet_bulb_temperature_cube(self, temperature, relative_humidity,
                                         pressure):
        """
        Creates a cube of wet bulb temperature values

        Args:
            temperature (iris.cube.Cube):
                Cube of air temperatures.
            relative_humidity (iris.cube.Cube):
                Cube of relative humidities.
            pressure (iris.cube.Cube):
                Cube of air pressures.

        Returns:
            iris.cube.Cube:
                Cube of wet bulb temperature (K).

        """
        temperature.convert_units('K')
        relative_humidity.convert_units(1)
        pressure.convert_units('Pa')
        wbt_data = self._calculate_wet_bulb_temperature(
            pressure.data, relative_humidity.data, temperature.data)

        attributes = generate_mandatory_attributes(
            [temperature, relative_humidity, pressure])
        wbt = create_new_diagnostic_cube('wet_bulb_temperature',
                                         'K',
                                         temperature,
                                         attributes,
                                         data=wbt_data)
        return wbt
Beispiel #4
0
    def create_symbol_cube(cubes):
        """
        Create an empty weather symbol cube

        Args:
            cubes (list or iris.cube.CubeList):
                List of input cubes used to generate weather symbols
        Returns:
            iris.cube.Cube:
                A cube with suitable metadata to describe the weather symbols
                that will fill it
        """
        threshold_coord = find_threshold_coordinate(cubes[0])
        template_cube = next(cubes[0].slices_over([threshold_coord])).copy()
        # remove coordinates and bounds that do not apply to weather symbols
        template_cube.remove_coord(threshold_coord)
        for coord in template_cube.coords():
            if coord.name() in ['forecast_period', 'time']:
                coord.bounds = None

        attributes = generate_mandatory_attributes(cubes)
        symbols = create_new_diagnostic_cube(
            "weather_code",
            "1",
            template_cube,
            attributes,
            optional_attributes=weather_code_attributes(),
            dtype=np.int32)
        return symbols
Beispiel #5
0
    def _create_template_cube(self, cube):
        """
        Create a template cube to store the timezone masks. This cube has only
        one scalar coordinate which is time, denoting when it is valid; this is
        only relevant if using daylight savings. The attribute
        includes_daylight_savings is set to indicate this.

        Args:
            cube (iris.cube.Cube):
                A cube with the desired grid from which coordinates are taken
                for inclusion in the template.
        Returns:
            iris.cube.Cube:
                A template cube in which each timezone mask can be stored.
        """
        time_point = np.array(self.time.timestamp(), dtype=np.int64)
        time_coord = iris.coords.DimCoord(
            time_point,
            "time",
            units=Unit("seconds since 1970-01-01 00:00:00", calendar="gregorian"),
        )

        for crd in cube.coords(dim_coords=False):
            cube.remove_coord(crd)
        cube.add_aux_coord(time_coord)

        attributes = generate_mandatory_attributes([cube])
        attributes["includes_daylight_savings"] = str(self.include_dst)

        return create_new_diagnostic_cube(
            "timezone_mask", 1, cube, attributes, dtype=np.int8
        )
Beispiel #6
0
 def test_dtype(self):
     """Test dummy data of a different type can be set"""
     result = create_new_diagnostic_cube(self.name,
                                         self.units,
                                         self.template_cube,
                                         dtype=np.int32)
     self.assertEqual(result.data.dtype, np.int32)
Beispiel #7
0
    def _prepare_error_probability_cube(self, forecast_cube):
        """Initialise a cube with the same dimensions as the input forecast_cube,
        with an additional threshold dimension added as the leading dimension.

        Args:
            forecast_cube:
                Cube containing the forecast to be calibrated.

        Returns:
            An empty probability cube.
        """
        # Create a template for error CDF, with threshold the leading dimension.
        forecast_error_variable = f"forecast_error_of_{forecast_cube.name()}"

        error_probability_cube = create_new_diagnostic_cube(
            name=f"probability_of_{forecast_error_variable}_above_threshold",
            units="1",
            template_cube=forecast_cube,
            mandatory_attributes=generate_mandatory_attributes([forecast_cube
                                                                ]),
        )
        error_threshold_coord = DimCoord(
            self.error_thresholds,
            long_name=forecast_error_variable,
            var_name="threshold",
            units=forecast_cube.units,
            attributes={"spp__relative_to_threshold": "above"},
        )
        error_probability_cube = add_coordinate_to_cube(
            error_probability_cube,
            new_coord=error_threshold_coord,
        )

        return error_probability_cube
Beispiel #8
0
def calculate_sleet_probability(prob_of_snow, prob_of_rain):
    """
    This calculates the probability of sleet using the calculation:
    prob(sleet) = 1 - (prob(snow) + prob(rain))

    Args:
      prob_of_snow (iris.cube.Cube):
        Cube of the probability of snow.
      prob_of_rain (iris.cube.Cube):
        Cube of the probability of rain.

    Returns:
      iris.cube.Cube:
        Cube of the probability of sleet.

    Raises:
        ValueError: If the cube contains negative values for the the
                    probability of sleet.
    """
    ones = np.ones((prob_of_snow.shape), dtype="float32")
    sleet_prob = ones - (prob_of_snow.data + prob_of_rain.data)
    if np.any(sleet_prob < 0.0):
        msg = "Negative values of sleet probability have been calculated."
        raise ValueError(msg)

    # Copy all of the attributes from the prob_of_snow cube
    mandatory_attributes = generate_mandatory_attributes(
        [prob_of_rain, prob_of_snow])
    probability_of_sleet = create_new_diagnostic_cube("probability_of_sleet",
                                                      "1",
                                                      prob_of_snow,
                                                      mandatory_attributes,
                                                      data=sleet_prob)
    return probability_of_sleet
Beispiel #9
0
    def _create_output_cube(self, cube, advected_data, timestep):
        """
        Create a cube and appropriate metadata to contain the advected forecast

        Args:
            cube (iris.cube.Cube):
                Source cube (before advection)
            advected_data (numpy.ndarray):
                Advected data
            timestep (datetime.timedelta):
                Time difference between the advected output and the source

        Returns:
            iris.cube.Cube
        """
        attributes = generate_mandatory_attributes([cube])
        if "institution" in cube.attributes.keys():
            attributes["source"] = "{} Nowcast".format(attributes["institution"])
        else:
            attributes["source"] = "Nowcast"
        advected_cube = create_new_diagnostic_cube(
            cube.name(), cube.units, cube, attributes, data=advected_data
        )
        amend_attributes(advected_cube, self.attributes_dict)
        set_history_attribute(advected_cube, "Nowcast")

        self._update_time(cube.coord("time").copy(), advected_cube, timestep)
        self._add_forecast_reference_time(cube.coord("time").copy(), advected_cube)
        self._add_forecast_period(advected_cube, timestep)

        return advected_cube
    def process(self, snow_fraction, phase):
        """
        Make significant-phase-mask cube for the specified phase.

        Args:
            snow_fraction (iris.cube.Cube):
                The input snow-fraction data to derive the phase mask from.
            phase (str):
                One of "rain", "sleet" or "snow". This is the phase mask that will be
                returned.

        Returns:
            iris.cube.Cube:
                The requested phase mask containing 1 where that phase is dominant
                and 0 elsewhere. Dimensions will be identical to snow-fraction.
        """
        self._validate_snow_fraction(snow_fraction)

        try:
            data = self.phase_operator[phase](snow_fraction.data).astype(
                np.int8)
        except KeyError:
            raise KeyError(
                f"Requested phase mask '{phase}' not in {list(self.phase_operator.keys())}"
            )
        phase_mask = create_new_diagnostic_cube(
            f"{phase}_mask",
            "1",
            snow_fraction,
            generate_mandatory_attributes([snow_fraction],
                                          model_id_attr=self.model_id_attr),
            data=data,
        )
        return phase_mask
    def create_wet_bulb_temperature_cube(self, temperature: Cube,
                                         relative_humidity: Cube,
                                         pressure: Cube) -> Cube:
        """
        Creates a cube of wet bulb temperature values

        Args:
            temperature:
                Cube of air temperatures.
            relative_humidity:
                Cube of relative humidities.
            pressure:
                Cube of air pressures.

        Returns:
            Cube of wet bulb temperature (K).
        """
        temperature.convert_units("K")
        relative_humidity.convert_units(1)
        pressure.convert_units("Pa")
        wbt_data = self._calculate_wet_bulb_temperature(
            pressure.data, relative_humidity.data, temperature.data)

        attributes = generate_mandatory_attributes(
            [temperature, relative_humidity, pressure])
        wbt = create_new_diagnostic_cube("wet_bulb_temperature",
                                         "K",
                                         temperature,
                                         attributes,
                                         data=wbt_data)
        return wbt
Beispiel #12
0
    def create_symbol_cube(cubes):
        """
        Create an empty weather symbol cube

        Args:
            cubes (list or iris.cube.CubeList):
                List of input cubes used to generate weather symbols
        Returns:
            iris.cube.Cube:
                A cube with suitable metadata to describe the weather symbols
                that will fill it and data initiated with the value -1 to allow
                any unset points to be readily identified.
        """
        threshold_coord = find_threshold_coordinate(cubes[0])
        template_cube = next(cubes[0].slices_over([threshold_coord])).copy()
        # remove coordinates and bounds that do not apply to weather symbols
        template_cube.remove_coord(threshold_coord)
        for coord in template_cube.coords():
            if coord.name() in ["forecast_period", "time"]:
                coord.bounds = None

        attributes = generate_mandatory_attributes(cubes)
        symbols = create_new_diagnostic_cube(
            "weather_code",
            "1",
            template_cube,
            attributes,
            optional_attributes=weather_code_attributes(),
            data=np.ma.masked_all_like(template_cube.data).astype(np.int32),
        )
        return symbols
Beispiel #13
0
    def create_symbol_cube(self, cubes: Union[List[Cube], CubeList]) -> Cube:
        """
        Create an empty weather symbol cube

        Args:
            cubes:
                List of input cubes used to generate weather symbols

        Returns:
            A cube with suitable metadata to describe the weather symbols
            that will fill it and data initiated with the value -1 to allow
            any unset points to be readily identified.
        """
        threshold_coord = find_threshold_coordinate(self.template_cube)
        template_cube = next(self.template_cube.slices_over([threshold_coord
                                                             ])).copy()
        # remove coordinates and bounds that do not apply to weather symbols
        template_cube.remove_coord(threshold_coord)

        mandatory_attributes = generate_mandatory_attributes(cubes)
        optional_attributes = weather_code_attributes()
        if self.model_id_attr:
            optional_attributes.update(
                update_model_id_attr_attribute(cubes, self.model_id_attr))

        symbols = create_new_diagnostic_cube(
            "weather_code",
            "1",
            template_cube,
            mandatory_attributes,
            optional_attributes=optional_attributes,
            data=np.ma.masked_all_like(template_cube.data).astype(np.int32),
        )
        return symbols
Beispiel #14
0
 def test_non_standard_name(self):
     """Test cube can be created with a non-CF-standard name"""
     result = create_new_diagnostic_cube("RainRate Composite", self.units,
                                         self.template_cube,
                                         self.mandatory_attributes)
     self.assertEqual(result.long_name, "RainRate Composite")
     self.assertIsNone(result.standard_name)
Beispiel #15
0
    def process(self, cubes):
        """
        Calculate the convective ratio from the convective and dynamic components as:
            convective_ratio = convective / (convective + dynamic)

        If convective + dynamic is zero, then the resulting point is masked.

        Args:
            cubes (List[iris.cube.Cube, iris.cube.Cube]):
                Both the convective and dynamic components as iris.cube.Cube in a list
                with names 'lwe_convective_precipitation_rate' and
                'lwe_stratiform_precipitation_rate'

        Returns:
            iris.cube.Cube:
                Cube containing the convective ratio.
        """

        self._split_input(cubes)

        attributes = generate_mandatory_attributes([self.convective])
        output_cube = create_new_diagnostic_cube(
            "convective_ratio",
            "1",
            self.convective,
            attributes,
            data=self._convective_ratio(),
        )

        return output_cube
Beispiel #16
0
    def _create_shower_condition_cube(self, data: ndarray, cube: Cube) -> Cube:
        """
        Returns a shower condition cube, with coordinates and mandatory
        attributes based upon the provided cube. The threshold coordinate is
        modified to describe shower conditions, such that the probabilities
        describe the likelihood of conditions being showery. The arbitrary
        threshold value is 1.

        Args:
            data:
                The shower condition probabilities to populate the new cube.
            cube:
                The cube to use as a template, and from which to extract
                attributes for use in the new diagnostic cube.

        Returns:
            A probability of shower conditions cube.
        """
        template = make_shower_condition_cube(cube)
        attributes = generate_mandatory_attributes(
            [cube], model_id_attr=self.model_id_attr)

        result = create_new_diagnostic_cube(
            template.name(),
            "1",
            template,
            mandatory_attributes=attributes,
            data=data,
        )

        return result
Beispiel #17
0
    def _create_output_cube(gradient, diff, cube, axis):
        """
        Create the output gradient cube.

        Args:
            gradient (numpy.ndarray):
                Gradient values used in the data array of the resulting cube.
            diff (iris.cube.Cube):
                Cube containing differences along the x or y axis
            cube (iris.cube.Cube):
                Cube with correct output dimensions
            axis (str):
                Short-hand reference for the x or y coordinate, as allowed by
                iris.util.guess_coord_axis.

        Returns:
            iris.cube.Cube:
                A cube of the gradients in the coordinate direction specified.
        """
        grad_cube = create_new_diagnostic_cube(
            "gradient_of_" + cube.name(),
            cube.units / diff.coord(axis=axis).units,
            diff,
            MANDATORY_ATTRIBUTE_DEFAULTS,
            data=gradient,
        )
        return grad_cube
Beispiel #18
0
    def process(self,
                cubes: List[Cube],
                model_id_attr: Optional[str] = None) -> Cube:
        """
        Calculate the convective ratio from the convective and dynamic components as:
            convective_ratio = convective / (convective + dynamic)

        If convective + dynamic is zero, then the resulting point is masked.

        Args:
            cubes:
                Both the convective and dynamic components as iris.cube.Cube in a list
                with names 'lwe_convective_precipitation_rate' and
                'lwe_stratiform_precipitation_rate'
            model_id_attr:
                Name of the attribute used to identify the source model for
                blending. This is inherited from the input temperature cube.

        Returns:
            Cube containing the convective ratio.
        """

        self._split_input(cubes)

        attributes = generate_mandatory_attributes([self.convective],
                                                   model_id_attr=model_id_attr)
        output_cube = create_new_diagnostic_cube(
            "convective_ratio",
            "1",
            self.convective,
            attributes,
            data=self._convective_ratio(),
        )

        return output_cube
def calculate_feels_like_temperature(temperature,
                                     wind_speed,
                                     relative_humidity,
                                     pressure,
                                     model_id_attr=None):
    """
    Calculates the feels like temperature using a combination of
    the wind chill index and Steadman's apparent temperature equation.

    Args:
        temperature (iris.cube.Cube):
            Cube of air temperatures
        wind_speed (iris.cube.Cube):
            Cube of 10m wind speeds
        relative_humidity (iris.cube.Cube):
            Cube of relative humidities
        pressure (iris.cube.Cube):
            Cube of air pressure
        model_id_attr (str):
            Name of the attribute used to identify the source model for
            blending.

    Returns:
        iris.cube.Cube:
            Cube of feels like temperatures in the same units as the input
            temperature cube.
    """
    t_cube = temperature.copy()
    t_cube.convert_units('degC')
    t_celsius = t_cube.data

    w_cube = wind_speed.copy()
    w_cube.convert_units('m s-1')
    p_cube = pressure.copy()
    p_cube.convert_units('Pa')
    rh_cube = relative_humidity.copy()
    rh_cube.convert_units('1')
    apparent_temperature = _calculate_apparent_temperature(
        t_celsius, w_cube.data, rh_cube.data, p_cube.data)

    w_cube.convert_units('km h-1')
    wind_chill = _calculate_wind_chill(t_celsius, w_cube.data)

    feels_like_temperature = _feels_like_temperature(t_celsius,
                                                     apparent_temperature,
                                                     wind_chill)

    attributes = generate_mandatory_attributes(
        [temperature, wind_speed, relative_humidity, pressure],
        model_id_attr=model_id_attr)
    feels_like_temperature_cube = create_new_diagnostic_cube(
        "feels_like_temperature",
        "degC",
        temperature,
        attributes,
        data=feels_like_temperature)
    feels_like_temperature_cube.convert_units(temperature.units)

    return feels_like_temperature_cube
Beispiel #20
0
 def test_data(self):
     """Test data can be set on the output cube"""
     data = np.arange(3 * 5 * 5).reshape((3, 5, 5)).astype(np.float32)
     result = create_new_diagnostic_cube(self.name,
                                         self.units,
                                         self.template_cube,
                                         data=data)
     self.assertTrue(np.allclose(result.data, data))
Beispiel #21
0
 def test_attributes(self):
     """Test attributes can be set on the output cube"""
     attributes = {"source": "IMPROVER"}
     result = create_new_diagnostic_cube(self.name,
                                         self.units,
                                         self.template_cube,
                                         attributes=attributes)
     self.assertDictEqual(result.attributes, attributes)
Beispiel #22
0
    def _apply_error_to_forecast(self, forecast_cube: Cube,
                                 error_percentiles_cube: Cube) -> Cube:
        """Apply the error distributions (as error percentiles) to the forecast cube.
        The result is a series (sub-ensemble) of values for each forecast realization.

        Note:

            Within the RainForests approach we work with an additive error correction
            as opposed to a multiplicative correction used in ECPoint. The advantage of
            using an additive error is that we are also able to calibrate zero-values in
            the input forecast.

        Warning:

            After applying the error distributions to the forecast cube, values outside
            the expected bounds of the forecast parameter can arise. These values occur when
            when the input forecast value is between error thresholds and there exists a
            lower bound on the observable value (eg. 0 in the case of rainfall).

            In this situation, error thresholds below the residual value (min(obs) - fcst)
            must have a probability of exceedance of 1, whereas as error thresholds above
            this value can take on any value between [0, 1]. In the subsequent step where
            error percentile values are extracted, the linear interpolation in mapping from
            probabilities to percentiles can give percentile values that lie below the
            residual value; when these are applied to the forecast value, they result in
            forecast values outside the expected bounds of the forecast parameter in the
            resultant sub-ensemble.

            To address this, we remap all values outside of the expected bounds to nearest
            bound (eg. negative values are mapped to 0 in the case of rainfall).

        Args:
            forecast_cube:
                Cube containing the forecast to be calibrated.
            error_percentiles_cube:
                Cube containing percentile values for the error distributions.

        Returns:
            Cube containing the forecast sub-ensembles.
        """
        # Apply the error_percentiles to the forecast_cube (additive correction)
        forecast_subensembles_data = (forecast_cube.data[:, np.newaxis] +
                                      error_percentiles_cube.data)
        # RAINFALL SPECIFIC IMPLEMENTATION:
        # As described above, we need to address value outside of expected bounds.
        # In the case of rainfall, we map all negative values to 0.
        forecast_subensembles_data = np.maximum(0.0,
                                                forecast_subensembles_data)
        # Return cube containing forecast subensembles
        return create_new_diagnostic_cube(
            name=forecast_cube.name(),
            units=forecast_cube.units,
            template_cube=error_percentiles_cube,
            mandatory_attributes=generate_mandatory_attributes([forecast_cube
                                                                ]),
            optional_attributes=forecast_cube.attributes,
            data=forecast_subensembles_data,
        )
Beispiel #23
0
    def _create_output_cube(self, template, data, points, bounds):
        """
        Populates a template cube with data from the integration

        Args:
            template (iris.cube.Cube):
                Copy of upper or lower bounds cube, based on direction of
                integration
            data (list or numpy.ndarray):
                Integrated data
            points (list or numpy.ndarray):
                Points values for the integrated coordinate. These will not
                match the template cube if any slices were skipped in the
                integration, and therefore are used to slice the template cube
                to match the data array.
            bounds (list or numpy.ndarray):
                Bounds values for the integrated coordinate

        Returns:
            iris.cube.Cube
        """
        # extract required slices from template cube
        template = template.extract(
            iris.Constraint(coord_values={
                self.coord_name_to_integrate: lambda x: x in points
            }))

        # re-promote integrated coord to dimension coord if need be
        aux_coord_names = [coord.name() for coord in template.aux_coords]
        if self.coord_name_to_integrate in aux_coord_names:
            template = iris.util.new_axis(template,
                                          self.coord_name_to_integrate)

        # order dimensions on the template cube so that the integrated
        # coordinate is first (as this is the leading dimension on the
        # data array)
        enforce_coordinate_ordering(template, self.coord_name_to_integrate)

        # generate appropriate metadata for new cube
        attributes = generate_mandatory_attributes([template])
        coord_dtype = template.coord(self.coord_name_to_integrate).dtype
        name, units = self._generate_output_name_and_units()

        # create new cube from template
        integrated_cube = create_new_diagnostic_cube(name,
                                                     units,
                                                     template,
                                                     attributes,
                                                     data=np.array(data))

        integrated_cube.coord(self.coord_name_to_integrate).bounds = np.array(
            bounds).astype(coord_dtype)

        # re-order cube to match dimensions of input cube
        ordered_dimensions = get_dim_coord_names(self.input_cube)
        enforce_coordinate_ordering(integrated_cube, ordered_dimensions)
        return integrated_cube
Beispiel #24
0
    def process(self, cubes: CubeList, model_id_attr: str = None) -> Cube:
        """
        From the supplied CAPE and precipitation-rate cubes, calculate a probability
        of lightning cube.

        Args:
            cubes:
                Cubes of CAPE and Precipitation rate.
            model_id_attr:
                The name of the dataset attribute to be used to identify the source
                model when blending data from different models.

        Returns:
            Cube of lightning data

        Raises:
            ValueError:
                If one of the cubes is not found or doesn't match the other
        """
        cape, precip = self._get_inputs(cubes)

        cape_true = LatitudeDependentThreshold(
            lambda lat: latitude_to_threshold(
                lat, midlatitude=350.0, tropics=500.0),
            threshold_units="J kg-1",
            comparison_operator=">",
        )(cape)

        precip_true = LatitudeDependentThreshold(
            lambda lat: latitude_to_threshold(
                lat, midlatitude=1.0, tropics=4.0),
            threshold_units="mm h-1",
            comparison_operator=">",
        )(precip)

        data = cape_true.data * precip_true.data

        cube = create_new_diagnostic_cube(
            name=
            "probability_of_number_of_lightning_flashes_per_unit_area_above_threshold",
            units="1",
            template_cube=precip,
            data=data.astype(FLOAT_DTYPE),
            mandatory_attributes=generate_mandatory_attributes(
                cubes, model_id_attr=model_id_attr),
        )

        coord = DimCoord(
            np.array([0], dtype=FLOAT_DTYPE),
            units="m-2",
            long_name="number_of_lightning_flashes_per_unit_area",
            var_name="threshold",
            attributes={"spp__relative_to_threshold": "greater_than"},
        )
        cube.add_aux_coord(coord)

        return cube
Beispiel #25
0
    def create_output_cube(self, cube: Cube, local_time: datetime) -> Cube:
        """
        Constructs the output cube

        Args:
            cube:
                Cube of data to extract timezone-offsets from. Must contain a time
                coord spanning all the timezones.
            local_time:
                The "local" time of the output cube as %Y%m%dT%H%MZ. This will form a
                scalar "time_in_local_timezone" coord on the output cube, while the
                "time" coord will be auxillary to the spatial coords and will show the
                UTC time that matches the local_time at each point.

        """
        template_cube = cube.slices_over("time").next().copy()
        template_cube.remove_coord("time")
        template_cube.remove_coord("forecast_period")
        output_cube = create_new_diagnostic_cube(
            template_cube.name(),
            template_cube.units,
            template_cube,
            generate_mandatory_attributes([template_cube]),
            optional_attributes=template_cube.attributes,
            data=self.output_data,
        )

        # Copy cell-methods from template_cube
        [output_cube.add_cell_method(cm) for cm in template_cube.cell_methods]

        # Create a local time coordinate to help with plotting data.
        local_time_coord_standards = TIME_COORDS["time_in_local_timezone"]
        local_time_units = cf_units.Unit(
            local_time_coord_standards.units,
            calendar=local_time_coord_standards.calendar,
        )
        timezone_points = np.array(
            np.round(local_time_units.date2num(local_time)),
            dtype=local_time_coord_standards.dtype,
        )
        output_cube.add_aux_coord(
            AuxCoord(
                timezone_points,
                long_name="time_in_local_timezone",
                units=local_time_units,
            )
        )
        output_cube.add_aux_coord(
            AuxCoord(
                self.time_points,
                bounds=self.time_bounds,
                standard_name="time",
                units=self.time_units,
            ),
            [n + output_cube.ndim for n in [-2, -1]],
        )
        return output_cube
Beispiel #26
0
 def test_attributes(self):
     """Test optional attributes can be set on the output cube, and override
     the values in mandatory_attributes"""
     attributes = {"source": "Mars", "mosg__model_configuration": "uk_det"}
     expected_attributes = self.mandatory_attributes
     expected_attributes.update(attributes)
     result = create_new_diagnostic_cube(self.name,
                                         self.units,
                                         self.template_cube,
                                         self.mandatory_attributes,
                                         optional_attributes=attributes)
     self.assertDictEqual(result.attributes, expected_attributes)
Beispiel #27
0
    def process(self, cube):
        """
        Calculate the convective ratio either for the underlying field e.g.
        precipitation rate, or using the differences between adjacent grid
        squares.

        If the difference between adjacent grid squares is used, firstly the
        absolute differences are calculated, and then the difference cubes are
        thresholded using a high and low threshold. The thresholded difference
        cubes are then summed in order to put these cubes back onto the grid
        of the original cube. The convective ratio is then calculated by
        applying neighbourhood processing to the resulting cubes by dividing
        the high threshold cube by the low threshold cube.

        Args:
            cube (iris.cube.Cube):
                The cube from which the convective ratio will be calculated.

        Returns:
            iris.cube.Cube:
                Cube containing the convective ratio defined as the ratio
                between a cube with a high threshold applied and a cube with a
                low threshold applied.
        """
        cubelist = iris.cube.CubeList([])
        threshold_list = [self.lower_threshold, self.higher_threshold]
        if self.use_adjacent_grid_square_differences:
            for threshold in threshold_list:
                diff_cubelist = self.absolute_differences_between_adjacent_grid_squares(
                    cube)
                thresholded_cubes = self.iterate_over_threshold(
                    diff_cubelist, threshold)
                cubelist.append(
                    self.sum_differences_between_adjacent_grid_squares(
                        cube, thresholded_cubes))
        else:
            for threshold in threshold_list:
                cubelist.extend(self.iterate_over_threshold([cube], threshold))

        convective_ratios = self._calculate_convective_ratio(
            cubelist, threshold_list)

        attributes = generate_mandatory_attributes([cube])
        output_cube = create_new_diagnostic_cube("convective_ratio",
                                                 "1",
                                                 cube,
                                                 attributes,
                                                 data=convective_ratios)

        return output_cube
Beispiel #28
0
 def test_basic(self):
     """Test result is a cube that inherits coordinates only"""
     result = create_new_diagnostic_cube(self.name, self.units,
                                         self.template_cube)
     self.assertIsInstance(result, iris.cube.Cube)
     self.assertEqual(result.standard_name, "lwe_precipitation_rate")
     self.assertEqual(result.units, "mm h-1")
     self.assertSequenceEqual(result.coords(dim_coords=True),
                              self.template_cube.coords(dim_coords=True))
     self.assertSequenceEqual(result.coords(dim_coords=False),
                              self.template_cube.coords(dim_coords=False))
     self.assertFalse(np.allclose(result.data, self.template_cube.data))
     self.assertFalse(result.attributes)
     self.assertFalse(result.cell_methods)
     self.assertEqual(result.data.dtype, np.float32)
    def process(self, cubes: Union[CubeList, List[Cube]]) -> Cube:
        """
        Derives the probability of a precipitation phase at the surface. If
        the snow-sleet falling-level is supplied, this is the probability of
        snow at (or below) the surface. If the sleet-rain falling-level is
        supplied, this is the probability of rain at (or above) the surface.
        If the hail-rain falling-level is supplied, this is the probability
        of rain from hail at (or above) the surface.

        Args:
            cubes:
                Contains cubes of the altitude of the phase-change level (this
                can be snow->sleet, hail->rain or sleet->rain) and the altitude
                of the orography.

        Returns:
            Cube containing the probability of a specific precipitation phase
            reaching the surface orography. If the falling_level_cube was
            snow->sleet, then this will be the probability of snow at the
            surface. If the falling_level_cube was sleet->rain, then this
            will be the probability of rain from sleet at the surface.
            If the falling_level_cube was hail->rain, then this
            will be the probability of rain from hail at the surface.
            The probabilities are categorical (1 or 0) allowing
            precipitation to be divided uniquely between snow, sleet and
            rain phases.
        """
        self._extract_input_cubes(cubes)
        processed_falling_level = iris.util.squeeze(
            self.get_discriminating_percentile(self.falling_level_cube))

        result_data = np.where(
            self.comparator(self.orography_cube.data,
                            processed_falling_level.data),
            1,
            0,
        ).astype(np.int8)
        mandatory_attributes = generate_mandatory_attributes(
            [self.falling_level_cube])

        cube = create_new_diagnostic_cube(
            f"probability_of_{self.param}_at_surface",
            Unit("1"),
            self.falling_level_cube,
            mandatory_attributes,
            data=result_data,
        )
        return cube
Beispiel #30
0
 def _make_updraught_cube(self, data: np.ndarray) -> Cube:
     """Puts the data array into a CF-compliant cube"""
     attributes = {}
     if self.model_id_attr:
         attributes[self.model_id_attr] = self.precip.attributes[
             self.model_id_attr]
     cube = create_new_diagnostic_cube(
         "maximum_vertical_updraught",
         "m s-1",
         self.precip,
         mandatory_attributes=generate_mandatory_attributes(
             [self.precip, self.cape]),
         optional_attributes=attributes,
         data=data,
     )
     return cube