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)
Esempio n. 2
0
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)