Ejemplo n.º 1
0
    def enforce_time_coords_dtype(cube: Cube) -> Cube:
        """
        Enforce the data type of the time, forecast_reference_time and
        forecast_period within the cube, so that time coordinates do not
        become mis-represented. The units of the time and
        forecast_reference_time are enforced to be
        "seconds since 1970-01-01 00:00:00" with a datatype of int64.
        The units of forecast_period are enforced to be seconds with a datatype
        of int32. This functions modifies the cube in-place.

        Args:
            cube:
                The cube that will have the datatype and units for the
                time, forecast_reference_time and forecast_period coordinates
                enforced.

        Returns:
            Cube where the datatype and units for the
            time, forecast_reference_time and forecast_period coordinates
            have been enforced.
        """
        for coord_name in [
                "time", "forecast_reference_time", "forecast_period"
        ]:
            coord_spec = TIME_COORDS[coord_name]
            if cube.coords(coord_name):
                coord = cube.coord(coord_name)
                coord.convert_units(coord_spec.units)
                coord.points = round_close(coord.points,
                                           dtype=coord_spec.dtype)
                if hasattr(coord, "bounds") and coord.bounds is not None:
                    coord.bounds = round_close(coord.bounds,
                                               dtype=coord_spec.dtype)
        return cube
Ejemplo n.º 2
0
    def _get_cycletime_point(input_cube, cycletime):
        """
        For cycle and model blending, establish the single forecast reference
        time to set on the cube after blending.

        Args:
            input_cube (iris.cube.Cube):
                Cube to be blended
            cycletime (str or None):
                The cycletime in a YYYYMMDDTHHMMZ format e.g. 20171122T0100Z.
                If None, the latest forecast reference time is used.

        Returns:
            numpy.int64:
                Forecast reference time point in units of input cube coordinate
        """
        frt_coord = input_cube.coord("forecast_reference_time")
        if cycletime is None:
            return np.max(frt_coord.points)
        frt_units = frt_coord.units.origin
        frt_calendar = frt_coord.units.calendar
        cycletime_point = cycletime_to_number(cycletime,
                                              time_unit=frt_units,
                                              calendar=frt_calendar)
        return round_close(cycletime_point, dtype=np.int64)
Ejemplo n.º 3
0
def _create_frt_type_coord(
    cube: Cube, point: datetime, name: str = "forecast_reference_time"
) -> DimCoord:
    """Create a new auxiliary coordinate based on forecast reference time

    Args:
        cube:
            Input cube with scalar forecast reference time coordinate
        points
            Single datetime point for output coord
        name
            Name of aux coord to be returned

    Returns:
        New auxiliary coordinate
    """
    frt_coord_name = "forecast_reference_time"
    coord_type_spec = TIME_COORDS[frt_coord_name]
    coord_units = Unit(coord_type_spec.units)
    new_points = round_close([coord_units.date2num(point)], dtype=coord_type_spec.dtype)
    try:
        new_coord = DimCoord(new_points, standard_name=name, units=coord_units)
    except ValueError:
        new_coord = DimCoord(new_points, long_name=name, units=coord_units)
    return new_coord
Ejemplo n.º 4
0
    def _standardise_dtypes_and_units(cube):
        """
        Modify input cube in place to conform to mandatory dtype and unit
        standards.

        Args:
            cube (iris.cube.Cube:
                Cube to be updated in place

        """
        def as_correct_dtype(obj, required_dtype):
            """
            Returns an object updated if necessary to the required dtype

            Args:
                obj (np.ndarray):
                    The object to be updated
                required_dtype (np.dtype):
                    The dtype required

            Returns:
                np.ndarray
            """
            if obj.dtype != required_dtype:
                return obj.astype(required_dtype)
            return obj

        cube.data = as_correct_dtype(cube.data, get_required_dtype(cube))
        for coord in cube.coords():
            if coord.name() in TIME_COORDS and not check_units(coord):
                coord.convert_units(get_required_units(coord))
            req_dtype = get_required_dtype(coord)
            # ensure points and bounds have the same dtype
            if np.issubdtype(req_dtype, np.integer):
                coord.points = round_close(coord.points)
            coord.points = as_correct_dtype(coord.points, req_dtype)
            if coord.has_bounds():
                if np.issubdtype(req_dtype, np.integer):
                    coord.bounds = round_close(coord.bounds)
                coord.bounds = as_correct_dtype(coord.bounds, req_dtype)
