Пример #1
0
    def check_probability_cube_metadata(self, cube: Cube) -> None:
        """Checks probability-specific metadata"""
        if cube.units != "1":
            self.errors.append(
                f"Expected units of 1 on probability data, got {cube.units}")

        try:
            self.diagnostic = get_diagnostic_cube_name_from_probability_name(
                cube.name())
        except ValueError as cause:
            # if the probability name is not valid
            self.errors.append(str(cause))

        expected_threshold_name = get_threshold_coord_name_from_probability_name(
            cube.name())

        if not cube.coords(expected_threshold_name):
            msg = f"Cube does not have expected threshold coord '{expected_threshold_name}'; "
            try:
                threshold_name = find_threshold_coordinate(cube).name()
            except CoordinateNotFoundError:
                coords = [coord.name() for coord in cube.coords()]
                msg += (
                    f"no coord with var_name='threshold' found in all coords: {coords}"
                )
                self.errors.append(msg)
            else:
                msg += f"threshold coord has incorrect name '{threshold_name}'"
                self.errors.append(msg)
                self.check_threshold_coordinate_properties(
                    cube.name(), cube.coord(threshold_name))
        else:
            threshold_coord = cube.coord(expected_threshold_name)
            self.check_threshold_coordinate_properties(cube.name(),
                                                       threshold_coord)
Пример #2
0
 def test_in_vicinity(self):
     """Test correct name is returned from an "in vicinity" probability.
     Name "cloud_height" is used in this test to illustrate why suffix
     cannot be removed with "rstrip"."""
     diagnostic = "cloud_height"
     result = get_threshold_coord_name_from_probability_name(
         f"probability_of_{diagnostic}_in_vicinity_above_threshold")
     self.assertEqual(result, diagnostic)
Пример #3
0
    def _update_cell_methods(
        cell_methods: Tuple[CellMethod],
        probabilistic_name: str,
        new_diagnostic_name: str,
    ) -> List[CellMethod]:
        """
        Update any cell methods that include a comment that refers to the
        diagnostic name to refer instead to the new diagnostic name. Those cell
        methods that do not include the diagnostic name are passed through
        unmodified.

        Args:
            cell_methods:
                The cell methods found on the cube that is being used as the
                metadata template.
            probabilistic_name:
                The full name of the metadata template cube.
            new_diagnostic_name:
                The new diagnostic name to use in the modified cell methods.

        Returns:
            A list of modified cell methods to replace the originals.
        """

        # strip probability and vicinity components to provide the diagnostic name
        diagnostic_name = get_threshold_coord_name_from_probability_name(
            probabilistic_name
        )

        new_cell_methods = []
        for cell_method in cell_methods:
            try:
                (cell_comment,) = cell_method.comments
            except ValueError:
                new_cell_methods.append(cell_method)
            else:
                if diagnostic_name in cell_comment:
                    new_cell_methods.append(
                        CellMethod(
                            cell_method.method,
                            coords=cell_method.coord_names,
                            intervals=cell_method.intervals,
                            comments=f"of {new_diagnostic_name}",
                        )
                    )
                else:
                    new_cell_methods.append(cell_method)
        return new_cell_methods
Пример #4
0
    def construct_extract_constraint(
            self, diagnostic: str, threshold: AuxCoord,
            coord_named_threshold: bool) -> Constraint:
        """
        Construct an iris constraint.

        Args:
            diagnostic:
                The name of the diagnostic to be extracted from the CubeList.
            threshold:
                The thresholds within the given diagnostic cube that is
                needed, including units.  Note these are NOT coords from the
                original cubes, just constructs to associate units with values.
            coord_named_threshold:
                If true, use old naming convention for threshold coordinates
                (coord.long_name=threshold).  Otherwise extract threshold
                coordinate name from diagnostic name

        Returns:
            A constraint
        """

        if coord_named_threshold:
            threshold_coord_name = "threshold"
        else:
            threshold_coord_name = get_threshold_coord_name_from_probability_name(
                diagnostic)

        threshold_val = threshold.points.item()
        if abs(threshold_val) < self.float_abs_tolerance:
            cell_constraint = lambda cell: np.isclose(
                cell.point,
                threshold_val,
                rtol=0,
                atol=self.float_abs_tolerance,
            )
        else:
            cell_constraint = lambda cell: np.isclose(
                cell.point,
                threshold_val,
                rtol=self.float_tolerance,
                atol=0,
            )

        kw_dict = {"{}".format(threshold_coord_name): cell_constraint}
        constraint = iris.Constraint(name=diagnostic, **kw_dict)
        return constraint
