def test_metadata(self):
     """Test name and cell methods are updated as expected after conversion"""
     threshold_coord = find_threshold_coordinate(self.cube)
     expected_name = threshold_coord.name()
     expected_units = threshold_coord.units
     # add a cell method indicating "max in period" for the underlying data
     self.cube.add_cell_method(
         CellMethod("max", coords="time", comments=f"of {expected_name}"))
     expected_cell_method = CellMethod("max", coords="time")
     result = Plugin().process(self.cube)
     self.assertEqual(result.name(), expected_name)
     self.assertEqual(result.units, expected_units)
     self.assertEqual(result.cell_methods[0], expected_cell_method)
Example #2
0
 def test_negative_percentiles(self):
     """
     Test that the plugin returns the expected values for the
     percentiles if negative probabilities are requested.
     """
     cube = self.temperature_cube
     percentiles = [-10, 10]
     plugin = Plugin()
     msg = "NaNs are present within the result for the"
     with self.assertRaisesRegex(ValueError, msg):
         plugin._location_and_scale_parameters_to_percentiles(
             self.location_parameter, self.scale_parameter, cube,
             percentiles)
Example #3
0
 def test_threshold_below_cube(self):
     """Test that the expected probabilites are returned for a cube in which
     they are calculated below the thresholds."""
     self.template_cube.coord(
         var_name="threshold"
     ).attributes["spp__relative_to_threshold"] = "below"
     expected = (np.ones((3, 3, 3)) * [0.25, 0.5, 0.75]).T
     result = Plugin()._location_and_scale_parameters_to_probabilities(
         self.location_parameter_values,
         self.scale_parameter_values,
         self.template_cube,
     )
     np.testing.assert_allclose(result.data, expected, rtol=1.0e-4)
Example #4
0
 def test_probability_data(self):
     """
     Test that the plugin returns the expected results for the
     probabilities, where they've been padded with zeros and ones to
     represent the extreme ends of the Cumulative Distribution Function.
     """
     zero_array = np.zeros(self.probabilities_for_cdf[:, 0].shape)
     one_array = np.ones(self.probabilities_for_cdf[:, 0].shape)
     result = Plugin()._add_bounds_to_thresholds_and_probabilities(
         self.threshold_points, self.probabilities_for_cdf, self.bounds_pairing
     )
     self.assertArrayAlmostEqual(result[1][:, 0], zero_array)
     self.assertArrayAlmostEqual(result[1][:, -1], one_array)
Example #5
0
 def test_result_cube_has_no_air_temperature_threshold_coordinate(self):
     """
     Test that the plugin returns a cube with coordinates that
     do not include a threshold-type coordinate.
     """
     result = Plugin()._probabilities_to_percentiles(
         self.cube, self.percentiles, self.bounds_pairing
     )
     try:
         threshold_coord = find_threshold_coordinate(result)
     except CoordinateNotFoundError:
         threshold_coord = None
     self.assertIsNone(threshold_coord)
Example #6
0
    def test_transpose_cube_dimensions(self):
        """
        Test that the plugin returns an the expected data, when comparing
        input cubes which have dimensions in a different order.
        """
        # Calculate result for nontransposed cube.
        nontransposed_result = Plugin()._interpolate_percentiles(
            self.cube, self.percentiles, self.bounds_pairing, self.perc_coord
        )

        # Calculate result for transposed cube.
        # Original cube dimensions are [P, Y, X].
        # Transposed cube dimensions are [X, Y, P].
        self.cube.transpose([2, 1, 0])
        transposed_result = Plugin()._interpolate_percentiles(
            self.cube, self.percentiles, self.bounds_pairing, self.perc_coord
        )

        # Result cube will be [P, X, Y]
        # Transpose cube to be [P, Y, X]
        transposed_result.transpose([0, 2, 1])
        self.assertArrayAlmostEqual(nontransposed_result.data, transposed_result.data)
Example #7
0
 def test_endpoints_of_distribution_exceeded(self):
     """
     Test that the plugin raises a ValueError when the constant
     end points of the distribution are exceeded by a threshold value
     used in the forecast.
     """
     probabilities_for_cdf = np.array([[0.05, 0.7, 0.95]])
     threshold_points = np.array([8, 10, 60])
     msg = "The calculated threshold values"
     with self.assertRaisesRegex(ValueError, msg):
         Plugin()._add_bounds_to_thresholds_and_probabilities(
             threshold_points, probabilities_for_cdf, self.bounds_pairing
         )
