예제 #1
0
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)
예제 #2
0
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)
예제 #3
0
    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)
예제 #4
0
    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)
예제 #5
0
 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]])
예제 #6
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]]]))
예제 #7
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)
예제 #8
0
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
예제 #9
0
 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)
예제 #10
0
 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)
예제 #11
0
 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"
     )
예제 #12
0
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
예제 #13
0
    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)
예제 #14
0
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)
예제 #15
0
    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)
예제 #16
0
 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)
예제 #17
0
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)
예제 #18
0
    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)
예제 #19
0
    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)
예제 #20
0
    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)
예제 #21
0
    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)
예제 #22
0
    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)
예제 #23
0
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)
예제 #24
0
    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)
예제 #25
0
    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)
예제 #26
0
    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)
예제 #27
0
 def setUp(self):
     """Initialise plugin and supply data for tests"""
     self.plugin = WindDirection()
     self.cube = make_wdir_cube_534()
예제 #28
0
 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())
예제 #29
0
 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))
예제 #30
0
 def setUp(self):
     """Initialise plugin and supply data for tests"""
     self.plugin = WindDirection()