def test_basic(self): """Test that a number is returned of the expected value.""" cycletime = "20171122T0000Z" dt = 419808.0 result = cycletime_to_number(cycletime) self.assertIsInstance(result, float) self.assertAlmostEqual(result, dt)
def test_alternative_units_defined(self): """Test when alternative units are defined.""" cycletime = "20171122T0000Z" dt = 1511308800.0 result = cycletime_to_number( cycletime, time_unit="seconds since 1970-01-01 00:00:00") self.assertAlmostEqual(result, dt)
def test_alternative_calendar_defined(self): """Test when an alternative calendar is defined.""" cycletime = "20171122T0000Z" dt = 419520.0 result = cycletime_to_number( cycletime, calendar="365_day") self.assertAlmostEqual(result, dt)
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)
def test_cycletime_format_defined(self): """Test when a cycletime is defined.""" cycletime = "201711220000" dt = 419808.0 result = cycletime_to_number( cycletime, cycletime_format="%Y%m%d%H%M") self.assertAlmostEqual(result, dt)
def test_alternative_units_defined(self): """Test when alternative units are defined. The result is cast as an integer as seconds should be of this type and compared as such. There are small precision errors in the 7th decimal place of the returned float.""" cycletime = "20171122T0000Z" dt = 1511308800 result = cycletime_to_number( cycletime, time_unit="seconds since 1970-01-01 00:00:00") self.assertEqual(int(np.round(result)), dt)
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)
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)
def conform_metadata(cube, cube_orig, coord, cycletime=None): """Ensure that the metadata conforms after blending together across the chosen coordinate. The metadata adjustments are: - Forecast reference time: If a cycletime is not present, the most recent available forecast_reference_time is used. If a cycletime is present, the cycletime is used as the forecast_reference_time instead. - Forecast period: If a forecast_period coordinate is present, and cycletime is not present, the lowest forecast_period is used. If a forecast_period coordinate is present, and the cycletime is present, forecast_periods are forceably re-calculated from the time and forecast_reference_time coordinates so that their values are self-consistent. - Forecast reference time and time: If forecast_reference_time and time coordinates are present, then a forecast_period coordinate is calculated and added to the cube. - Model_id, model_realization and realization coordinates are removed. - A title attribute is added to the cube if none is found. Otherwise the attributes are unchanged. Args: cube (iris.cube.Cube): Cube containing the metadata to be adjusted. cube_orig (iris.cube.Cube): Cube containing metadata that may be useful for adjusting metadata on the `cube` variable. coord (str): Coordinate that has been blended. This allows specific metadata changes to be limited to whichever coordinate is being blended. cycletime (str): The cycletime in a YYYYMMDDTHHMMZ format e.g. 20171122T0100Z. Returns: cube (iris.cube.Cube): Cube containing the adjusted metadata. """ # unify time coordinates for cycle and grid (model) blends if coord in ["forecast_reference_time", "model_id"]: # if cycle blending, update forecast reference time and remove bounds if cube.coords("forecast_reference_time"): if cycletime is None: new_cycletime = (np.max( cube_orig.coord("forecast_reference_time").points)) else: cycletime_units = ( cube_orig.coord("forecast_reference_time").units.origin) cycletime_calendar = ( cube.coord("forecast_reference_time").units.calendar) new_cycletime = cycletime_to_number( cycletime, time_unit=cycletime_units, calendar=cycletime_calendar) # Preserve the data type to avoid converting ints to floats. frt_type = cube.coord("forecast_reference_time").dtype new_cycletime = np.round(new_cycletime).astype(frt_type) cube.coord("forecast_reference_time").points = new_cycletime cube.coord("forecast_reference_time").bounds = None # recalculate forecast period coordinate if cube.coords("forecast_period"): forecast_period = forecast_period_coord( cube, force_lead_time_calculation=True) forecast_period.convert_units(cube.coord("forecast_period").units) forecast_period.var_name = cube.coord("forecast_period").var_name cube.replace_coord(forecast_period) elif cube.coords("forecast_reference_time") and cube.coords("time"): forecast_period = forecast_period_coord(cube) ndim = cube.coord_dims("time") cube.add_aux_coord(forecast_period, data_dims=ndim) # update blended cube attributes if "title" not in cube.attributes.keys(): cube.attributes["title"] = "IMPROVER Model Forecast" # remove appropriate scalar coordinates for crd in ["model_id", "model_configuration", "realization"]: if cube.coords(crd) and cube.coord(crd).shape == (1, ): cube.remove_coord(crd) return cube
def conform_metadata(cube, cube_orig, coord, cycletime=None, coords_for_bounds_removal=None): """Ensure that the metadata conforms after blending together across the chosen coordinate. The metadata adjustments are: - Forecast reference time: If a cycletime is not present, the most recent available forecast_reference_time is used. If a cycletime is present, the cycletime is used as the forecast_reference_time instead. - Forecast period: If a forecast_period coordinate is present, and cycletime is not present, the lowest forecast_period is used. If a forecast_period coordinate is present, and the cycletime is present, forecast_periods are forceably calculated from the time and forecast_reference_time coordinate. This is because, if the cycletime is present, then the forecast_reference_time will also have been just re-calculated, so the forecast_period coordinate needs to be reset to match the newly calculated forecast_reference_time. - Forecast reference time and time: If forecast_reference_time and time coordinates are present, then a forecast_period coordinate is calculated and added to the cube. - Model_id, model_realization and realization coordinates are removed. - Remove bounds from the scalar coordinates, if the coordinates are specified within the coords_for_bounds_removal argument. Args: cube (iris.cube.Cube): Cube containing the metadata to be adjusted. cube_orig (iris.cube.Cube): Cube containing metadata that may be useful for adjusting metadata on the `cube` variable. coord (str): Coordinate that has been blended. This allows specific metadata changes to be limited to whichever coordinate is being blended. Keyword Args: cycletime (str): The cycletime in a YYYYMMDDTHHMMZ format e.g. 20171122T0100Z. coords_for_bounds_removal (None or list): List of coordinates that are scalar and should have their bounds removed. Returns: cube (iris.cube.Cube): Cube containing the adjusted metadata. """ if coord in ["forecast_reference_time", "model"]: if cube.coords("forecast_reference_time"): if cycletime is None: new_cycletime = (np.max( cube_orig.coord("forecast_reference_time").points)) else: cycletime_units = ( cube_orig.coord("forecast_reference_time").units.origin) cycletime_calendar = ( cube.coord("forecast_reference_time").units.calendar) new_cycletime = cycletime_to_number( cycletime, time_unit=cycletime_units, calendar=cycletime_calendar) # Preserve the data type to avoid converting ints to floats. fr_type = cube.coord("forecast_reference_time").dtype new_cycletime = np.round(new_cycletime).astype(fr_type) cube.coord("forecast_reference_time").points = new_cycletime cube.coord("forecast_reference_time").bounds = None if cube.coords("forecast_period"): forecast_period = (forecast_period_coord( cube, force_lead_time_calculation=True)) forecast_period.bounds = None forecast_period.convert_units(cube.coord("forecast_period").units) forecast_period.var_name = cube.coord("forecast_period").var_name cube.replace_coord(forecast_period) elif cube.coords("forecast_reference_time") and cube.coords("time"): forecast_period = (forecast_period_coord(cube)) ndim = cube.coord_dims("time") cube.add_aux_coord(forecast_period, data_dims=ndim) for coord in ["model_id", "model_realization", "realization"]: if cube.coords(coord) and cube.coord(coord).shape == (1, ): cube.remove_coord(coord) if coords_for_bounds_removal is None: coords_for_bounds_removal = [] for coord in cube.coords(): if coord.name() in coords_for_bounds_removal: if coord.shape == (1, ) and coord.has_bounds(): coord.bounds = None return cube