Example #8
0
    def test_threshold_above_cube_truncnorm(self):
        """Test that the expected probabilities are returned for a cube in
        which they are calculated above the thresholds using a truncated normal
        distribution."""

        expected = (np.ones((3, 3, 3)) * [0.8914245, 0.5942867, 0.2971489]).T
        plugin = Plugin(distribution="truncnorm", shape_parameters=[0, np.inf])
        result = plugin._location_and_scale_parameters_to_probabilities(
            self.location_parameter_values,
            self.scale_parameter_values,
            self.template_cube,
        )
        np.testing.assert_allclose(result.data, expected, rtol=1.0e-4)
 def test_endpoints_of_distribution_exceeded(self):
     """
     Test that the plugin raises a ValueError when the constant
     end points of the distribution are exceeded by a forecast value.
     The end points must be outside the minimum and maximum within the
     forecast values.
     """
     forecast_at_percentiles = np.array([[8, 10, 60]])
     percentiles = np.array([5, 70, 95])
     msg = "Forecast values exist that fall outside the expected extrema"
     with self.assertRaisesRegex(ValueError, msg):
         Plugin()._add_bounds_to_percentiles_and_forecast_at_percentiles(
             percentiles, forecast_at_percentiles, self.bounds_pairing)
    def test_ok_for_diff_percentile_coord(self):
        """Test the plugin succeeds if percentile coord is different"""
        data = np.array([[[[4.75, 5.375, 6.], [6.625, 7.25, 7.875],
                           [8.5, 9.125, 9.75]]],
                         [[[6., 6.625, 7.25], [7.875, 8.5, 9.125],
                           [9.75, 10.375, 11.]]],
                         [[[7.25, 7.875, 8.5], [9.125, 9.75, 10.375],
                           [11., 11.625, 12.25]]]])

        cube = self.percentile_cube_mismatch
        plugin = Plugin()
        result = plugin.process(cube)
        self.assertArrayAlmostEqual(result.data, data)
 def test_many_percentiles(self):
     """
     Test that the plugin returns an iris.cube.Cube if many percentiles
     are requested.
     """
     percentiles = np.linspace(1, 99, num=1000, endpoint=True)
     result = Plugin()._location_and_scale_parameters_to_percentiles(
         self.location_parameter,
         self.scale_parameter,
         self.temperature_cube,
         percentiles,
     )
     self.assertIsInstance(result, Cube)
Example #12
0
 def test_check_data_over_specifying_percentiles(self):
     """
     Test that the plugin raises a suitable error when both a number and set
     or percentiles are specified.
     """
     no_of_percentiles = 3
     percentiles = [25, 50, 75]
     cube = self.current_temperature_forecast_cube
     plugin = Plugin()
     msg = "Cannot specify both no_of_percentiles and percentiles"
     with self.assertRaisesRegex(ValueError, msg):
         plugin.process(cube, no_of_percentiles=no_of_percentiles,
                        percentiles=percentiles)
 def test_percentiles_not_ascending(self):
     """
     Test that the plugin raises a ValueError, if the percentiles are
     not in ascending order.
     """
     forecast_at_percentiles = np.array([[8, 10, 12]])
     percentiles = np.array([100, 0, -100])
     bounds_pairing = (-40, 50)
     plugin = Plugin()
     msg = "The percentiles must be in ascending order"
     with self.assertRaisesRegex(ValueError, msg):
         plugin._add_bounds_to_percentiles_and_forecast_at_percentiles(
             percentiles, forecast_at_percentiles, bounds_pairing)
Example #14
0
 def test_result_cube_has_no_air_temperature_threshold_coordinate(self):
     """
     Test that the plugin returns a cube with coordinates that
     do not include the air_temperature_threshold coordinate.
     """
     cube = self.current_temperature_forecast_cube
     percentiles = [10, 50, 90]
     bounds_pairing = (-40, 50)
     plugin = Plugin()
     result = plugin._probabilities_to_percentiles(
         cube, percentiles, bounds_pairing)
     for coord in result.coords():
         self.assertNotEqual(coord.name(), "threshold")
Example #15
0
 def test_check_data_specifying_percentiles(self):
     """
     Test that the plugin returns an Iris.cube.Cube with the expected
     data values for a specific set of percentiles.
     """
     expected_data = np.array(
         [self.percentile_25, self.percentile_50, self.percentile_75])
     cube = self.current_temperature_forecast_cube
     percentiles = [25, 50, 75]
     plugin = Plugin()
     result = plugin.process(
         cube, percentiles=percentiles)
     self.assertArrayAlmostEqual(result.data, expected_data, decimal=5)
 def test_return_coord_units(self):
     """Test that the plugin returns an Iris.cube.Cube with an appropriate
     percentile coordinate with suitable units.
     """
     cube = self.current_temperature_forecast_cube
     percentiles = [10, 50, 90]
     bounds_pairing = (-40, 50)
     plugin = Plugin()
     result = plugin._probabilities_to_percentiles(
         cube, percentiles, bounds_pairing)
     self.assertIsInstance(result.coord('percentile'), DimCoord)
     self.assertArrayEqual(result.coord('percentile').points, percentiles)
     self.assertEqual(result.coord('percentile').units, unit.Unit("%"))