Ejemplo n.º 5
0
 def _add_forecast_reference_time(input_time, advected_cube):
     """Add or replace a forecast reference time on the advected cube"""
     try:
         advected_cube.remove_coord("forecast_reference_time")
     except CoordinateNotFoundError:
         pass
     frt_coord_name = "forecast_reference_time"
     frt_coord_spec = TIME_COORDS[frt_coord_name]
     frt_coord = input_time.copy()
     frt_coord.rename(frt_coord_name)
     frt_coord.convert_units(frt_coord_spec.units)
     frt_coord.points = round_close(frt_coord.points, dtype=frt_coord_spec.dtype)
     advected_cube.add_aux_coord(frt_coord)
Ejemplo n.º 6
0
    def _get_cycletime_point(self, cube):
        """
        For cycle and model blending, establish the current cycletime to set on
        the cube after blending.

        Returns:
            numpy.int64:
                Cycle time point in units matching the input cube forecast reference
                time coordinate
        """
        frt_coord = cube.coord("forecast_reference_time")
        frt_units = frt_coord.units.origin
        frt_calendar = frt_coord.units.calendar
        # raises TypeError if cycletime is None
        cycletime_point = cycletime_to_number(
            self.cycletime, time_unit=frt_units, calendar=frt_calendar
        )
        return round_close(cycletime_point, dtype=np.int64)
Ejemplo n.º 7
0
def unify_cycletime(cubes, cycletime):
    """
    Function to unify the forecast_reference_time and update forecast_period.
    The cycletime specified is used as the forecast_reference_time, and the
    forecast_period is recalculated using the time coordinate and updated
    forecast_reference_time.

    Args:
        cubes (iris.cube.CubeList or list of iris.cube.Cube):
            Cubes that will have their forecast_reference_time and
            forecast_period updated. Any bounds on the forecast_reference_time
            coordinate will be discarded.
        cycletime (datetime.datetime):
            Datetime for the cycletime that will be used to replace the
            forecast_reference_time on the individual cubes.

    Returns:
        iris.cube.CubeList:
            Updated cubes

    Raises:
        ValueError: if forecast_reference_time is a dimension coordinate
    """
    result_cubes = iris.cube.CubeList([])
    for cube in cubes:
        cube = cube.copy()
        frt_coord_name = "forecast_reference_time"
        coord_type_spec = TIME_COORDS[frt_coord_name]
        coord_units = Unit(coord_type_spec.units)
        frt_points = round_close([coord_units.date2num(cycletime)],
                                 dtype=coord_type_spec.dtype)
        frt_coord = cube.coord(frt_coord_name).copy(points=frt_points)
        cube.remove_coord(frt_coord_name)
        cube.add_aux_coord(frt_coord, data_dims=None)

        # Update the forecast period for consistency within each cube
        if cube.coords("forecast_period"):
            cube.remove_coord("forecast_period")
        fp_coord = forecast_period_coord(cube,
                                         force_lead_time_calculation=True)
        cube.add_aux_coord(fp_coord, data_dims=cube.coord_dims("time"))
        result_cubes.append(cube)
    return result_cubes
Ejemplo n.º 8
0
    def _update_time(input_time, advected_cube, timestep):
        """Increment validity time on the advected cube

        Args:
            input_time (iris.coords.Coord):
                Time coordinate from source cube
            advected_cube (iris.cube.Cube):
                Cube containing advected data (modified in place)
            timestep (datetime.timedelta)
                Time difference between the advected output and the source
        """
        original_datetime = next(input_time.cells())[0]
        new_datetime = original_datetime + timestep
        new_time = input_time.units.date2num(new_datetime)
        time_coord_name = "time"
        time_coord_spec = TIME_COORDS[time_coord_name]
        time_coord = advected_cube.coord(time_coord_name)
        time_coord.points = new_time
        time_coord.convert_units(time_coord_spec.units)
        time_coord.points = round_close(time_coord.points, dtype=time_coord_spec.dtype)
Ejemplo n.º 9
0
def _get_cycletime_point(cube: Cube, cycletime: str) -> int64:
    """
    For cycle and model blending, establish the current cycletime to set on
    the cube after blending.

    Args:
        blended_cube
        cycletime:
            Current cycletime in YYYYMMDDTHHmmZ format

    Returns:
        Cycle time point in units matching the input cube forecast reference
        time coordinate
    """
    frt_coord = cube.coord("forecast_reference_time")
    frt_units = frt_coord.units.origin
    frt_calendar = frt_coord.units.calendar
    # raises TypeError if cycletime is None
    cycletime_point = cycletime_to_number(cycletime,
                                          time_unit=frt_units,
                                          calendar=frt_calendar)
    return round_close(cycletime_point, dtype=np.int64)
