def _orography_gradients(self) -> Tuple[Cube, Cube]: """ Calculates the dimensionless gradient of self.topography along both spatial axes, smoothed along the perpendicular axis. If spatial coordinates are not in the same units as topography height (m), converts coordinate units in place. Returns: - 2D cube of dimensionless topography gradients in the positive x direction - 2D cube of dimensionless topography gradients in the positive y direction """ self.topography.coord(axis="x").convert_units(self.topography.units) xdim = self.topography.coord_dims(self.topography.coord(axis="x"))[0] self.topography.coord(axis="y").convert_units(self.topography.units) ydim = self.topography.coord_dims(self.topography.coord(axis="y"))[0] # smooth topography by +/- one grid cell along the perpendicular axis # before calculating each gradient (as done in STEPS) topo_smx = uniform_filter1d(self.topography.data, 3, axis=ydim) topo_smx_cube = self.topography.copy(data=topo_smx) gradx, _ = GradientBetweenAdjacentGridSquares( regrid=True)(topo_smx_cube) gradx.units = "1" topo_smy = uniform_filter1d(self.topography.data, 3, axis=xdim) topo_smy_cube = self.topography.copy(data=topo_smy) _, grady = GradientBetweenAdjacentGridSquares( regrid=True)(topo_smy_cube) grady.units = "1" return gradx, grady
def setUp(self): """Set up cube & plugin""" self.plugin = OrographicSmoothingCoefficients( min_smoothing_coefficient=0.5, max_smoothing_coefficient=0.3 ) self.cube = set_up_cube() self.gradient_x, self.gradient_y = GradientBetweenAdjacentGridSquares( regrid=True ).process(self.cube)
def test_gradient(wind_speed, make_expected, grid): """Check calculating the gradient with and without regridding""" expected_x = make_expected(grid["xshape"], 1) expected_y = make_expected(grid["yshape"], 2) gradient_x, gradient_y = GradientBetweenAdjacentGridSquares(regrid=grid["regrid"])( wind_speed ) for result, expected in zip((gradient_x, gradient_y), (expected_x, expected_y)): assert result.name() == expected.name() assert result.attributes == expected.attributes assert result.units == expected.units np.testing.assert_allclose(result.data, expected.data, rtol=1e-5, atol=1e-8)
def test_basic(self): """Test data are as expected""" expected = np.array( [[1.53125, 2.53125, 3.78125], [0.0, 0.5, 2.0], [1.53125, 0.03125, 0.78125]] ) gradient_x, _ = GradientBetweenAdjacentGridSquares(regrid=True).process( self.cube ) smoothing_coefficient_x = self.plugin.unnormalised_smoothing_coefficients( gradient_x ) self.assertArrayAlmostEqual(smoothing_coefficient_x.data, expected)
def process(self, cube): """ This creates the smoothing_coefficient cubes. It returns one for the x direction and one for the y direction. It uses the DifferenceBetweenAdjacentGridSquares plugin to calculate an average gradient across each grid square. These gradients are then used to calculate "smoothing_coefficient" arrays that are normalised between a user-specified max and min. Args: cube (iris.cube.Cube): A 2D field of orography on the grid for which smoothing_coefficients are to be generated. Returns: (iris.cube.CubeList): containing: **smoothing_coefficient_x** (iris.cube.Cube): A cube of orography-dependent smoothing_coefficients calculated in the x direction. **smoothing_coefficient_y** (iris.cube.Cube): A cube of orography-dependent smoothing_coefficients calculated in the y direction. """ if not isinstance(cube, iris.cube.Cube): raise ValueError("OrographicSmoothingCoefficients() expects cube " "input, got {}".format(type(cube))) if len(cube.data.shape) != 2: raise ValueError( "Expected orography on 2D grid, got {} dims".format( len(cube.data.shape))) gradient_x, gradient_y = GradientBetweenAdjacentGridSquares()(cube) ( smoothing_coefficient_x, smoothing_coefficient_y, ) = self.gradient_to_smoothing_coefficient(gradient_x, gradient_y) return iris.cube.CubeList( [smoothing_coefficient_x, smoothing_coefficient_y])
def process(self, cube: Cube, mask: Optional[Cube] = None) -> CubeList: """ This creates the smoothing_coefficient cubes. It returns one for the x direction and one for the y direction. It uses the DifferenceBetweenAdjacentGridSquares plugin to calculate an average gradient across each grid square. These gradients are then used to calculate "smoothing_coefficient" arrays that are normalised between a user-specified max and min. Args: cube: A 2D field of orography on the grid for which smoothing_coefficients are to be generated. mask: A mask that defines where the smoothing coefficients should be zeroed. The mask must have the same spatial dimensions as the orography cube. How the mask is used to zero smoothing coefficients is determined by the plugin configuration arguments. Returns: - A cube of orography-dependent smoothing_coefficients calculated in the x direction. - A cube of orography-dependent smoothing_coefficients calculated in the y direction. """ if not isinstance(cube, iris.cube.Cube): raise ValueError( "OrographicSmoothingCoefficients() expects cube " "input, got {}".format(type(cube)) ) if len(cube.data.shape) != 2: raise ValueError( "Expected orography on 2D grid, got {} dims".format( len(cube.data.shape) ) ) if mask is not None and ( mask.coords(dim_coords=True) != cube.coords(dim_coords=True) ): raise ValueError( "If a mask is provided it must have the same grid as the " "orography field." ) # Enforce coordinate order for simpler processing. original_order = [crd.name() for crd in cube.coords(dim_coords=True)] target_order = [cube.coord(axis="y").name(), cube.coord(axis="x").name()] enforce_coordinate_ordering(cube, target_order) # Returns two cubes, ordered gradient in x and gradient in y. gradients = GradientBetweenAdjacentGridSquares()(cube) # Calculate unscaled smoothing coefficients. smoothing_coefficients = iris.cube.CubeList() iterator = zip( gradients, ["smoothing_coefficient_x", "smoothing_coefficient_y"] ) for gradient, name in iterator: coefficient_data = self.unnormalised_smoothing_coefficients(gradient) smoothing_coefficients.append( self.create_coefficient_cube( coefficient_data, gradient, name, cube.attributes.copy() ) ) # Scale the smoothing coefficients between provided values. smoothing_coefficients = self.scale_smoothing_coefficients( smoothing_coefficients ) # If a mask has been provided, zero coefficients where required. if mask is not None: enforce_coordinate_ordering(mask, target_order) self.zero_masked(*smoothing_coefficients, mask) for smoothing_coefficient in smoothing_coefficients: enforce_coordinate_ordering(smoothing_coefficient, original_order) return smoothing_coefficients