Example #17
0
    def test_check_data_specifying_single_percentile_not_as_list(self):
        """
        Test that the plugin returns an Iris.cube.Cube with the expected
        data values for a specific percentile passed in as a value.
        """
        data = np.array([[[[21.5, 8.75, 11.], [8.33333333, 8.75, -16.],
                           [-16., -25., -28.]]]])

        cube = self.current_temperature_forecast_cube
        percentiles = 25
        plugin = Plugin()
        result = plugin.process(cube, percentiles=percentiles)
        self.assertArrayAlmostEqual(result.data, data)
Example #18
0
 def test_check_data_masked_input_data_non_nans(self):
     """
     Test that the plugin returns an Iris.cube.Cube with the expected
     data values when the input data is masked and underlying data is not NaNs.
     """
     self.percentile_cube.data[:, 0, 0] = 1000
     self.percentile_cube.data = np.ma.masked_equal(
         self.percentile_cube.data, 1000)
     self.expected[:, 0, 0] = np.nan
     self.expected = np.ma.masked_invalid(self.expected)
     result = Plugin().process(self.percentile_cube)
     self.assertArrayAlmostEqual(result.data.data, self.expected.data)
     self.assertArrayEqual(result.data.mask, self.expected.mask)
    def test_scalar_realisation_percentile(self):
        """
        Test that the plugin returns the expected values when providing a cube
        with a scalar realization or percentile coordinate.
        """

        result = Plugin()._location_and_scale_parameters_to_percentiles(
            self.location_parameter,
            self.scale_parameter,
            self.temperature_cube[0],
            self.percentiles,
        )
        self.assertIsInstance(result, Cube)
 def test_basic(self):
     """
     Test that the plugin returns an iris.cube.Cube, the cube has a
     realization coordinate and is correctly re-ordered to match the source
     realizations.
     """
     expected_data = self.raw_cube.data.copy()
     result = Plugin().process(self.post_processed_percentiles,
                               self.raw_cube)
     self.assertIsInstance(result, Cube)
     self.assertTrue(result.coords("realization"))
     self.assertArrayEqual(result.coord("realization").points, [0, 1, 2])
     self.assertArrayAlmostEqual(result.data, expected_data)
 def test_specify_member_numbers(self):
     """
     Use the ensemble_member_numbers optional argument to specify particular
     values for the ensemble member numbers.
     """
     cube = self.current_temperature_cube
     plen = len(cube.coord("percentile_over_realization").points)
     ensemble_member_numbers = np.arange(plen) + 12
     plugin = Plugin()
     result = plugin.process(cube, ensemble_member_numbers)
     self.assertEqual(len(result.coord("realization").points), plen)
     self.assertArrayAlmostEqual(
         result.coord("realization").points, np.array([12, 13, 14]))
Example #22
0
    def test_check_single_threshold(self):
        """
        Test that the plugin returns an Iris.cube.Cube with the expected
        data values for the percentiles, if a single percentile is used within
        the input set of percentiles.
        """
        expected = np.array(
            [
                [[[4.0, 4.625, 5.25], [5.875, 6.5, 7.125], [7.75, 8.375, 9.0]]
                 ],
                [[
                    [24.44444444, 24.79166667, 25.13888889],
                    [25.48611111, 25.83333333, 26.18055556],
                    [26.52777778, 26.875, 27.22222222],
                ]],
                [[
                    [44.88888889, 44.95833333, 45.02777778],
                    [45.09722222, 45.16666667, 45.23611111],
                    [45.30555556, 45.375, 45.44444444],
                ]],
            ],
            dtype=np.float32,
        )

        data = np.array([8])
        data = data[:, np.newaxis, np.newaxis, np.newaxis]

        current_temperature_forecast_cube = add_forecast_reference_time_and_forecast_period(
            set_up_cube(
                data,
                "air_temperature",
                "1",
                realizations=[0],
                y_dimension_length=1,
                x_dimension_length=1,
            ))
        cube = current_temperature_forecast_cube
        cube.coord("realization").rename(self.perc_coord)
        cube.coord(self.perc_coord).points = np.array([0.2])

        for acube in self.percentile_cube.slices_over(self.perc_coord):
            cube = acube
            break
        percentiles = [10, 50, 90]
        bounds_pairing = (-40, 50)
        plugin = Plugin()
        result = plugin._interpolate_percentiles(cube, percentiles,
                                                 bounds_pairing,
                                                 self.perc_coord)

        self.assertArrayAlmostEqual(result.data, expected)
