class Test_process(IrisTest): """Test process method""" def setUp(self): """ Set up a basic cube and linear weights cube for the process method. Input cube has 2 thresholds on and 3 forecast_reference_times """ thresholds = [10, 20] data = np.ones((2, 2, 3), dtype=np.float32) cycle1 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 0, 0), ) cycle2 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 1, 0), ) cycle3 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 2, 0), ) self.cube_to_collapse = CubeList([cycle1, cycle2, cycle3]).merge_cube() self.cube_to_collapse = squeeze(self.cube_to_collapse) self.cube_to_collapse.rename("weights") # This input array has 3 forecast reference times and 2 thresholds. # The two thresholds have the same weights. self.cube_to_collapse.data = np.array( [[[[1, 0, 1], [1, 1, 1]], [[1, 0, 1], [1, 1, 1]]], [[[0, 0, 1], [0, 1, 1]], [[0, 0, 1], [0, 1, 1]]], [[[1, 1, 1], [1, 1, 1]], [[1, 1, 1], [1, 1, 1]]]], dtype=np.float32) self.cube_to_collapse.data = np.ma.masked_equal( self.cube_to_collapse.data, 0) # Create a one_dimensional weights cube by slicing the larger # weights cube. # The resulting cube only has a forecast_reference_time coordinate. self.one_dimensional_weights_cube = self.cube_to_collapse[:, 0, 0, 0] self.one_dimensional_weights_cube.remove_coord( "projection_x_coordinate") self.one_dimensional_weights_cube.remove_coord( "projection_y_coordinate") self.one_dimensional_weights_cube.remove_coord( find_threshold_coordinate(self.one_dimensional_weights_cube)) self.one_dimensional_weights_cube.data = np.array([0.2, 0.5, 0.3], dtype=np.float32) self.plugin = SpatiallyVaryingWeightsFromMask(fuzzy_length=4) @ManageWarnings(record=True) def test_none_masked(self, warning_list=None): """Test when we have no masked data in the input cube.""" self.cube_to_collapse.data = np.ones(self.cube_to_collapse.data.shape) self.cube_to_collapse.data = np.ma.masked_equal( self.cube_to_collapse.data, 0) expected_data = np.array([[[0.2, 0.2, 0.2], [0.2, 0.2, 0.2]], [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5]], [[0.3, 0.3, 0.3], [0.3, 0.3, 0.3]]], dtype=np.float32) message = ("Input cube to SpatiallyVaryingWeightsFromMask " "must be masked") result = self.plugin.process(self.cube_to_collapse, self.one_dimensional_weights_cube, "forecast_reference_time") self.assertTrue(any(message in str(item) for item in warning_list)) self.assertArrayEqual(result.data, expected_data) self.assertEqual(result.dtype, np.float32) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_all_masked(self): """Test when we have all masked data in the input cube.""" self.cube_to_collapse.data = np.ones(self.cube_to_collapse.data.shape) self.cube_to_collapse.data = np.ma.masked_equal( self.cube_to_collapse.data, 1) result = self.plugin.process(self.cube_to_collapse, self.one_dimensional_weights_cube, "forecast_reference_time") expected_data = np.zeros((3, 2, 3)) self.assertArrayAlmostEqual(expected_data, result.data) self.assertTrue(result.metadata, self.cube_to_collapse.data) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_no_fuzziness_no_one_dimensional_weights(self): """Test a simple case where we have no fuzziness in the spatial weights and no adjustment from the one_dimensional weights.""" plugin = SpatiallyVaryingWeightsFromMask(fuzzy_length=1) self.one_dimensional_weights_cube.data = np.ones((3)) expected_result = np.array( [[[0.5, 0., 0.33333333], [0.5, 0.33333333, 0.33333333]], [[0., 0., 0.33333333], [0., 0.33333333, 0.33333333]], [[0.5, 1., 0.33333333], [0.5, 0.33333333, 0.33333333]]], dtype=np.float32) result = plugin.process(self.cube_to_collapse, self.one_dimensional_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_no_fuzziness_no_one_dimensional_weights_transpose(self): """Test a simple case where we have no fuzziness in the spatial weights and no adjustment from the one_dimensional weights and transpose the input cube.""" plugin = SpatiallyVaryingWeightsFromMask(fuzzy_length=1) self.one_dimensional_weights_cube.data = np.ones((3)) expected_result = np.array( [[[0.5, 0., 0.33333333], [0.5, 0.33333333, 0.33333333]], [[0., 0., 0.33333333], [0., 0.33333333, 0.33333333]], [[0.5, 1., 0.33333333], [0.5, 0.33333333, 0.33333333]]], dtype=np.float32) self.cube_to_collapse.transpose([2, 0, 1, 3]) result = plugin.process(self.cube_to_collapse, self.one_dimensional_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_no_fuzziness_with_one_dimensional_weights(self): """Test a simple case where we have no fuzziness in the spatial weights and an adjustment from the one_dimensional weights.""" plugin = SpatiallyVaryingWeightsFromMask(fuzzy_length=1) expected_result = np.array([[[0.4, 0., 0.2], [0.4, 0.2, 0.2]], [[0., 0., 0.5], [0., 0.5, 0.5]], [[0.6, 1., 0.3], [0.6, 0.3, 0.3]]], dtype=np.float32) result = plugin.process(self.cube_to_collapse, self.one_dimensional_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_fuzziness_no_one_dimensional_weights(self): """Test a simple case where we have some fuzziness in the spatial weights and no adjustment from the one_dimensional weights.""" plugin = SpatiallyVaryingWeightsFromMask(fuzzy_length=2) self.one_dimensional_weights_cube.data = np.ones((3)) expected_result = np.array( [[[0.33333334, 0., 0.25], [0.41421354, 0.25, 0.2928932]], [[0., 0., 0.25], [0., 0.25, 0.2928932]], [[0.6666667, 1., 0.5], [0.5857864, 0.5, 0.41421354]]], dtype=np.float32) result = plugin.process(self.cube_to_collapse, self.one_dimensional_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_fuzziness_with_one_dimensional_weights(self): """Test a simple case where we have some fuzziness in the spatial sweights and with adjustment from the one_dimensional weights.""" plugin = SpatiallyVaryingWeightsFromMask(fuzzy_length=2) expected_result = np.array( [[[0.25, 0., 0.15384616], [0.32037723, 0.15384616, 0.17789416]], [[0., 0., 0.3846154], [0., 0.3846154, 0.44473538]], [[0.75, 1., 0.4615385], [0.6796227, 0.4615385, 0.3773705]]], dtype=np.float32) result = plugin.process(self.cube_to_collapse, self.one_dimensional_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata)
class Test_process(IrisTest): """Test process method""" def setUp(self): """ Set up a basic cube and linear weights cube for the process method. Input cube has 2 thresholds and 3 forecast_reference_times """ thresholds = [10, 20] data = np.ones((2, 2, 3), dtype=np.float32) cycle1 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 0, 0), ) cycle2 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 1, 0), ) cycle3 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 2, 0), ) self.cube_to_collapse = CubeList([cycle1, cycle2, cycle3]).merge_cube() self.cube_to_collapse = squeeze(self.cube_to_collapse) self.cube_to_collapse.rename("weights") # This input array has 3 forecast reference times and 2 thresholds. # The two thresholds have the same weights. self.cube_to_collapse.data = np.array( [ [[[1, 0, 1], [1, 1, 1]], [[1, 0, 1], [1, 1, 1]]], [[[0, 0, 1], [0, 1, 1]], [[0, 0, 1], [0, 1, 1]]], [[[1, 1, 1], [1, 1, 1]], [[1, 1, 1], [1, 1, 1]]], ], dtype=np.float32, ) self.cube_to_collapse.data = np.ma.masked_equal( self.cube_to_collapse.data, 0) # Create a one_dimensional weights cube by slicing the larger # weights cube. # The resulting cube only has a forecast_reference_time coordinate. self.one_dimensional_weights_cube = self.cube_to_collapse[:, 0, 0, 0] self.one_dimensional_weights_cube.remove_coord( "projection_x_coordinate") self.one_dimensional_weights_cube.remove_coord( "projection_y_coordinate") self.one_dimensional_weights_cube.remove_coord( find_threshold_coordinate(self.one_dimensional_weights_cube)) self.one_dimensional_weights_cube.data = np.array([0.2, 0.5, 0.3], dtype=np.float32) self.plugin = SpatiallyVaryingWeightsFromMask( "forecast_reference_time", fuzzy_length=2) self.plugin_no_fuzzy = SpatiallyVaryingWeightsFromMask( "forecast_reference_time", fuzzy_length=1) @ManageWarnings(record=True) def test_none_masked(self, warning_list=None): """Test when we have no masked data in the input cube.""" self.cube_to_collapse.data = np.ones(self.cube_to_collapse.data.shape) self.cube_to_collapse.data = np.ma.masked_equal( self.cube_to_collapse.data, 0) expected_data = np.array( [ [[0.2, 0.2, 0.2], [0.2, 0.2, 0.2]], [[0.5, 0.5, 0.5], [0.5, 0.5, 0.5]], [[0.3, 0.3, 0.3], [0.3, 0.3, 0.3]], ], dtype=np.float32, ) message = "Expected masked input" result = self.plugin.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) self.assertTrue(any(message in str(item) for item in warning_list)) self.assertArrayEqual(result.data, expected_data) self.assertEqual(result.dtype, np.float32) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_all_masked(self): """Test when we have all masked data in the input cube.""" self.cube_to_collapse.data = np.ones(self.cube_to_collapse.data.shape) self.cube_to_collapse.data = np.ma.masked_equal( self.cube_to_collapse.data, 1) result = self.plugin.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) expected_data = np.zeros((3, 2, 3)) self.assertArrayAlmostEqual(expected_data, result.data) self.assertTrue(result.metadata, self.cube_to_collapse.data) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_no_fuzziness_no_one_dimensional_weights(self): """Test a simple case where we have no fuzziness in the spatial weights and no adjustment from the one_dimensional weights.""" self.one_dimensional_weights_cube.data = np.ones((3)) expected_result = np.array( [ [[0.5, 0.0, 0.333333], [0.5, 0.333333, 0.333333]], [[0.0, 0.0, 0.333333], [0.0, 0.333333, 0.333333]], [[0.5, 1.0, 0.333333], [0.5, 0.333333, 0.333333]], ], dtype=np.float32, ) result = self.plugin_no_fuzzy.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_no_fuzziness_no_one_dimensional_weights_transpose(self): """Test a simple case where we have no fuzziness in the spatial weights and no adjustment from the one_dimensional weights and transpose the input cube.""" self.one_dimensional_weights_cube.data = np.ones((3)) expected_result = np.array( [ [[0.5, 0.0, 0.333333], [0.5, 0.333333, 0.333333]], [[0.0, 0.0, 0.333333], [0.0, 0.333333, 0.333333]], [[0.5, 1.0, 0.333333], [0.5, 0.333333, 0.333333]], ], dtype=np.float32, ) self.cube_to_collapse.transpose([2, 0, 1, 3]) result = self.plugin_no_fuzzy.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_no_fuzziness_with_one_dimensional_weights(self): """Test a simple case where we have no fuzziness in the spatial weights and an adjustment from the one_dimensional weights.""" expected_result = np.array( [ [[0.4, 0.0, 0.2], [0.4, 0.2, 0.2]], [[0.0, 0.0, 0.5], [0.0, 0.5, 0.5]], [[0.6, 1.0, 0.3], [0.6, 0.3, 0.3]], ], dtype=np.float32, ) result = self.plugin_no_fuzzy.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_fuzziness_no_one_dimensional_weights(self): """Test a simple case where we have some fuzziness in the spatial weights and no adjustment from the one_dimensional weights.""" self.one_dimensional_weights_cube.data = np.ones((3)) expected_result = np.array( [ [[0.25, 0.0, 0.166667], [0.353553, 0.166667, 0.235702]], [[0.00, 0.0, 0.166667], [0.000000, 0.166667, 0.235702]], [[0.75, 1.0, 0.666667], [0.646447, 0.666667, 0.528595]], ], dtype=np.float32, ) result = self.plugin.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_fuzziness_with_one_dimensional_weights(self): """Test a simple case where we have some fuzziness in the spatial weights and with adjustment from the one_dimensional weights.""" expected_result = np.array( [ [[0.2, 0.0, 0.10], [0.282843, 0.10, 0.141421]], [[0.0, 0.0, 0.25], [0.000000, 0.25, 0.353553]], [[0.8, 1.0, 0.65], [0.717157, 0.65, 0.505025]], ], dtype=np.float32, ) result = self.plugin.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.cube_to_collapse.metadata) def test_fuzziness_with_unequal_weightings(self): """Simulate the case of two models and a nowcast at short lead times: two unmasked slices with low weights, and one masked slice with high weights""" self.cube_to_collapse.data[0].mask = np.full_like( self.cube_to_collapse.data[0], False) self.one_dimensional_weights_cube.data = np.array([0.025, 1.0, 0.075], dtype=np.float32) expected_data = np.array( [ [[0.25, 0.25, 0.136364], [0.25, 0.136364, 0.0892939]], [[0.0, 0.0, 0.45454544], [0.0, 0.454545, 0.642824]], [[0.75, 0.75, 0.409091], [0.75, 0.409091, 0.267882]], ], dtype=np.float32, ) result = self.plugin.process( self.cube_to_collapse, self.one_dimensional_weights_cube, ) self.assertArrayAlmostEqual(result.data, expected_data)
class Test_normalised_masked_weights(IrisTest): """Test normalised_masked_weights method""" def setUp(self): """Set up a cube with 2 thresholds to test normalisation. We are testing normalising along the leading dimension in this cube.""" thresholds = [10, 20] data = np.ones((2, 2, 3), dtype=np.float32) cycle1 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 0, 0), ) cycle2 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 1, 0), ) cycle3 = set_up_probability_cube( data, thresholds, spatial_grid="equalarea", time=datetime(2017, 11, 10, 4, 0), frt=datetime(2017, 11, 10, 2, 0), ) self.spatial_weights_cube = CubeList([cycle1, cycle2, cycle3]).merge_cube() self.spatial_weights_cube = squeeze(self.spatial_weights_cube) self.spatial_weights_cube.rename("weights") # This input array has 3 forecast reference times and 2 thresholds. # The two thresholds have the same weights. self.spatial_weights_cube.data = np.array( [[[[0.2, 0, 0.2], [0.2, 0, 0.2]], [[0.2, 0, 0.2], [0.2, 0, 0.2]]], [[[0, 0, 0.5], [0, 0, 0.5]], [[0, 0, 0.5], [0, 0, 0.5]]], [[[0.3, 0.3, 0.3], [0.3, 0.3, 0.3]], [[0.3, 0.3, 0.3], [0.3, 0.3, 0.3]]]], dtype=np.float32) self.plugin = SpatiallyVaryingWeightsFromMask() @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_basic(self): """Test a basic example normalising along forecast_reference_time""" expected_result = np.array( [[[[0.4, 0, 0.2], [0.4, 0, 0.2]], [[0.4, 0, 0.2], [0.4, 0, 0.2]]], [[[0, 0, 0.5], [0, 0, 0.5]], [[0, 0, 0.5], [0, 0, 0.5]]], [[[0.6, 1.0, 0.3], [0.6, 1.0, 0.3]], [[0.6, 1.0, 0.3], [0.6, 1.0, 0.3]]]], dtype=np.float32) result = self.plugin.normalised_masked_weights( self.spatial_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.spatial_weights_cube.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_less_input_dims(self): """Test a smaller input cube""" expected_result = np.array( [[[0.4, 0, 0.2], [0.4, 0, 0.2]], [[0, 0, 0.5], [0, 0, 0.5]], [[0.6, 1.0, 0.3], [0.6, 1.0, 0.3]]], dtype=np.float32) result = self.plugin.normalised_masked_weights( self.spatial_weights_cube[:, 0, :, :], "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result[:, 0, :, :].metadata, self.spatial_weights_cube.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_transpose_cube(self): """Test the function still works when we transpose the input cube. Same as test_basic except for transpose.""" expected_result = np.array( [[[[0.4, 0, 0.2], [0.4, 0, 0.2]], [[0.4, 0, 0.2], [0.4, 0, 0.2]]], [[[0, 0, 0.5], [0, 0, 0.5]], [[0, 0, 0.5], [0, 0, 0.5]]], [[[0.6, 1.0, 0.3], [0.6, 1.0, 0.3]], [[0.6, 1.0, 0.3], [0.6, 1.0, 0.3]]]], dtype=np.float32) # The function always puts the blend_coord as a leading dimension. # The process method will ensure the order of the output dimensions # matches those in the input. expected_result = np.transpose(expected_result, axes=[0, 3, 2, 1]) self.spatial_weights_cube.transpose(new_order=[3, 2, 0, 1]) result = self.plugin.normalised_masked_weights( self.spatial_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.spatial_weights_cube.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_already_normalised(self): """Test nothing happens if the input data is already normalised.""" self.spatial_weights_cube.data = np.array( [[[[0.4, 0, 0.2], [0.4, 0, 0.2]], [[0.4, 0, 0.2], [0.4, 0, 0.2]]], [[[0, 0, 0.5], [0, 0, 0.5]], [[0, 0, 0.5], [0, 0, 0.5]]], [[[0.6, 1.0, 0.3], [0.6, 1.0, 0.3]], [[0.6, 1.0, 0.3], [0.6, 1.0, 0.3]]]], dtype=np.float32) result = self.plugin.normalised_masked_weights( self.spatial_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, self.spatial_weights_cube.data) self.assertEqual(result.metadata, self.spatial_weights_cube.metadata) @ManageWarnings( ignored_messages=["Collapsing a non-contiguous coordinate."]) def test_weights_sum_to_zero(self): """Test all x-y slices have zero weight in the same index. This case corresponds to the case when all the input fields are all masked in the same place.""" self.spatial_weights_cube.data[:, :, :, 0] = 0 expected_result = np.array( [[[[0, 0, 0.2], [0, 0, 0.2]], [[0, 0, 0.2], [0, 0, 0.2]]], [[[0, 0, 0.5], [0, 0, 0.5]], [[0, 0, 0.5], [0, 0, 0.5]]], [[[0, 1.0, 0.3], [0, 1.0, 0.3]], [[0, 1.0, 0.3], [0, 1.0, 0.3]]]], dtype=np.float32) result = self.plugin.normalised_masked_weights( self.spatial_weights_cube, "forecast_reference_time") self.assertArrayAlmostEqual(result.data, expected_result) self.assertEqual(result.metadata, self.spatial_weights_cube.metadata)