def test_upperbound_fails(self): """test checking that an exception is raised when the _make_mask_cube method is called with only an upper bound.""" emsg = "should have both an upper and lower limit" with self.assertRaisesRegex(TypeError, emsg): _make_mask_cube(self.mask, self.coords, [None, self.upper], self.units)
def test_wrong_number_of_bounds(self): """test checking that an exception is raised when the _make_mask_cube method is called with an incorrect number of bounds.""" emsg = "should have only an upper and lower limit" with self.assertRaisesRegex(TypeError, emsg): _make_mask_cube(self.mask, self.coords, [0], self.units) with self.assertRaisesRegex(TypeError, emsg): _make_mask_cube(self.mask, self.coords, [0, 2, 4], self.units)
def test_upperbound(self): """test creating cube with upper threshold only set""" result = _make_mask_cube(self.mask, self.key, self.coords, upper_threshold=self.upper) self.assertEqual( result.coords('topographic_bound_upper')[0].points, self.upper)
def test_nobounds(self): """test creating cube with neither upper nor lower threshold set""" result = _make_mask_cube(self.mask, self.key, self.coords) self.assertEqual(result.coord('longitude'), self.x_coord) self.assertEqual(result.coord('latitude'), self.y_coord) self.assertArrayEqual(result.data, self.mask) self.assertEqual(result.attributes['Topographical Type'], self.key.title())
def test_cube_attribute_no_seapoints(self): """Test the new attribute is added to the cube.""" result = _make_mask_cube( self.mask, self.coords, [self.lower, self.upper], self.units ) self.assertEqual( result.attributes["topographic_zones_include_seapoints"], "False" )
def test_bothbounds(self): """test creating cube with both thresholds set""" result = _make_mask_cube( self.mask, self.coords, [self.lower, self.upper], self.units ) self.assertEqual(result.coord("topographic_zone").bounds[0][1], self.upper) self.assertEqual(result.coord("topographic_zone").bounds[0][0], self.lower) self.assertEqual( result.coord("topographic_zone").points, np.mean([self.lower, self.upper]) ) self.assertEqual(result.coord("topographic_zone").units, Unit("m"))
def test_bothbounds(self): """test creating cube with both thresholds set""" result = _make_mask_cube(self.mask, self.key, self.coords, topographic_bounds=[self.lower, self.upper]) self.assertEqual( result.coord('topographic_zone').bounds[0][1], self.upper) self.assertEqual( result.coord('topographic_zone').bounds[0][0], self.lower) self.assertEqual( result.coord('topographic_zone').points, np.mean([self.lower, self.upper]))
def process(self, orography, thresholds_dict, landmask=None): """Calculate the weights depending upon where the orography point is within the topographic zones. Args: orography (iris.cube.Cube): Orography on standard grid. thresholds_dict (dict): Definition of orography bands required. The expected format of the dictionary is e.g. `{'bounds': [[0, 50], [50, 200]], 'units': 'm'}` landmask (iris.cube.Cube): Land mask on standard grid, with land points set to one and sea points set to zero. If provided sea points are masked out in the output array. Returns: iris.cube.Cube: Cube containing the weights depending upon where the orography point is within the topographic zones. """ # Check that orography is a 2d cube. if len(orography.shape) != 2: msg = ("The input orography cube should be two-dimensional." "The input orography cube has {} dimensions".format( len(orography.shape))) raise InvalidCubeError(msg) # Find bands and midpoints from bounds. bands = np.array(thresholds_dict["bounds"], dtype=np.float32) threshold_units = thresholds_dict["units"] # Create topographic_zone_cube first, so that a cube is created for # each band. This will allow the data for neighbouring bands to be # put into the cube. mask_data = np.zeros(orography.shape, dtype=np.float32) topographic_zone_cubes = iris.cube.CubeList([]) for band in bands: sea_points_included = not landmask topographic_zone_cube = _make_mask_cube( mask_data, orography.coords(), band, threshold_units, sea_points_included=sea_points_included, ) topographic_zone_cubes.append(topographic_zone_cube) topographic_zone_weights = topographic_zone_cubes.concatenate_cube() topographic_zone_weights.data = topographic_zone_weights.data.astype( np.float32) # Ensure topographic_zone coordinate units is equal to orography units. topographic_zone_weights.coord("topographic_zone").convert_units( orography.units) # Read bands from cube, now that they can be guaranteed to be in the # same units as the orography. The bands are converted to a list, so # that they can be iterated through. bands = list(topographic_zone_weights.coord("topographic_zone").bounds) midpoints = topographic_zone_weights.coord("topographic_zone").points # Raise a warning, if orography extremes are outside the extremes of # the bands. if np.max(orography.data) > np.max(bands): msg = ("The maximum orography is greater than the uppermost band. " "This will potentially cause the topographic zone weights " "to not sum to 1 for a given grid point.") warnings.warn(msg) if np.min(orography.data) < np.min(bands): msg = ("The minimum orography is lower than the lowest band. " "This will potentially cause the topographic zone weights " "to not sum to 1 for a given grid point.") warnings.warn(msg) # Insert the appropriate weights into the topographic zone cube. This # includes the weights from the band that a point is in, as well as # the contribution from an adjacent band. for band_number, band in enumerate(bands): # Determine the points that are within the specified band. mask_y, mask_x = np.where((orography.data > band[0]) & (orography.data <= band[1])) orography_band = np.full(orography.shape, np.nan, dtype=np.float32) orography_band[mask_y, mask_x] = orography.data[mask_y, mask_x] # Calculate the weights. This involves calculating the # weights for all the orography but only inserting weights # that are within the band into the topographic_zone_weights cube. weights = self.calculate_weights(orography_band, band) topographic_zone_weights.data[band_number, mask_y, mask_x] = weights[mask_y, mask_x] # Calculate the contribution to the weights from the adjacent # lower band. topographic_zone_weights.data = self.add_weight_to_lower_adjacent_band( topographic_zone_weights.data, orography_band, midpoints[band_number], band_number, ) # Calculate the contribution to the weights from the adjacent # upper band. topographic_zone_weights.data = self.add_weight_to_upper_adjacent_band( topographic_zone_weights.data, orography_band, midpoints[band_number], band_number, len(bands) - 1, ) # Metadata updates topographic_zone_weights.rename("topographic_zone_weights") topographic_zone_weights.units = Unit("1") # Mask output weights using a land-sea mask. topographic_zone_masked_weights = iris.cube.CubeList([]) for topographic_zone_slice in topographic_zone_weights.slices_over( "topographic_zone"): if landmask: topographic_zone_slice.data = GenerateOrographyBandAncils( ).sea_mask(landmask.data, topographic_zone_slice.data) topographic_zone_masked_weights.append(topographic_zone_slice) topographic_zone_weights = topographic_zone_masked_weights.merge_cube() return topographic_zone_weights