Example #23
0
 def test_check_data(self):
     """
     Test that the plugin returns an Iris.cube.Cube matching the expected
     data values when a cubes containing location and scale parameters are
     passed in, which are equivalent to the ensemble mean and ensemble
     variance. The resulting data values are the percentiles, which have
     been generated.
     """
     plugin = Plugin()
     result = plugin._location_and_scale_parameters_to_percentiles(
         self.location_parameter, self.scale_parameter,
         self.temperature_cube, self.percentiles)
     self.assertIsInstance(result, Cube)
     np.testing.assert_allclose(result.data, self.data, rtol=1.e-4)
Example #24
0
    def test_unordered_data(self):
        """
        Test that the plugin returns an iris.cube.Cube with the correct data.
        ECC orders the calibrated data based on the ordering of the raw data.
        This could mean that the calibrated data appears out of order.
        ECC does not reorder the calibrated data in a monotonically-increasing
        order.
        """
        raw_data = np.array([[[[5, 5, 5],
                               [7, 5, 5],
                               [5, 5, 5]]],
                             [[[4, 4, 4],
                               [4, 4, 4],
                               [4, 4, 4]]],
                             [[[6, 6, 6],
                               [6, 6, 6],
                               [6, 6, 6]]]])

        calibrated_data = np.array([[[[4, 5, 4],
                                      [4, 5, 4],
                                      [4, 5, 4]]],
                                    [[[5, 6, 5],
                                      [5, 6, 5],
                                      [5, 6, 5]]],
                                    [[[6, 7, 6],
                                      [6, 7, 6],
                                      [6, 7, 6]]]])

        # This reordering does not pay attention to the values within the
        # calibrated data, the rankings created to perform the sorting are
        # taken exclusively from the raw_data.
        result_data = np.array([[[[5, 6, 5],
                                  [6, 6, 5],
                                  [5, 6, 5]]],
                                [[[4, 5, 4],
                                  [4, 5, 4],
                                  [4, 5, 4]]],
                                [[[6, 7, 6],
                                  [5, 7, 6],
                                  [6, 7, 6]]]])

        raw_cube = self.cube.copy()
        raw_cube.data = raw_data
        calibrated_cube = self.cube.copy()
        calibrated_cube.data = calibrated_data

        plugin = Plugin()
        result = plugin.rank_ecc(calibrated_cube, raw_cube)
        result.transpose([1, 0, 2, 3])
        self.assertArrayAlmostEqual(result.data, result_data)
Example #25
0
 def test_check_data_not_specifying_percentiles(self):
     """
     Test that the plugin returns an Iris.cube.Cube with the expected
     data values without specifying the number of percentiles.
     """
     data = np.array(
         [
             [[4.75, 5.375, 6.0], [6.625, 7.25, 7.875], [8.5, 9.125, 9.75]],
             [[6.0, 6.625, 7.25], [7.875, 8.5, 9.125], [9.75, 10.375, 11.0]],
             [[7.25, 7.875, 8.5], [9.125, 9.75, 10.375], [11.0, 11.625, 12.25]],
         ]
     )
     result = Plugin().process(self.percentile_cube)
     self.assertArrayAlmostEqual(result.data, data)
Example #26
0
 def test_bounds_of_percentiles(self):
     """
     Test that the plugin returns the expected results for the
     percentiles, where the percentile values have been padded with 0 and 1.
     """
     cube = self.percentile_cube
     percentiles = cube.coord("percentile_over_realization").points
     forecast_at_percentiles = cube.data.reshape(3, 9)
     bounds_pairing = (-40, 50)
     plugin = Plugin()
     result = plugin._add_bounds_to_percentiles_and_forecast_at_percentiles(
         percentiles, forecast_at_percentiles, bounds_pairing)
     self.assertArrayAlmostEqual(result[0][0], 0)
     self.assertArrayAlmostEqual(result[0][-1], 100)