Пример #5
0
    def construct_extract_constraint(self, diagnostics, thresholds,
                                     coord_named_threshold):
        """
        Construct an iris constraint.

        Args:
            diagnostics (str or list of str):
                The names of the diagnostics to be extracted from the CubeList.
            thresholds (iris.AuxCoord or list of iris.AuxCoord):
                All thresholds within the given diagnostic cubes that are
                needed, including units.  Note these are NOT coords from the
                original cubes, just constructs to associate units with values.
            coord_named_threshold (bool):
                If true, use old naming convention for threshold coordinates
                (coord.long_name=threshold).  Otherwise extract threshold
                coordinate name from diagnostic name

        Returns:
            str or list of str:
                String, or list of strings, encoding iris cube constraints.
        """
        def _constraint_string(diagnostic, threshold_name, threshold_val):
            """
            Return iris constraint as a string for deferred creation of the
            lambda functions.
            Args:
                diagnostic (str):
                    Name of diagnostic
                threshold_name (str):
                    Name of threshold coordinate on input cubes
                threshold_val (float):
                    Value of threshold coordinate required
            Returns: (str)
            """
            if abs(threshold_val) < WeatherSymbols().float_abs_tolerance:
                cell_constraint_str = (
                    " -{float_abs_tol} < cell < "
                    "{float_abs_tol}".format(
                        float_abs_tol=WeatherSymbols().float_abs_tolerance))
            else:
                cell_constraint_str = (
                    "{threshold_val} * {float_min} < cell < "
                    "{threshold_val} * {float_max}".format(
                        threshold_val=threshold_val,
                        float_min=(1.0 - WeatherSymbols().float_tolerance),
                        float_max=(1.0 + WeatherSymbols().float_tolerance),
                    ))
            constraint_str = (
                "iris.Constraint(name='{diagnostic}', {threshold_name}="
                "lambda cell: {cell_constraint})".format(
                    diagnostic=diagnostic,
                    threshold_name=threshold_name,
                    cell_constraint=cell_constraint_str,
                ))
            return constraint_str

        # if input is list, loop over and return a list of strings
        if not isinstance(diagnostics, list):
            diagnostics = [diagnostics]
            thresholds = [thresholds]
        constraints = []
        for diagnostic, threshold in zip(diagnostics, thresholds):
            if coord_named_threshold:
                threshold_coord_name = "threshold"
            elif diagnostic in self.threshold_coord_names:
                threshold_coord_name = self.threshold_coord_names[diagnostic]
            else:
                threshold_coord_name = get_threshold_coord_name_from_probability_name(
                    diagnostic)
            threshold_val = threshold.points.item()
            constraints.append(
                _constraint_string(diagnostic, threshold_coord_name,
                                   threshold_val))
        if len(constraints) > 1:
            return constraints

        # otherwise, return a string
        return constraints[0]
