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)
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)
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
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
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)
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