Example #27
0
    def test_1d_cube_recycling_raw_ensemble_realizations(self):
        """
        Test that the plugin returns the correct cube data for a
        1d input cube, if the number of raw ensemble realizations is fewer
        than the number of percentiles required, and therefore, raw
        ensemble realization recycling is required.

        Case where two raw ensemble realizations are exactly the same,
        after the raw ensemble realizations have been recycled.
        The number of raw ensemble realizations are recycled in order to match
        the number of percentiles.

        After recycling the raw _data will be
        raw_data = np.array([[1],
                             [2],
                             [1]])

        If there's a tie, the re-ordering randomly allocates the ordering
        for the data from the raw ensemble realizations, which is why there are
        two possible options for the resulting post-processed ensemble
        realizations.

        Raw ensemble realizations
        1,  2
        Post-processed percentiles
        1,  2,  3
        After recycling raw ensemble realizations
        1,  2,  1
        As the second ensemble realization(with a data value of 2), is the
        highest value, the highest value from the post-processed percentiles
        will be the second ensemble realization data value within the
        post-processed realizations. The data values of 1 and 2 from the
        post-processed percentiles will then be split between the first
        and third post-processed ensemble realizations.

        """
        raw_data = np.array([1, 2])
        post_processed_percentiles_data = np.array([1, 2, 3])
        expected_first = np.array([1, 3, 2])
        expected_second = np.array([2, 3, 1])

        raw_cube = self.raw_cube[:2, 0, 0]
        raw_cube.data = raw_data
        post_processed_percentiles = self.post_processed_percentiles[:, 0, 0]
        post_processed_percentiles.data = post_processed_percentiles_data

        result = Plugin().process(post_processed_percentiles, raw_cube)
        permutations = [expected_first, expected_second]
        matches = [np.array_equal(aresult, result.data) for aresult in permutations]
        self.assertIn(True, matches)
Example #28
0
 def test_bounds_of_threshold_points(self):
     """
     Test that the plugin returns the expected results for the
     threshold_points, where they've been padded with the values from
     the bounds_pairing.
     """
     cube = self.current_temperature_forecast_cube
     probabilities_for_cdf = cube.data.reshape(3, 9)
     bounds_pairing = (-40, 50)
     plugin = Plugin()
     result = plugin._add_bounds_to_thresholds_and_probabilities(
         self.threshold_points, probabilities_for_cdf, bounds_pairing)
     self.assertArrayAlmostEqual(result[0][0], bounds_pairing[0])
     self.assertArrayAlmostEqual(result[0][-1], bounds_pairing[1])
Example #29
0
    def test_check_data_multiple_timesteps(self):
        """
        Test that the plugin returns an Iris.cube.Cube with the expected
        data values for the percentiles.
        """
        expected = np.array(
            [
                [
                    [[4.5, 5.21428571], [5.92857143, 6.64285714]],
                    [[7.35714286, 8.07142857], [8.78571429, 9.5]],
                ],
                [
                    [[6.5, 7.21428571], [7.92857143, 8.64285714]],
                    [[9.35714286, 10.07142857], [10.78571429, 11.5]],
                ],
                [
                    [[7.5, 8.21428571], [8.92857143, 9.64285714]],
                    [[10.35714286, 11.07142857], [11.78571429, 12.5]],
                ],
            ]
        )

        cube = set_up_percentile_cube(
            np.zeros((3, 2, 2), dtype=np.float32),
            self.percentiles,
            name="air_temperature",
            units="degC",
            time=datetime(2015, 11, 23, 7),
            frt=datetime(2015, 11, 23, 6),
        )
        cube = add_coordinate(
            cube,
            [datetime(2015, 11, 23, 7), datetime(2015, 11, 23, 8)],
            "time",
            is_datetime=True,
            order=[1, 0, 2, 3],
        )

        data = np.tile(np.linspace(5, 10, 8), 3).reshape(3, 2, 2, 2)
        data[0] -= 1
        data[1] += 1
        data[2] += 3
        cube.data = data.astype(np.float32)

        percentiles = [20, 60, 80]
        result = Plugin()._interpolate_percentiles(
            cube, percentiles, self.bounds_pairing, self.perc_coord
        )
        self.assertArrayAlmostEqual(result.data, expected)
Example #30
0
 def test_endpoints_of_distribution_exceeded(self):
     """
     Test that the plugin raises a ValueError when the constant
     end points of the distribution are exceeded by a forecast value.
     The end points must be outside the minimum and maximum within the
     forecast values.
     """
     forecast_at_percentiles = np.array([[8, 10, 60]])
     percentiles = np.array([5, 70, 95])
     bounds_pairing = (-40, 50)
     plugin = Plugin()
     msg = "The end points added to the forecast at percentiles"
     with self.assertRaisesRegexp(ValueError, msg):
         plugin._add_bounds_to_percentiles_and_forecast_at_percentiles(
             percentiles, forecast_at_percentiles, bounds_pairing)