def test_process(self): """Test that the RecursiveFilter plugin returns the correct data""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) result = plugin.process(self.cube, alphas_x=None, alphas_y=None) expected = 0.13382206 self.assertAlmostEqual(result.data[0][2][2], expected)
def test_return_type(self): """Test that the RecursiveFilter plugin returns an iris.cube.Cube.""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) result = plugin.process(self.cube, alphas_x=None, alphas_y=None) self.assertIsInstance(result, Cube)
def test_different_smoothing_coefficients(self): """Test that the _run_recursion method returns expected values when smoothing_coefficient values are different in the x and y directions""" cube = iris.util.squeeze(self.cube) smoothing_coefficient_y = 0.5 * self.smoothing_coefficient_x smoothing_coefficients_x = ( RecursiveFilter()._set_smoothing_coefficients( cube, self.smoothing_coefficient_x, None)) smoothing_coefficients_y = ( RecursiveFilter()._set_smoothing_coefficients( cube, smoothing_coefficient_y, None)) padded_cube = pad_cube_with_halo(cube, 2, 2) result = RecursiveFilter()._run_recursion(padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, 1) # slice back down to the source grid - easier to visualise! unpadded_result = result.data[2:-2, 2:-2] expected_result = np.array( [[0.01620921, 0.02866841, 0.05077430, 0.02881413, 0.01657352], [0.03978802, 0.06457599, 0.10290188, 0.06486591, 0.04051282], [0.10592333, 0.15184643, 0.19869247, 0.15238355, 0.10726611], [0.03978982, 0.06457873, 0.10290585, 0.06486866, 0.04051464], [0.01621686, 0.02868005, 0.05079120, 0.02882582, 0.01658128]]) self.assertArrayAlmostEqual(unpadded_result, expected_result)
def test_different_smoothing_coefficients(self): """Test that the _run_recursion method returns expected values when smoothing_coefficient values are different in the x and y directions""" edge_width = 1 cube = iris.util.squeeze(self.cube) smoothing_coefficients_x, smoothing_coefficients_y = RecursiveFilter( edge_width=edge_width )._pad_coefficients(*self.smoothing_coefficients_alternative) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter(edge_width=edge_width)._run_recursion( padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, 1 ) # slice back down to the source grid - easier to visualise! unpadded_result = result.data[2:-2, 2:-2] expected_result = np.array( [ [0.01320939, 0.02454378, 0.04346254, 0.02469828, 0.01359563], [0.03405095, 0.06060188, 0.09870366, 0.06100013, 0.03504659], [0.0845406, 0.13908109, 0.18816182, 0.14006987, 0.08701254], [0.03405397, 0.06060749, 0.09871361, 0.06100579, 0.03504971], [0.01322224, 0.02456765, 0.04350482, 0.0247223, 0.01360886], ], dtype=np.float32, ) self.assertArrayAlmostEqual(unpadded_result, expected_result)
def test_alphas_shape_does_not_match_data_shape_when_alphas_not_none(self): """Test if array dimensions of alphas array do not match dimensions of data_array when alphas is not set to None (invalid).""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) msg = "Dimensions of alphas array do not match dimensions " with self.assertRaisesRegexp(ValueError, msg): plugin.set_alphas(self.cube, self.alpha_x, self.alphas_cube2)
def test_alpha_floats_nan_in_data(self): """Test that the RecursiveFilter plugin returns the correct data when using float alpha values and the data contains nans.""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) self.cube.data[0][3][2] = np.nan result = plugin.process(self.cube, alphas_x=None, alphas_y=None) expected = 0.11979733 self.assertAlmostEqual(result.data[0][2][2], expected)
def test_dimensions_of_output_array_is_as_expected(self): """Test that the RecursiveFilter plugin returns a data array with the correct dimensions""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) result = plugin.process(self.cube, alphas_x=None, alphas_y=None) # Output data array should have same dimensions as input data array expected_shape = (1, 5, 5) self.assertEqual(result.data.shape, expected_shape) self.assertEqual(result.data.shape, expected_shape)
def test_basic_method(self): """Test that the returned recurse_forward_x array has the expected type and result.""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) result = plugin.recurse_forward_x(self.cube.data[0, :], self.alphas_cube1.data[0, :]) expected_result = 0.196875 self.assertIsInstance(result, np.ndarray) self.assertAlmostEqual(result[4][2], expected_result)
def test_basic_method(self): """Test that the returned recurse_backwards_y array has the expected type and result.""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) result = plugin.recurse_backwards_y(self.cube.data[:, 0], self.alphas_cube1.data[:, 0]) expected_result = 0.0125 self.assertIsInstance(result, np.ndarray) self.assertAlmostEqual(result[0][0], expected_result)
def test_return_type(self): """Test that the _run_recursion method returns an iris.cube.Cube.""" edge_width = 1 cube = iris.util.squeeze(self.cube) alphas_x = RecursiveFilter()._set_alphas(cube, self.alpha_x, None) alphas_y = RecursiveFilter()._set_alphas(cube, self.alpha_y, None) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter()._run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) self.assertIsInstance(result, Cube)
def test_return_type(self): """Test that the run_recursion method returns an iris.cube.Cube.""" edge_width = 1 alphas_x = RecursiveFilter().set_alphas(self.cube, self.alpha_x, None) alphas_y = RecursiveFilter().set_alphas(self.cube, self.alpha_y, None) padded_cube = SquareNeighbourhood().pad_cube_with_halo( self.cube, edge_width, edge_width) result = RecursiveFilter().run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) self.assertIsInstance(result, Cube)
def test_expected_result(self): """Test that the run_recursion method returns the expected value.""" edge_width = 1 alphas_x = RecursiveFilter().set_alphas(self.cube, self.alpha_x, None) alphas_y = RecursiveFilter().set_alphas(self.cube, self.alpha_y, None) padded_cube = SquareNeighbourhood().pad_cube_with_halo( self.cube, edge_width, edge_width) result = RecursiveFilter().run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) expected_result = 0.13382206 self.assertAlmostEqual(result.data[4][4], expected_result)
def test_alphas_cube_and_alpha_not_set(self): """Test error is raised when both alphas_cube and alpha are set to None (invalid).""" alpha_x = None alpha_y = None alphas_cube = None plugin = RecursiveFilter(alpha_x=alpha_x, alpha_y=alpha_y, iterations=self.iterations) msg = "A value for alpha must be set if alphas_cube is " with self.assertRaisesRegexp(ValueError, msg): plugin.set_alphas(self.cube, alpha_x, alphas_cube)
def test_result_basic(self): """Test that the _run_recursion method returns the expected value.""" edge_width = 1 cube = iris.util.squeeze(self.cube) alphas_x = RecursiveFilter()._set_alphas(cube, self.alpha_x, None) alphas_y = RecursiveFilter()._set_alphas(cube, self.alpha_y, None) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter()._run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) expected_result = 0.13382206 self.assertAlmostEqual(result.data[4][4], expected_result)
def test_alphas_array_as_expected_when_alphas_is_not_none(self): """Test that the returned alphas array has the expected result when alphas=None.""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) result = plugin.set_alphas(self.cube, self.alpha_x, self.alphas_cube1) expected_result = 0.5 self.assertIsInstance(result.data, np.ndarray) self.assertEqual(result.data[0][2], expected_result) # Check shape: Array should be padded with 4 extra rows/columns expected_shape = (9, 9) self.assertEqual(result.shape, expected_shape)
def test_alpha_cubes_masked_data(self): """Test that the RecursiveFilter plugin returns the correct data when using alpha cubes and a masked data cube.""" plugin = RecursiveFilter(alpha_x=None, alpha_y=None, iterations=self.iterations) mask = np.zeros((self.cube.data.shape)) mask[0][3][2] = 1 self.cube.data = np.ma.MaskedArray(self.cube.data, mask=mask) result = plugin.process(self.cube, alphas_x=self.alphas_cube, alphas_y=self.alphas_cube) expected = 0.11979733 self.assertAlmostEqual(result.data[0][2][2], expected)
def test_alpha_floats_nan_in_masked_data(self): """Test that the RecursiveFilter plugin returns the correct data when using float alpha values, the data contains nans and the data is masked (but not the nan value).""" plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) self.cube.data[0][3][2] = np.nan mask = np.zeros((self.cube.data.shape)) mask[0][1][2] = 1 self.cube.data = np.ma.MaskedArray(self.cube.data, mask=mask) result = plugin.process(self.cube, alphas_x=None, alphas_y=None) expected = 0.105854129 self.assertAlmostEqual(result.data[0][2][2], expected)
def test_basic(self): """Test that the run_recursion method returns an iris.cube.Cube.""" edge_width = 1 alphas_x = None alphas_y = None plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) alphas_x = plugin.set_alphas(self.cube, self.alpha_x, alphas_x) alphas_y = plugin.set_alphas(self.cube, self.alpha_y, alphas_y) padded_cube = SquareNeighbourhood().pad_cube_with_halo(self.cube, edge_width, edge_width) result = plugin.run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) self.assertIsInstance(result, Cube)
def test_return_type(self): """Test that the _run_recursion method returns an iris.cube.Cube.""" edge_width = 1 cube = iris.util.squeeze(self.cube) smoothing_coefficients_x, smoothing_coefficients_y = RecursiveFilter( edge_width=edge_width )._pad_coefficients(*self.smoothing_coefficients) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter(edge_width=1)._run_recursion( padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, self.iterations, ) self.assertIsInstance(result, Cube)
def test_smoothing_coefficient_y_lt_zero(self): """Test when an smoothing_coefficient_y value <= zero is given (invalid).""" smoothing_coefficient_y = -0.5 msg = "Invalid smoothing_coefficient_y: must be > 0 and <= 0.5: -0.5" with self.assertRaisesRegex(ValueError, msg): RecursiveFilter(smoothing_coefficient_y=smoothing_coefficient_y)
def test_result_basic(self): """Test that the _run_recursion method returns the expected value.""" edge_width = 1 cube = iris.util.squeeze(self.cube) smoothing_coefficients_x, smoothing_coefficients_y = RecursiveFilter( edge_width=edge_width )._pad_coefficients(*self.smoothing_coefficients) padded_cube = pad_cube_with_halo(cube, 2 * edge_width, 2 * edge_width) result = RecursiveFilter(edge_width=edge_width)._run_recursion( padded_cube, smoothing_coefficients_x, smoothing_coefficients_y, self.iterations, ) expected_result = 0.12302627 self.assertAlmostEqual(result.data[4][4], expected_result)
def test_smoothing_coefficients_mismatched_y_points(self): """Test that an error is raised if the y smoothing_coefficients_cube has mismatched coordinate points compared to the data cube.""" msg = "The points of the y spatial dimension of the smoothing coefficients" with self.assertRaisesRegex(ValueError, msg): RecursiveFilter(edge_width=1)._validate_smoothing_coefficients( self.cube, self.smoothing_coefficients_cube_wrong_y_points)
def test_alphas_cube_and_alpha_both_set(self): """Test error is raised when both alphas_cube and alpha are set.""" alpha = 0.5 alphas_cube = self.alphas_cube msg = "A cube of alpha values and a single float value for" with self.assertRaisesRegex(ValueError, msg): RecursiveFilter()._set_alphas(self.cube, alpha, alphas_cube)
def test_mismatched_dimensions_alphas_cube_data_cube(self): """Test that an error is raised if the alphas_cube is of a different shape to the data cube.""" msg = "Dimensions of alphas array do not match dimensions " with self.assertRaisesRegex(ValueError, msg): RecursiveFilter()._set_alphas(self.cube, None, self.alphas_cube_wrong_dims)
def process( cube: cli.inputcube, smoothing_coefficients: cli.inputcubelist, *, iterations: int = 1, ): """Module to apply a recursive filter to neighbourhooded data. Run a recursive filter to convert a square neighbourhood into a Gaussian-like kernel or smooth over short distances. The filter uses a smoothing_coefficient (between 0 and 1) to control what proportion of the probability is passed onto the next grid-square in the x and y directions. The smoothing_coefficient can be set on a grid square by grid-square basis for the x and y directions separately (using two arrays of smoothing_coefficients of the same dimensionality as the domain). Args: cube (iris.cube.Cube): Cube to be processed. smoothing_coefficients (iris.cube.CubeList): CubeList describing the smoothing_coefficients to be used in the x and y directions. iterations (int): Number of times to apply the filter. (Typically < 3) Number of iterations should be 2 or less, higher values have been shown to lead to poorer conservation. Returns: iris.cube.Cube: The processed Cube. """ from improver.nbhood.recursive_filter import RecursiveFilter plugin = RecursiveFilter(iterations=iterations) return plugin(cube, smoothing_coefficients=smoothing_coefficients)
def test_expected_result(self): """Test that the run_recursion method returns an iris.cube.Cube.""" edge_width = 1 alphas_x = None alphas_y = None plugin = RecursiveFilter(alpha_x=self.alpha_x, alpha_y=self.alpha_y, iterations=self.iterations) alphas_x = plugin.set_alphas(self.cube, self.alpha_x, alphas_x) alphas_y = plugin.set_alphas(self.cube, self.alpha_y, alphas_y) padded_cube = SquareNeighbourhood().pad_cube_with_halo(self.cube, edge_width, edge_width) result = plugin.run_recursion(padded_cube, alphas_x, alphas_y, self.iterations) expected_result = 0.13382206 self.assertAlmostEqual(result.data[4][4], expected_result)
def test_coordinate_reordering_with_different_smoothing_coefficients(self): """Test that x and y smoothing_coefficients still apply to the right coordinate when the input cube spatial dimensions are (x, y) not (y, x)""" enforce_coordinate_ordering(self.cube, ["realization", "longitude", "latitude"]) plugin = RecursiveFilter(iterations=self.iterations,) result = plugin( self.cube, smoothing_coefficients=self.smoothing_coefficients_alternative ) expected_result = np.array( [ [0.02554158, 0.05397786, 0.1312837, 0.05397786, 0.02554158], [0.03596632, 0.07334216, 0.1668669, 0.07334216, 0.03596632], [0.05850913, 0.11031596, 0.21073693, 0.11031596, 0.05850913], [0.03596632, 0.07334216, 0.1668669, 0.07334216, 0.03596632], [0.02554158, 0.05397786, 0.1312837, 0.05397786, 0.02554158], ], dtype=np.float32, ) self.assertSequenceEqual( [x.name() for x in result.coords(dim_coords=True)], ["realization", "longitude", "latitude"], ) self.assertArrayAlmostEqual(result.data[0], expected_result)
def test_alpha_y_gt_unity(self): """Test when an alpha_y value > unity is given (invalid).""" alpha_y = 1.1 msg = "Invalid alpha_y: must be >= 0 and < 1: 1.1" with self.assertRaisesRegexp(ValueError, msg): RecursiveFilter(alpha_x=None, alpha_y=alpha_y, iterations=None, edge_width=1)
def test_alpha_y_lt_zero(self): """Test when an alpha_y value < zero is given (invalid).""" alpha_y = -0.5 msg = "Invalid alpha_y: must be >= 0 and < 1: -0.5" with self.assertRaisesRegexp(ValueError, msg): RecursiveFilter(alpha_x=None, alpha_y=alpha_y, iterations=None, edge_width=1)
def test_iterations(self): """Test when iterations value less than unity is given (invalid).""" iterations = 0 msg = "Invalid number of iterations: must be >= 1: 0" with self.assertRaisesRegexp(ValueError, msg): RecursiveFilter(alpha_x=None, alpha_y=None, iterations=iterations, edge_width=1)