def test_unmatched_array_shapes(self): """Test that a useful error is raised when the index_array and data_array have different shapes. This choose function provides only a subset of the full numpy choose features, and one of its limitations is to work only with arrays that match; there is no broadcasting.""" index_array = np.array([[[0, 1], [1, 0]], [[0, 2], [0, 1]]]) msg = ("The choose function only works with an index_array that " "matches the shape of array_set.") with self.assertRaisesRegex(ValueError, msg): choose(index_array, self.small_data)
def test_invalid_array_indices(self): """Test that a useful error is raised when the array that is indexed to provide data does not exist because the index is out of range. More simply, if there are only 3 sub-arays, indices of 3 or more should lead to a sensbile error. Note that the behaviour of this function is equivalent to numpy choose with mode=raise, there is no attempt to wrap or clip invalid index values.""" index_array = np.array([[[0, 1], [1, 0]], [[0, 2], [0, 1]], [[3, 3], [3, 3]]]) msg = "index_array contains an index that is larger than the number" with self.assertRaisesRegex(IndexError, msg): choose(index_array, self.small_data)
def test_numpy_choose_comparison_3D_index_array_test2(self): """Test that a 3D array of indices with a shape matching the data array returns the same result as numpy choose. Here the sub-arrays are rearranged as complete units.""" index_array = np.array([[[1, 1], [1, 1]], [[2, 2], [2, 2]], [[0, 0], [0, 0]]]) choose_result = choose(index_array, self.small_data) npchoose_result = np.choose(index_array, self.small_data) self.assertArrayEqual(choose_result, npchoose_result) self.assertEqual(choose_result.shape, npchoose_result.shape)
def test_numpy_choose_comparison_3D_index_array_test1(self): """Test that a 3D array of indices with a shape matching the data array returns the same result as numpy choose. Here values are taken from a mix of sub-arrays.""" index_array = np.array([[[0, 1], [1, 0]], [[0, 2], [0, 1]], [[1, 1], [2, 0]]]) choose_result = choose(index_array, self.small_data) npchoose_result = np.choose(index_array, self.small_data) self.assertArrayEqual(choose_result, npchoose_result) self.assertEqual(choose_result.shape, npchoose_result.shape)
def test_3D_index_array_utilising_indices_beyond_32(self): """An explicit test that this function is handling indices beyond 32. The numpy choose function does not support a data array with a leading dimension of longer than 32. Note that due to indexing from 0, an index of 32 here is for array 33, beyond numpy's limit.""" index_array = np.ones(self.data.shape).astype(int) expected = np.array([self.data[1]] * 33) result = choose(index_array, self.data) self.assertArrayEqual(result, expected) self.assertEqual(result.shape, expected.shape)
def test_3D_index_array_test2(self): """Test that a 3D array of indices with a shape matching the data array returns the expected values. Here the sub-arrays are rearranged as complete units.""" index_array = np.array([[[1, 1], [1, 1]], [[2, 2], [2, 2]], [[0, 0], [0, 0]]]) expected = np.array( [self.small_data[1], self.small_data[2], self.small_data[0]]) result = choose(index_array, self.small_data) self.assertArrayEqual(result, expected) self.assertEqual(result.shape, expected.shape)
def test_3D_index_array_test1(self): """Test that a 3D array of indices with a shape matching the data array returns the expected values. Here values are taken from a mix of sub-arrays. This example can be seen graphically in the documentation for the choose function.""" index_array = np.array([[[0, 1], [1, 0]], [[0, 2], [0, 1]], [[1, 1], [2, 0]]]) expected = np.array([[[1, 6], [7, 4]], [[1, 10], [3, 8]], [[5, 6], [11, 4]]]) result = choose(index_array, self.small_data) self.assertArrayEqual(result, expected) self.assertEqual(result.shape, expected.shape)
def rank_ecc(post_processed_forecast_percentiles, raw_forecast_realizations, random_ordering=False, random_seed=None): """ Function to apply Ensemble Copula Coupling. This ranks the post-processed forecast realizations based on a ranking determined from the raw forecast realizations. Args: post_processed_forecast_percentiles (cube): Cube for post-processed percentiles. The percentiles are assumed to be in ascending order. raw_forecast_realizations (cube): Cube containing the raw (not post-processed) forecasts. The probabilistic dimension is assumed to be the zeroth dimension. random_ordering (Logical): If random_ordering is True, the post-processed forecasts are reordered randomly, rather than using the ordering of the raw ensemble. random_seed (Integer or None): If random_seed is an integer, the integer value is used for the random seed. If random_seed is None, no random seed is set, so the random values generated are not reproducible. Returns: iris.cube.Cube: Cube for post-processed realizations where at a particular grid point, the ranking of the values within the ensemble matches the ranking from the raw ensemble. """ results = iris.cube.CubeList([]) for rawfc, calfc in zip( raw_forecast_realizations.slices_over("time"), post_processed_forecast_percentiles.slices_over("time")): if random_seed is not None: random_seed = int(random_seed) random_seed = np.random.RandomState(random_seed) random_data = random_seed.rand(*rawfc.data.shape) if random_ordering: # Returns the indices that would sort the array. # As these indices are from a random dataset, only an argsort # is used. ranking = np.argsort(random_data, axis=0) else: # Lexsort returns the indices sorted firstly by the # primary key, the raw forecast data (unless random_ordering # is enabled), and secondly by the secondary key, an array of # random data, in order to split tied values randomly. sorting_index = np.lexsort((random_data, rawfc.data), axis=0) # Returns the indices that would sort the array. ranking = np.argsort(sorting_index, axis=0) # Index the post-processed forecast data using the ranking array. # The following uses a custom choose function that reproduces the # required elements of the np.choose method without the limitation # of having < 32 arrays or a leading dimension < 32 in the # input data array. This function allows indexing of a 3d array # using a 3d array. calfc.data = choose(ranking, calfc.data) results.append(calfc) # Ensure we haven't lost any dimensional coordinates with only one # value in. results = results.merge_cube() results = check_cube_coordinates(post_processed_forecast_percentiles, results) return results