Ejemplo n.º 10
0
    def test_3d_spot_cube_for_time(self):
        """Test output with two extra dimensions, one of which is time with
        forecast_period as an auxiliary coordinate"""
        data = np.ones((3, 2, 4), dtype=np.float32)
        time_spec = TIME_COORDS["time"]
        time_units = Unit(time_spec.units)
        time_as_dt = [
            datetime(2021, 12, 25, 12, 0),
            datetime(2021, 12, 25, 12, 1)
        ]
        time_points = round_close(
            np.array([time_units.date2num(t) for t in time_as_dt]),
            dtype=time_spec.dtype,
        )
        time_coord = DimCoord(time_points,
                              units=time_units,
                              standard_name="time")

        fp_spec = TIME_COORDS["forecast_period"]
        fp_units = Unit(fp_spec.units)
        fp_points = np.array([0, 3600], dtype=fp_spec.dtype)
        fp_coord = AuxCoord(fp_points,
                            units=fp_units,
                            standard_name="forecast_period")

        result = build_spotdata_cube(
            data,
            *self.args,
            grid_attributes=self.grid_attributes,
            additional_dims=[time_coord],
            additional_dims_aux=[[fp_coord]],
        )

        self.assertArrayAlmostEqual(result.data, data)
        self.assertEqual(result.coord_dims("grid_attributes")[0], 0)
        self.assertEqual(result.coord_dims("time")[0], 1)
        self.assertEqual(result.coord_dims("forecast_period")[0], 1)
Ejemplo n.º 11
0
def test_error_not_close():
    """Test error when output would require significant rounding"""
    with pytest.raises(ValueError):
        round_close(29.9)
Ejemplo n.º 12
0
def test_round_close_array():
    """Test near-integer output from array input"""
    expected = np.array([30, 4], dtype=int)
    result = round_close(np.array([29.999999, 4.0000001]))
    np.testing.assert_array_equal(result, expected)
Ejemplo n.º 13
0
def test_dtype():
    """Test near-integer output with specific dtype"""
    result = round_close(29.99999, dtype=np.int32)
    assert result == 30
    assert isinstance(result, np.int32)
Ejemplo n.º 14
0
def test_round_close():
    """Test output when input is nearly an integer"""
    result = round_close(29.99999)
    assert result == 30
    assert isinstance(result, np.int64)
Ejemplo n.º 15
0
def _calculate_forecast_period(time_coord,
                               frt_coord,
                               dim_coord=False,
                               coord_spec=TIME_COORDS["forecast_period"]):
    """
    Calculate a forecast period from existing time and forecast reference
    time coordinates.

    Args:
        time_coord (iris.coords.Coord):
            Time coordinate
        frt_coord (iris.coords.Coord):
            Forecast reference coordinate
        dim_coord (bool):
            If true, create an iris.coords.DimCoord instance.  Default is to
            create an iris.coords.AuxCoord.
        coord_spec (collections.namedtuple):
            Specification of units and dtype for the forecast_period
            coordinate.

    Returns:
        iris.coords.Coord:
            Forecast period coordinate corresponding to the input times and
            forecast reference times specified

    Warns:
        UserWarning: If any calculated forecast periods are negative
    """
    # use cell() access method to get datetime.datetime instances
    time_points = np.array([c.point for c in time_coord.cells()])
    forecast_reference_time_points = np.array(
        [c.point for c in frt_coord.cells()])
    required_lead_times = time_points - forecast_reference_time_points
    required_lead_times = np.array(
        [x.total_seconds() for x in required_lead_times])

    if time_coord.bounds is not None:
        time_bounds = np.array([c.bound for c in time_coord.cells()])
        required_lead_time_bounds = time_bounds - forecast_reference_time_points
        required_lead_time_bounds = np.array(
            [[b.total_seconds() for b in x]
             for x in required_lead_time_bounds])
    else:
        required_lead_time_bounds = None

    coord_type = DimCoord if dim_coord else AuxCoord
    result_coord = coord_type(
        required_lead_times,
        standard_name="forecast_period",
        bounds=required_lead_time_bounds,
        units="seconds",
    )

    result_coord.convert_units(coord_spec.units)

    if coord_spec.dtype not in FLOAT_TYPES:
        result_coord.points = round_close(result_coord.points)
        if result_coord.bounds is not None:
            result_coord.bounds = round_close(result_coord.bounds)

    result_coord.points = result_coord.points.astype(coord_spec.dtype)
    if result_coord.bounds is not None:
        result_coord.bounds = result_coord.bounds.astype(coord_spec.dtype)

    if np.any(result_coord.points < 0):
        msg = ("The values for the time {} and "
               "forecast_reference_time {} coordinates from the "
               "input cube have produced negative values for the "
               "forecast_period. A forecast does not generate "
               "values in the past.").format(time_coord.points,
                                             frt_coord.points)
        warnings.warn(msg)

    return result_coord