class Test_calc_confidence_measure(IrisTest): """Test the calc_avg_dist_mean function returns confidence values.""" def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() self.plugin.wdir_complex = WIND_DIR_COMPLEX self.plugin.realization_axis = 0 rvals = np.array( [[6.12323400e-17, 0.996194698], [0.984807753, 0.984807753]], dtype=np.float32, ) self.plugin.r_vals_slice = set_up_variable_cube( rvals, name="r_values", units="1", spatial_grid="equalarea") wdir = np.array([[180.0, 55.0], [280.0, 0.0]], dtype=np.float32) self.plugin.wdir_slice_mean = set_up_variable_cube( wdir, name="wind_from_direction", units="degrees", spatial_grid="equalarea") def test_returns_confidence(self): """First element has two angles directly opposite (90 & 270 degs). Therefore the calculated mean angle of 180 degs is basically meaningless. This code calculates a confidence measure based on how far the individual ensemble realizationss are away from the mean point.""" expected_out = np.array([[0.0, 0.95638061], [0.91284426, 0.91284426]]) self.plugin.calc_confidence_measure() result = self.plugin.confidence_slice.data self.assertIsInstance(result, np.ndarray) self.assertArrayAlmostEqual(result, expected_out)
class Test_find_r_values(IrisTest): """Test the find_r_values function.""" def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() def test_converts_single(self): """Tests that r-value is correctly extracted from complex value.""" # Attach a cube for the plugin to copy in creating the resulting cube: self.plugin.wdir_slice_mean = make_wdir_cube_222()[0][0][0] expected_out = 2.0 # Set-up complex values for angle=45 and r=2 self.plugin.wdir_mean_complex = 1.4142135624 + 1.4142135624j self.plugin.find_r_values() self.assertAlmostEqual(self.plugin.r_vals_slice.data, expected_out) def test_converts_array(self): """Test that code can find r-values from array of complex numbers.""" longitude = DimCoord(np.linspace(-180, 180, 36), standard_name='longitude', units='degrees') cube = Cube(COMPLEX_ANGLES, standard_name="wind_from_direction", dim_coords_and_dims=[(longitude, 0)], units="degree") # Attach a cube for the plugin to copy in creating the resulting cube: self.plugin.wdir_slice_mean = cube self.plugin.wdir_mean_complex = COMPLEX_ANGLES expected_out = np.ones(COMPLEX_ANGLES.shape, dtype=np.float32) self.plugin.find_r_values() result = self.plugin.r_vals_slice.data self.assertIsInstance(result, np.ndarray) self.assertArrayAlmostEqual(result, expected_out)
def test_handles_angle_wrap(self): """Test that code correctly handles 360 and 0 degrees.""" expected_out = 1 + 0j result = WindDirection().deg_to_complex(0) self.assertAlmostEqual(result, expected_out) expected_out = 1 - 0j result = WindDirection().deg_to_complex(360) self.assertAlmostEqual(result, expected_out)
def test_complex(self): """Test neighbourhooding with an array of complex wind directions""" # set up cube with complex wind directions 30-60 degrees a = 0.54066949 + 0.82535591j b = 0.56100423 + 0.80502117j c = 0.5 + 0.8660254j d = 0.59150635 + 0.77451905j expected_data_complex = np.array( [[a, b, b, a, b, c, a], [a, 0.55228934 + 0.81373606j, d, b, d, c, b], [b, 0.54575318 + 0.82027223j, d, b, d, c, b], [b, 0.62200847 + 0.74401694j, b, a, b, c, a], [a, 0.55228934 + 0.81373606j, d, b, d, c, b], [b, 0.57320508 + 0.79282032j, c, c, c, c, c], [c, c, b, a, b, c, a]]) expected_data_deg = np.array( [[ 56.77222443, 55.12808228, 55.12808228, 56.77222443, 55.12808228, 60.00000381, 56.77222443 ], [ 56.77222443, 55.83494186, 52.63074112, 55.12808228, 52.63074112, 60.00000381, 55.12808228 ], [ 55.12808228, 56.36291885, 52.63074112, 55.12808228, 52.63074112, 60.00000381, 55.12808228 ], [ 55.12808228, 50.10391235, 55.12808228, 56.77222443, 55.12808228, 60.00000381, 56.77222443 ], [ 56.77222443, 55.83494186, 52.63074112, 55.12808228, 52.63074112, 60.00000381, 55.12808228 ], [ 55.12808228, 54.13326263, 60.00000381, 60.00000381, 60.00000381, 60.00000381, 60.00000381 ], [ 60.00000381, 60.00000381, 55.12808228, 56.77222443, 55.12808228, 60.00000381, 56.77222443 ]], dtype=np.float32) self.cube.data = WindDirection.deg_to_complex(30. * self.cube.data + 30.) nbcube = (SquareNeighbourhood()._pad_and_calculate_neighbourhood( self.cube, self.mask, 1, 1)) self.assertTrue(np.any(np.iscomplex(nbcube.data))) self.assertArrayAlmostEqual(nbcube.data, expected_data_complex) self.assertArrayAlmostEqual(WindDirection.complex_to_deg(nbcube.data), expected_data_deg)
def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() self.plugin.wdir_complex = WIND_DIR_COMPLEX self.plugin.realization_axis = 0 self.plugin.r_vals_slice = make_wdir_cube_222()[0] self.plugin.r_vals_slice.data = (np.array( [[6.12323400e-17, 0.996194698], [0.984807753, 0.984807753]])) self.plugin.wdir_slice_mean = make_wdir_cube_222()[0] self.plugin.wdir_slice_mean.data = np.array([[180.0, 55.0], [280.0, 0.0]])
def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() # 5x3x4 3D Array containing wind direction in angles. cube = make_wdir_cube_534() self.plugin.wdir_complex = self.plugin.deg_to_complex(cube.data) self.plugin.wdir_slice_mean = (next(cube.slices_over("realization"))) self.plugin.realization_axis = 0 self.expected_wind_mean = (np.array( [[[176.636276, 46.002445, 90.0, 90.0], [170.0, 170.0, 47.0, 36.544231], [333.413239, 320.035217, 10.0, 10.0]]]))
def test_fails_if_data_is_not_array(self): """Test code raises a Type Error if input data not an array.""" input_data = 0 - 1j msg = "Input data is not a numpy array, but" " {}".format( type(input_data)) with self.assertRaisesRegex(TypeError, msg): WindDirection().complex_to_deg(input_data)
def process(wind_direction: cli.inputcube, *, backup_method="neighbourhood"): """Calculates mean wind direction from ensemble realization. Create a cube containing the wind direction averaged over the ensemble realizations. Args: wind_direction (iris.cube.Cube): Cube containing the wind direction from multiple ensemble realizations. backup_method (str): Backup method to use if the complex numbers approach has low confidence. "neighbourhood" (default) recalculates using the complex numbers approach with additional realization extracted from neighbouring grid points from all available realizations. "first_realization" uses the value of realization zero, and should only be used with global lat-lon data. Returns: iris.cube.Cube: Cube containing the wind direction averaged from the ensemble realizations. """ from improver.wind_calculations.wind_direction import WindDirection result = WindDirection(backup_method=backup_method)(wind_direction) return result
def test_fails_if_data_is_not_convertible_to_degrees(self): """Test code raises a ValueError if input cube is not convertible to degrees.""" cube = set_up_temperature_cube() msg = 'Input cube cannot be converted to degrees' with self.assertRaisesRegex(ValueError, msg): WindDirection().process(cube)
def test_fails_if_data_is_not_cube(self): """Test code raises a Type Error if input cube is not a cube.""" input_data = 50.0 msg = "Wind direction input is not a cube, but" " {0}".format( type(input_data)) with self.assertRaisesRegex(TypeError, msg): WindDirection().process(input_data)
def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() self.plugin.wdir_complex = WIND_DIR_COMPLEX self.plugin.realization_axis = 0 rvals = np.array( [[6.12323400e-17, 0.996194698], [0.984807753, 0.984807753]], dtype=np.float32, ) self.plugin.r_vals_slice = set_up_variable_cube( rvals, name="r_values", units="1", spatial_grid="equalarea" ) wdir = np.array([[180.0, 55.0], [280.0, 0.0]], dtype=np.float32) self.plugin.wdir_slice_mean = set_up_variable_cube( wdir, name="wind_from_direction", units="degrees", spatial_grid="equalarea" )
def process(wind_direction, backup_method): """Calculates mean wind direction from ensemble realization. Create a cube containing the wind direction averaged over the ensemble realizations. Args: wind_direction (iris.cube.Cube): Cube containing the wind direction from multiple ensemble realizations. backup_method (str): Backup method to use if the complex numbers approach has low confidence. "first_realization" uses the value of realization zero. "neighbourhood" (default) recalculates using the complex numbers approach with additional realization extracted from neighbouring grid points from all available realizations. Returns (tuple of 3 Cubes): cube_mean_wdir (iris.cube.Cube): Cube containing the wind direction averaged from the ensemble realizations. cube_r_vals (numpy.ndarray): 3D array - Radius taken from average complex wind direction angle. cube_confidence_measure (numpy.ndarray): 3D array - The average distance from mean normalised - used as a confidence value. """ # Returns 3 cubes - r_vals and confidence_measure cubes currently # only contain experimental data to be used for further research. result = ( WindDirection(backup_method=backup_method).process(wind_direction)) return result
def test_with_backup(self): """Test that wind_dir_decider is invoked to select a better value for a low-confidence point.""" # create a low-confidence point self.cube.data[:, 1, 1] = [0.0, 72.0, 144.0, 216.0, 288.0] # set up a larger cube using a "neutral" pad value so that # neighbourhood processing does not fail data = np.full((5, 10, 10), 30.0, dtype=np.float32) data[:, 3:6, 3:7] = self.cube.data[:, :, :].copy() cube = set_up_variable_cube(data, name="wind_from_direction", units="degrees", spatial_grid="equalarea") cube.coord(axis="x").points = np.arange(-50000.0, -31000.0, 2000.0) cube.coord(axis="y").points = np.arange(0.0, 19000.0, 2000.0) self.expected_wind_mean[1, 1] = 30.0870 self.expected_r_vals[1, 1] = 2.665601e-08 self.expected_confidence_measure[1, 1] = 0.0 result_cube, r_vals_cube, confidence_measure_cube = WindDirection( ).process(cube) result = result_cube.data[3:6, 3:7] r_vals = r_vals_cube.data[3:6, 3:7] confidence_measure = confidence_measure_cube.data[3:6, 3:7] self.assertIsInstance(result, np.ndarray) self.assertIsInstance(r_vals, np.ndarray) self.assertIsInstance(confidence_measure, np.ndarray) self.assertArrayAlmostEqual(result, self.expected_wind_mean, decimal=4) self.assertArrayAlmostEqual(confidence_measure, self.expected_confidence_measure) self.assertArrayAlmostEqual(r_vals, self.expected_r_vals)
def main(argv=None): """Load in arguments to calculate mean wind direction from ensemble realizations.""" cli_specific_arguments = [(['--backup_method'], {'dest': 'backup_method', 'default': 'neighbourhood', 'choices': ['neighbourhood', 'first_realization'], 'help': ('Backup method to use if ' 'there is low confidence in' ' the wind_direction. ' 'Options are first_realization' ' or neighbourhood, ' 'first_realization should only ' 'be used with global lat-lon data. ' 'Default is neighbourhood.')})] cli_definition = {'central_arguments': ('input_file', 'output_file'), 'specific_arguments': cli_specific_arguments, 'description': ('Run wind direction to calculate mean' ' wind direction from ' 'ensemble realizations')} args = ArgParser(**cli_definition).parse_args(args=argv) wind_direction = load_cube(args.input_filepath) # Returns 3 cubes - r_vals and confidence_measure cubes currently # only contain experimental data to be used for further research. bmethod = args.backup_method cube_mean_wdir, _, _ = ( WindDirection(backup_method=bmethod).process(wind_direction)) save_netcdf(cube_mean_wdir, args.output_filepath)
def test_basic(self): """Test that the plugin returns expected data types. """ result_cube, r_vals_cube, confidence_measure_cube = WindDirection( ).process(self.cube) self.assertIsInstance(result_cube, Cube) self.assertIsInstance(r_vals_cube, Cube) self.assertIsInstance(confidence_measure_cube, Cube)
def test_basic(self): """Test that the __repr__ returns the expected string.""" result = str(WindDirection()) msg = ( '<WindDirection: backup_method "neighbourhood"; neighbourhood ' 'radius "6000.0"m>' ) self.assertEqual(result, msg)
class Test_complex_to_deg_roundtrip(IrisTest): """Test the complex_to_deg and deg_to_complex functions together.""" def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() self.cube = make_wdir_cube_534() def test_from_deg(self): """Tests that array of values are converted to complex and back.""" tmp_complex = self.plugin.deg_to_complex(self.cube.data) result = self.plugin.complex_to_deg(tmp_complex) self.assertArrayAlmostEqual(result, self.cube.data, decimal=4) def test_from_complex(self): """Tests that array of values are converted to degrees and back.""" tmp_degrees = self.plugin.complex_to_deg(COMPLEX_ANGLES) result = self.plugin.deg_to_complex(tmp_degrees) self.assertArrayAlmostEqual(result, COMPLEX_ANGLES)
def test_fails_if_data_is_not_convertible_to_degrees(self): """Test code raises a ValueError if input cube is not convertible to degrees.""" data = np.array([[300.0, 270.0], [270.0, 300.0]], dtype=np.float32) cube = set_up_variable_cube(data, name="air_temperature", units="K") msg = "Input cube cannot be converted to degrees" with self.assertRaisesRegex(ValueError, msg): WindDirection().process(cube)
def test_return_single_precision(self): """Test that the function returns data of float32.""" result_cube, r_vals_cube, confidence_measure_cube = WindDirection( ).process(self.cube) self.assertEqual(result_cube.dtype, np.float32) self.assertEqual(r_vals_cube.dtype, np.float32) self.assertEqual(confidence_measure_cube.dtype, np.float32)
def test_returns_expected_values(self): """Test that the function returns correct 2D arrays of floats. """ result_cube = WindDirection().process(self.cube) result = result_cube.data self.assertIsInstance(result, np.ndarray) self.assertArrayAlmostEqual(result, self.expected_wind_mean, decimal=4)
def test_runs_function_1st_member(self): """First element has two angles directly opposite (90 & 270 degs). Therefore the calculated mean angle of 180 degs is basically meaningless with an r value of nearly zero. So the code substitutes the wind direction taken from the first ensemble value in its place.""" cube = make_wdir_cube_222() self.plugin = WindDirection(backup_method="first_realization") self.plugin.wdir_complex = WIND_DIR_COMPLEX self.plugin.realization_axis = 0 self.plugin.wdir_slice_mean = cube[0].copy( data=np.array([[180.0, 55.0], [280.0, 0.0]])) self.plugin.wdir_mean_complex = self.plugin.deg_to_complex( self.plugin.wdir_slice_mean.data) expected_out = np.array([[90.0, 55.0], [280.0, 0.0]]) where_low_r = np.array([[True, False], [False, False]]) self.plugin.wind_dir_decider(where_low_r, cube) result = self.plugin.wdir_slice_mean.data self.assertIsInstance(result, np.ndarray) self.assertArrayAlmostEqual(result, expected_out)
def test_runs_function_nbhood(self): """First element has two angles directly opposite (90 & 270 degs). Therefore the calculated mean angle of 180 degs is basically meaningless with an r value of nearly zero. So the code substitutes the wind direction taken using the neighbourhood method.""" expected_out = np.array([[354.91, 55.0], [280.0, 0.0]]) cube = pad_wdir_cube_222() where_low_r = np.pad( np.array([[True, False], [False, False]]), ((4, 4), (4, 4)), "constant", constant_values=(True, True), ) wind_dir_deg_mean = np.array([[180.0, 55.0], [280.0, 0.0]]) self.plugin = WindDirection(backup_method="neighbourhood") self.plugin.realization_axis = 0 self.plugin.n_realizations = 1 self.plugin.wdir_mean_complex = np.pad( self.plugin.deg_to_complex(wind_dir_deg_mean), ((4, 4), (4, 4)), "constant", constant_values=(0.0, 0.0), ) self.plugin.wdir_complex = np.pad( WIND_DIR_COMPLEX, ((0, 0), (4, 4), (4, 4)), "constant", constant_values=(0.0 + 0.0j), ) self.plugin.wdir_slice_mean = cube[0].copy( data=np.pad( wind_dir_deg_mean, ((4, 4), (4, 4)), "constant", constant_values=0.0 ) ) self.plugin.wind_dir_decider(where_low_r, cube) result = self.plugin.wdir_slice_mean.data self.assertIsInstance(result, np.ndarray) self.assertArrayAlmostEqual(result[4:6, 4:6], expected_out, decimal=2)
class Test_calc_wind_dir_mean(IrisTest): """Test the calc_wind_dir_mean function.""" def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() # 5x3x4 3D Array containing wind direction in angles. cube = make_wdir_cube_534() self.plugin.wdir_complex = self.plugin.deg_to_complex(cube.data) self.plugin.wdir_slice_mean = (next(cube.slices_over("realization"))) self.plugin.realization_axis = 0 self.expected_wind_mean = (np.array( [[[176.636276, 46.002445, 90.0, 90.0], [170.0, 170.0, 47.0, 36.544231], [333.413239, 320.035217, 10.0, 10.0]]])) def test_complex(self): """Test that the function defines correct complex mean.""" self.plugin.calc_wind_dir_mean() result = self.plugin.wdir_mean_complex expected_complex = (self.plugin.deg_to_complex( self.expected_wind_mean, radius=np.absolute(result))) self.assertArrayAlmostEqual(result, expected_complex) def test_degrees(self): """Test that the function defines correct degrees cube.""" self.plugin.calc_wind_dir_mean() result = self.plugin.wdir_slice_mean self.assertIsInstance(result, Cube) self.assertIsInstance(result.data, np.ndarray) self.assertArrayAlmostEqual(result.data, self.expected_wind_mean, decimal=4)
def test_returns_expected_values(self): """Test that the function returns correct 2D arrays of floats. """ result_cube, r_vals_cube, confidence_measure_cube = WindDirection( ).process(self.cube) result = result_cube.data r_vals = r_vals_cube.data confidence_measure = confidence_measure_cube.data self.assertIsInstance(result, np.ndarray) self.assertIsInstance(r_vals, np.ndarray) self.assertIsInstance(confidence_measure, np.ndarray) self.assertArrayAlmostEqual(result, self.expected_wind_mean, decimal=4) self.assertArrayAlmostEqual(r_vals, self.expected_r_vals) self.assertArrayAlmostEqual(confidence_measure, self.expected_confidence_measure)
def test_complex_masked(self): """Test complex neighbourhooding works with a mask""" mask_cube = self.cube.copy() mask_cube.data[::] = 1.0 mask_cube.data[1, 2] = 0.0 mask_cube.data[2, 2] = 0.0 self.cube.data = WindDirection.deg_to_complex(30.*self.cube.data + 30.) # set_up_cubes_to_be_neighbourhooded would set masked points to 0.0 self.cube.data[1, 2] = 0.0 self.cube.data[2, 2] = 0.0 mask_cube.rename('mask_data') nbcube = ( SquareNeighbourhood()._pad_and_calculate_neighbourhood( self.cube, mask_cube, 1, 1)) self.assertIsInstance(nbcube, Cube)
def test_with_backup(self): """Test that wind_dir_decider is invoked to select a better value for a low-confidence point.""" self.cube.data[:, 0, 1, 1] = [0., 72., 144., 216., 288.] self.expected_wind_mean[0, 1, 1] = 30.77989074 self.expected_r_vals[1, 1] = 2.384186e-08 self.expected_confidence_measure[1, 1] = 0.0 result_cube, r_vals_cube, confidence_measure_cube = ( WindDirection().process(self.cube)) result = result_cube.data r_vals = r_vals_cube.data confidence_measure = confidence_measure_cube.data self.assertIsInstance(result, np.ndarray) self.assertIsInstance(r_vals, np.ndarray) self.assertIsInstance(confidence_measure, np.ndarray) self.assertArrayAlmostEqual(result, self.expected_wind_mean) self.assertArrayAlmostEqual(r_vals, self.expected_r_vals) self.assertArrayAlmostEqual( confidence_measure, self.expected_confidence_measure)
def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection() self.cube = make_wdir_cube_534()
def test_handles_angle_wrap(self): """Test that code correctly handles 360 and 0 degrees.""" # Input is complex for 0 and 360 deg - both should return 0.0. input_data = np.array([1 + 0j, 1 - 0j]) result = WindDirection().complex_to_deg(input_data) self.assertTrue((result == 0.0).all())
def test_converts_array(self): """Tests that array of complex values are converted to degrees.""" result = WindDirection().complex_to_deg(COMPLEX_ANGLES) self.assertIsInstance(result, np.ndarray) self.assertArrayAlmostEqual(result, np.arange(0.0, 360, 10))
def setUp(self): """Initialise plugin and supply data for tests""" self.plugin = WindDirection()