class Test__modify_first_guess(IrisTest): """Test the _modify_first_guess method.""" def setUp(self): """Create test cubes and plugin instance. The cube coordinates look like this: Dimension coordinates: projection_y_coordinate: 3; projection_x_coordinate: 3; Scalar coordinates: time: 2015-11-23 07:00:00 forecast_reference_time: 2015-11-23 07:00:00 forecast_period: 0 seconds self.cube: Metadata describes the nowcast lightning fields to be calculated. forecast_period: 0 seconds (simulates nowcast data) self.fg_cube: Has 4 hour forecast period, to test impact at lr2 self.ltng_cube: forecast_period: 0 seconds (simulates nowcast data) self.precip_cube: Has extra coordinate of length(3) "threshold" containing points [0.5, 7., 35.] mm h-1. self.vii_cube: Has extra coordinate of length(3) "threshold" containing points [0.5, 1., 2.] kg m-2. """ ( self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, self.vii_cube, ) = set_up_lightning_test_cubes() self.plugin = Plugin() def test_basic(self): """Test that the method returns the expected cube type""" result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, self.vii_cube) self.assertIsInstance(result, Cube) def test_input_with_vii(self): """Test that the method does not modify the input cubes.""" cube_a = self.cube.copy() cube_b = self.fg_cube.copy() cube_c = self.ltng_cube.copy() cube_d = self.precip_cube.copy() cube_e = self.vii_cube.copy() self.plugin._modify_first_guess(cube_a, cube_b, cube_c, cube_d, cube_e) self.assertArrayAlmostEqual(cube_a.data, self.cube.data) self.assertArrayAlmostEqual(cube_b.data, self.fg_cube.data) self.assertArrayAlmostEqual(cube_c.data, self.ltng_cube.data) self.assertArrayAlmostEqual(cube_d.data, self.precip_cube.data) self.assertArrayAlmostEqual(cube_e.data, self.vii_cube.data) def test_missing_lightning(self): """Test that the method raises an error if the lightning cube doesn't match the meta-data cube time coordinate.""" self.ltng_cube.coord("time").points = [1.0] msg = "No matching lightning cube for" with self.assertRaisesRegex(ConstraintMismatchError, msg): self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) def test_missing_first_guess(self): """Test that the method raises an error if the first-guess cube doesn't match the meta-data cube time coordinate.""" self.fg_cube.coord("time").points = [1.0] msg = "is not available within the input cube within the " "allowed difference" with self.assertRaisesRegex(ValueError, msg): self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) def test_cube_has_no_time_coord(self): """Test that the method raises an error if the meta-data cube has no time coordinate.""" self.cube.remove_coord("time") msg = "Expected to find exactly 1 time coordinate, but found none." with self.assertRaisesRegex(CoordinateNotFoundError, msg): self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) def test_precip_zero(self): """Test that apply_precip is being called""" # Set lightning data to "no-data" so it has a Null impact self.ltng_cube.data = np.full_like(self.ltng_cube.data, -1.0) # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data contains all ones except: expected.data[1, 1] = 0.0067 result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_vii_large(self): """Test that ApplyIce is being called""" # Set lightning data to zero so it has a Null impact self.vii_cube.data[:, 1, 1] = 1.0 self.ltng_cube.data[1, 1] = -1.0 self.fg_cube.data[1, 1] = 0.0 # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data contains all ones except: expected.data[1, 1] = 0.9 result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, self.vii_cube) self.assertArrayAlmostEqual(result.data, expected.data) def test_null(self): """Test that large precip probs and -1 lrates have no impact""" # Set prob(precip) data for lowest threshold to to 0.1, the highest # value that has no impact. self.precip_cube.data[0, 1, 1] = 0.1 # Set lightning data to -1 so it has a Null impact self.ltng_cube.data = np.full_like(self.ltng_cube.data, -1.0) # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data should be an unchanged copy of fg_cube. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_large(self): """Test that large lightning rates increase zero lightning risk""" expected = self.fg_cube.copy() # expected.data contains all ones # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 1, 1] = 1.0 # Set first-guess data zero point that will be increased self.fg_cube.data[1, 1] = 0.0 # No halo - we're only testing this method. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_large_shortfc(self): """Test that nearly-large lightning rates increases zero lightning risk when forecast_period is non-zero""" expected = self.fg_cube.copy() # expected.data contains all ones # Set precip data to 1. so it has a Null impact # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 1, 1] = 1.0 # Test the impact of the lightning-rate function. # A highish lightning value at one-hour lead time isn't high enough to # get to the high lightning category. self.ltng_cube.data[1, 1] = 0.8 self.cube.coord("forecast_period").points = [3600.0] # seconds # Set first-guess data zero point that will be increased self.fg_cube.data[1, 1] = 0.0 # This time, lightning probability increases only to 0.25, not 1. expected.data[1, 1] = 0.25 # No halo - we're only testing this method. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_large_null(self): """Test that large lightning rates do not increase high lightning risk""" expected = self.fg_cube.copy() # expected.data contains all ones # Set precip data to 1. so it has a Null impact # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 1, 1] = 1.0 # Set first-guess data zero point that will be increased self.fg_cube.data[1, 1] = 1.0 # No halo - we're only testing this method. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_small(self): """Test that lightning nearby (encoded as lightning rate zero) increases lightning risk at point""" # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 1, 1] = 1.0 # Set lightning data to zero to represent the data halo self.ltng_cube.data[1, 1] = 0.0 # Set first-guess data zero point that will be increased self.fg_cube.data[1, 1] = 0.0 # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data contains all ones except: expected.data[1, 1] = 0.25 result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data)
class Test__modify_first_guess(IrisTest): """Test the _modify_first_guess method.""" def setUp(self): """Create cubes with a single zero prob(precip) point. The cubes look like this: precipitation_amount / (kg m^-2) Dimension coordinates: time: 1; projection_y_coordinate: 3; projection_x_coordinate: 3; Auxiliary coordinates: forecast_period (on time coord): 4.0 hours (simulates UM data) Scalar coordinates: forecast_reference_time: 2015-11-23 03:00:00 Data: self.cube: Describes the nowcast fields to be calculated. forecast_period (on time coord): 0.0 hours (simulates nowcast data) All points contain float(1.) except the zero point [0, 1, 1] which is float(0.) self.fg_cube: All points contain float(1.) self.ltng_cube: forecast_period (on time coord): 0.0 hours (simulates nowcast data) All points contain float(1.) self.precip_cube: With extra coordinate of length(3) "threshold" containing points [0.5, 7., 35.] mm hr-1. All points contain float(1.) except the zero point [0, 0, 1, 1] which is float(0.) and [1:, 0, ...] which are float(0.) self.vii_cube: With extra coordinate of length(3) "threshold" containing points [0.5, 1., 2.] kg m^-2. forecast_period (on time coord): 0.0 hours (simulates nowcast data) Time and forecast_period dimensions "sqeezed" to be Scalar coords. All points contain float(0.) """ self.cube = add_forecast_reference_time_and_forecast_period( set_up_cube_with_no_realizations(zero_point_indices=((0, 1, 1), ), num_grid_points=3), fp_point=0.0) self.fg_cube = add_forecast_reference_time_and_forecast_period( set_up_cube_with_no_realizations(zero_point_indices=[], num_grid_points=3)) self.ltng_cube = add_forecast_reference_time_and_forecast_period( set_up_cube_with_no_realizations(zero_point_indices=[], num_grid_points=3), fp_point=0.0) self.precip_cube = (add_forecast_reference_time_and_forecast_period( set_up_cube(num_realization_points=3, zero_point_indices=((0, 1, 1), ), num_grid_points=3), fp_point=0.0)) threshold_coord = self.precip_cube.coord('realization') threshold_coord.points = [0.5, 7.0, 35.0] threshold_coord.rename('threshold') threshold_coord.units = cf_units.Unit('mm hr-1') self.precip_cube.data[1:, 0, ...] = 0. # iris.util.queeze is applied here to demote the singular coord "time" # to a scalar coord. self.vii_cube = squeeze( add_forecast_reference_time_and_forecast_period(set_up_cube( num_realization_points=3, zero_point_indices=[], num_grid_points=3), fp_point=0.0)) threshold_coord = self.vii_cube.coord('realization') threshold_coord.points = [0.5, 1.0, 2.0] threshold_coord.rename('threshold') threshold_coord.units = cf_units.Unit('kg m^-2') self.vii_cube.data = np.zeros_like(self.vii_cube.data) self.plugin = Plugin() def test_basic(self): """Test that the method returns the expected cube type""" result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, self.vii_cube) self.assertIsInstance(result, Cube) def test_input_with_vii(self): """Test that the method does not modify the input cubes.""" cube_a = self.cube.copy() cube_b = self.fg_cube.copy() cube_c = self.ltng_cube.copy() cube_d = self.precip_cube.copy() cube_e = self.vii_cube.copy() self.plugin._modify_first_guess(cube_a, cube_b, cube_c, cube_d, cube_e) self.assertArrayAlmostEqual(cube_a.data, self.cube.data) self.assertArrayAlmostEqual(cube_b.data, self.fg_cube.data) self.assertArrayAlmostEqual(cube_c.data, self.ltng_cube.data) self.assertArrayAlmostEqual(cube_d.data, self.precip_cube.data) self.assertArrayAlmostEqual(cube_e.data, self.vii_cube.data) def test_missing_lightning(self): """Test that the method raises an error if the lightning cube doesn't match the meta-data cube time coordinate.""" self.ltng_cube.coord('time').points = [1.0] msg = ("No matching lightning cube for") with self.assertRaisesRegex(ConstraintMismatchError, msg): self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) def test_missing_first_guess(self): """Test that the method raises an error if the first-guess cube doesn't match the meta-data cube time coordinate.""" self.fg_cube.coord('time').points = [1.0] msg = ("is not available within the input cube within the " "allowed difference") with self.assertRaisesRegex(ValueError, msg): self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) def test_cube_has_no_time_coord(self): """Test that the method raises an error if the meta-data cube has no time coordinate.""" self.cube.remove_coord('time') msg = ("Expected to find exactly 1 time coordinate, but found none.") with self.assertRaisesRegex(CoordinateNotFoundError, msg): self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) def test_precip_zero(self): """Test that apply_precip is being called""" # Set lightning data to "no-data" so it has a Null impact self.ltng_cube.data = np.full_like(self.ltng_cube.data, -1.) # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data contains all ones except: expected.data[0, 1, 1] = 0.0067 result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_vii_large(self): """Test that ApplyIce is being called""" # Set lightning data to zero so it has a Null impact self.vii_cube.data[:, 1, 1] = 1. self.ltng_cube.data[0, 1, 1] = -1. self.fg_cube.data[0, 1, 1] = 0. # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data contains all ones except: expected.data[0, 1, 1] = 0.9 result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, self.vii_cube) self.assertArrayAlmostEqual(result.data, expected.data) def test_null(self): """Test that large precip probs and -1 lrates have no impact""" # Set prob(precip) data for lowest threshold to to 0.1, the highest # value that has no impact. self.precip_cube.data[0, 0, 1, 1] = 0.1 # Set lightning data to -1 so it has a Null impact self.ltng_cube.data = np.full_like(self.ltng_cube.data, -1.) # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data should be an unchanged copy of fg_cube. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_large(self): """Test that large lightning rates increase zero lightning risk""" expected = self.fg_cube.copy() # expected.data contains all ones # Set precip data to 1. so it has a Null impact # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 0, 1, 1] = 1. # Set first-guess data zero point that will be increased self.fg_cube.data[0, 1, 1] = 0. # No halo - we're only testing this method. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_large_shortfc(self): """Test that nearly-large lightning rates increases zero lightning risk when forecast_period is non-zero""" expected = self.fg_cube.copy() # expected.data contains all ones # Set precip data to 1. so it has a Null impact # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 0, 1, 1] = 1. # Test the impact of the lightning-rate function. # A highish lightning value at one-hour lead time isn't high enough to # get to the high lightning category. self.ltng_cube.data[0, 1, 1] = 0.8 self.cube.coord('forecast_period').points = [1.] # hours # Set first-guess data zero point that will be increased self.fg_cube.data[0, 1, 1] = 0. # This time, lightning probability increases only to 0.25, not 1. expected.data[0, 1, 1] = 0.25 # No halo - we're only testing this method. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_large_null(self): """Test that large lightning rates do not increase high lightning risk""" expected = self.fg_cube.copy() # expected.data contains all ones # Set precip data to 1. so it has a Null impact # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 0, 1, 1] = 1. # Set first-guess data zero point that will be increased self.fg_cube.data[0, 1, 1] = 1. # No halo - we're only testing this method. result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data) def test_lrate_small(self): """Test that lightning nearby (encoded as lightning rate zero) increases lightning risk at point""" # Set prob(precip) data for lowest threshold to to 1., so it has a Null # impact when lightning is present. self.precip_cube.data[0, 0, 1, 1] = 1. # Set lightning data to zero to represent the data halo self.ltng_cube.data[0, 1, 1] = 0. # Set first-guess data zero point that will be increased self.fg_cube.data[0, 1, 1] = 0. # No halo - we're only testing this method. expected = self.fg_cube.copy() # expected.data contains all ones except: expected.data[0, 1, 1] = 0.25 result = self.plugin._modify_first_guess(self.cube, self.fg_cube, self.ltng_cube, self.precip_cube, None) self.assertArrayAlmostEqual(result.data, expected.data)