Пример #6
0
    def check_input_cubes(self, cubes):
        """
        Check that the input cubes contain all the diagnostics and thresholds
        required by the decision tree.  Sets self.coord_named_threshold to
        "True" if threshold-type coordinates have the name "threshold" (as
        opposed to the standard name of the diagnostic), for backward
        compatibility.

        Args:
            cubes (iris.cube.CubeList):
                A CubeList containing the input diagnostic cubes.

        Returns:
            dict or None:
                A dictionary of (keyword) nodes names where the diagnostic
                data is missing and (values) node associated with
                diagnostic_missing_action.

        Raises:
            IOError:
                Raises an IOError if any of the required input data is missing.
                The error includes details of which fields are missing.
        """
        optional_node_data_missing = {}
        missing_data = []
        for key, query in self.queries.items():
            diagnostics = get_parameter_names(
                expand_nested_lists(query, "diagnostic_fields"))
            thresholds = expand_nested_lists(query, "diagnostic_thresholds")
            conditions = expand_nested_lists(query, "diagnostic_conditions")
            for diagnostic, threshold, condition in zip(
                    diagnostics, thresholds, conditions):

                # First we check the diagnostic name and units, performing
                # a conversion is required and possible.
                test_condition = iris.Constraint(name=diagnostic)
                matched_cube = cubes.extract(test_condition)
                if not matched_cube:
                    if "diagnostic_missing_action" in query:
                        optional_node_data_missing.update(
                            {key: query[query["diagnostic_missing_action"]]})
                    else:
                        missing_data.append([diagnostic, threshold, condition])
                    continue

                cube_threshold_units = find_threshold_coordinate(
                    matched_cube[0]).units
                threshold.convert_units(cube_threshold_units)

                # Then we check if the required threshold is present in the
                # cube, and that the thresholding is relative to it correctly.
                threshold = threshold.points.item()
                threshold_name = find_threshold_coordinate(
                    matched_cube[0]).name()

                # Check cube and threshold coordinate names match according to
                # expected convention.  If not, add to exception dictionary.
                if (get_threshold_coord_name_from_probability_name(diagnostic)
                        != threshold_name):
                    self.threshold_coord_names[diagnostic] = threshold_name

                # Set flag to check for old threshold coordinate names
                if threshold_name == "threshold" and not self.coord_named_threshold:
                    self.coord_named_threshold = True

                # Check threshold == 0.0
                if abs(threshold) < self.float_abs_tolerance:
                    coord_constraint = {
                        threshold_name:
                        lambda cell: (-self.float_abs_tolerance < cell < self.
                                      float_abs_tolerance)
                    }
                else:
                    coord_constraint = {
                        threshold_name:
                        lambda cell:
                        (threshold *
                         (1.0 - self.float_tolerance) < cell < threshold *
                         (1.0 + self.float_tolerance))
                    }

                # Checks whether the spp__relative_to_threshold attribute is above
                # or below a threshold and and compares to the diagnostic_condition.
                test_condition = iris.Constraint(
                    coord_values=coord_constraint,
                    cube_func=lambda cube:
                    (probability_is_above_or_below(cube) == condition),
                )
                matched_threshold = matched_cube.extract(test_condition)
                if not matched_threshold:
                    missing_data.append([diagnostic, threshold, condition])

        if missing_data:
            msg = ("Weather Symbols input cubes are missing"
                   " the following required"
                   " input fields:\n")
            dyn_msg = "name: {}, threshold: {}, " "spp__relative_to_threshold: {}\n"
            for item in missing_data:
                msg = msg + dyn_msg.format(*item)
            raise IOError(msg)

        if not optional_node_data_missing:
            optional_node_data_missing = None
        return optional_node_data_missing
Пример #7
0
 def test_error_not_probability(self):
     """Test exception if input is not a probability cube name"""
     with self.assertRaises(ValueError):
         get_threshold_coord_name_from_probability_name(
             "lwe_precipitation_rate")
Пример #8
0
 def test_between_thresholds(self):
     """Test correct name is returned from a probability between thresholds
     """
     result = get_threshold_coord_name_from_probability_name(
         "probability_of_visibility_in_air_between_thresholds")
     self.assertEqual(result, "visibility_in_air")
Пример #9
0
 def test_below_threshold(self):
     """Test correct name is returned from a probability below threshold"""
     result = get_threshold_coord_name_from_probability_name(
         "probability_of_air_temperature_below_threshold")
     self.assertEqual(result, "air_temperature")
Пример #10
0
 def test_above_threshold(self):
     """Test correct name is returned from a standard (above threshold)
     probability field"""
     result = get_threshold_coord_name_from_probability_name(
         "probability_of_air_temperature_above_threshold")
     self.assertEqual(result, "air_temperature")