예제 #1
0
class Test_process(IrisTest):
    """Test dimensioned cube data is correctly advected"""
    def setUp(self):
        """Set up plugin instance and a cube to advect"""
        vel_x = set_up_xy_velocity_cube("advection_velocity_x")
        vel_y = vel_x.copy(data=2. * np.ones(shape=(4, 3)))
        vel_y.rename("advection_velocity_y")
        self.plugin = AdvectField(vel_x, vel_y)
        data = np.array([[2., 3., 4.], [1., 2., 3.], [0., 1., 2.],
                         [0., 0., 1.]])
        self.cube = iris.cube.Cube(data,
                                   standard_name='rainfall_rate',
                                   units='mm h-1',
                                   dim_coords_and_dims=[
                                       (self.plugin.y_coord, 0),
                                       (self.plugin.x_coord, 1)
                                   ])

        # input time: [datetime.datetime(2018, 2, 20, 4, 0)]
        self.time_coord = DimCoord(1519099200,
                                   standard_name="time",
                                   units='seconds since 1970-01-01 00:00:00')
        self.cube.add_aux_coord(self.time_coord)

        self.timestep = datetime.timedelta(seconds=600)

    def test_basic(self):
        """Test plugin returns a cube"""
        result = self.plugin.process(self.cube, self.timestep)
        self.assertIsInstance(result, iris.cube.Cube)

    def test_advected_values(self):
        """Test output cube data is as expected"""
        expected_data = np.array([[np.nan, np.nan, np.nan],
                                  [np.nan, np.nan, np.nan], [np.nan, 2., 3.],
                                  [np.nan, 1., 2.]])
        result = self.plugin.process(self.cube, self.timestep)
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])

    def test_masked_input(self):
        """Test masked data is correctly advected and remasked"""
        mask = np.array([[True, True, True], [False, False, False],
                         [False, False, False], [False, False, False]])
        masked_data = np.ma.MaskedArray(self.cube.data, mask=mask)
        masked_cube = self.cube.copy(masked_data)

        expected_data = np.full((4, 3), np.nan, dtype=np.float32)
        expected_data[3, 1:] = np.array([1., 2.])
        expected_mask = ~np.isfinite(expected_data)

        result = self.plugin.process(masked_cube, self.timestep)
        self.assertIsInstance(result.data, np.ma.MaskedArray)
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])
        self.assertArrayEqual(result.data.mask, expected_mask)

    def test_mask_creation(self):
        """Test a mask is added if the fill value is NaN"""
        expected_output = np.array([[np.nan, np.nan, np.nan],
                                    [np.nan, np.nan, np.nan], [np.nan, 2., 3.],
                                    [np.nan, 1., 2.]])
        result = self.plugin.process(self.cube, self.timestep)
        self.assertIsInstance(result.data, np.ma.MaskedArray)
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_output[~result.data.mask])

    @ManageWarnings(record=True)
    def test_unmasked_nans(self, warning_list=None):
        """Test an array with unmasked nans raises a warning"""
        data_with_nan = self.cube.data
        data_with_nan[2, 1] = np.nan
        cube = self.cube.copy(data_with_nan)
        expected_data = np.full((4, 3), np.nan, dtype=np.float32)
        expected_data[2, 1:] = np.array([2., 3.])
        result = self.plugin.process(cube, self.timestep)
        self.assertTrue(len(warning_list) == 1)
        self.assertTrue(warning_list[0].category == UserWarning)
        self.assertIn("contains unmasked NaNs", str(warning_list[0]))
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])

    def test_time_step(self):
        """Test outputs are OK for a time step with non-second components"""
        self.plugin.vel_x.data = self.plugin.vel_x.data / 6.
        self.plugin.vel_y.data = self.plugin.vel_y.data / 6.
        expected_data = np.array([[np.nan, np.nan, np.nan],
                                  [np.nan, np.nan, np.nan], [np.nan, 2., 3.],
                                  [np.nan, 1., 2.]])
        result = self.plugin.process(self.cube, datetime.timedelta(hours=1))
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])

    def test_raises_grid_mismatch_error(self):
        """Test error is raised if cube grid does not match velocity grids"""
        x_coord = DimCoord(np.arange(5), 'projection_x_coordinate', units='km')
        y_coord = DimCoord(np.arange(4), 'projection_y_coordinate', units='km')
        cube = iris.cube.Cube(np.zeros(shape=(4, 5)),
                              standard_name='rainfall_rate',
                              units='mm h-1',
                              dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)])
        cube.add_aux_coord(self.time_coord)

        msg = "Input data grid does not match advection velocities"
        with self.assertRaisesRegex(InvalidCubeError, msg):
            self.plugin.process(cube, self.timestep)

    def test_validity_time(self):
        """Test output cube time is correctly updated"""
        result = self.plugin.process(self.cube, self.timestep)
        output_cube_time, = \
            (result.coord("time").units).num2date(result.coord("time").points)
        self.assertEqual(output_cube_time.year, 2018)
        self.assertEqual(output_cube_time.month, 2)
        self.assertEqual(output_cube_time.day, 20)
        self.assertEqual(output_cube_time.hour, 4)
        self.assertEqual(output_cube_time.minute, 10)

    def test_lead_time(self):
        """Test output cube has a forecast_period coordinate with the correct
        value and units"""
        result = self.plugin.process(self.cube, self.timestep)
        result.coord("forecast_period").convert_units("s")
        lead_time = result.coord("forecast_period").points
        self.assertEqual(len(lead_time), 1)
        self.assertEqual(lead_time[0], self.timestep.total_seconds())
예제 #2
0
class Test_process(IrisTest):
    """Test dimensioned cube data is correctly advected"""
    def setUp(self):
        """Set up plugin instance and a cube to advect"""
        vel_x = set_up_xy_velocity_cube("advection_velocity_x")
        vel_y = vel_x.copy(data=2. * np.ones(shape=(4, 3)))
        vel_y.rename("advection_velocity_y")
        self.plugin = AdvectField(vel_x, vel_y)
        self.metadata_dict = {
            "attributes": {
                "mosg__grid_version": "1.0.0",
                "mosg__model_configuration": "nc_det",
                "source": "Met Office Nowcast",
                "institution": "Met Office",
                "title": "Nowcast on UK 2 km Standard Grid"
            }
        }
        self.plugin_with_meta = AdvectField(vel_x,
                                            vel_y,
                                            metadata_dict=self.metadata_dict)
        data = np.array(
            [[2., 3., 4.], [1., 2., 3.], [0., 1., 2.], [0., 0., 1.]],
            dtype=np.float32)
        self.cube = iris.cube.Cube(data,
                                   standard_name='rainfall_rate',
                                   units='mm h-1',
                                   dim_coords_and_dims=[
                                       (self.plugin.y_coord, 0),
                                       (self.plugin.x_coord, 1)
                                   ])

        # input time: [datetime.datetime(2018, 2, 20, 4, 0)]
        self.time_coord = DimCoord(1519099200,
                                   standard_name="time",
                                   units='seconds since 1970-01-01 00:00:00')
        self.cube.add_aux_coord(self.time_coord)

        self.timestep = datetime.timedelta(seconds=600)

    def test_basic(self):
        """Test plugin returns a cube"""
        result = self.plugin.process(self.cube, self.timestep)
        self.assertIsInstance(result, iris.cube.Cube)

    def test_metadata(self):
        """Test plugin returns a cube with the desired metadata."""
        result = self.plugin_with_meta.process(self.cube, self.timestep)
        result.attributes.pop("history")
        self.assertEqual(result.attributes, self.metadata_dict["attributes"])

    def test_check_source_metadata(self):
        """Test plugin returns a cube with the desired source attribute."""
        institution_cube = self.cube.copy()
        institution_cube.attributes["institution"] = "Met Office"
        expected_source = "Met Office Nowcast"
        result = self.plugin.process(institution_cube, self.timestep)
        self.assertEqual(result.attributes["source"], expected_source)

    def test_check_source_metadata_no_institution(self):
        """Test plugin returns a cube with the desired source attribute
        without an institution."""
        institution_cube = self.cube.copy()
        expected_source = "Nowcast"
        result = self.plugin.process(institution_cube, self.timestep)
        self.assertEqual(result.attributes["source"], expected_source)

    def test_check_history_metadata(self):
        """Test plugin returns a cube with the desired metadata."""
        expected_pattern = (r'[0-9]{4}-[0-9]{2}-[0-9]{2}T'
                            r'[0-9]{2}:[0-9]{2}:[0-9]{2}Z: Nowcast')
        result = self.plugin.process(self.cube, self.timestep)
        self.assertTrue("history" in result.attributes.keys())
        self.assertRegex(result.attributes['history'], expected_pattern)

    def test_advected_values(self):
        """Test output cube data is as expected"""
        expected_data = np.array([[np.nan, np.nan, np.nan],
                                  [np.nan, np.nan, np.nan], [np.nan, 2., 3.],
                                  [np.nan, 1., 2.]])
        result = self.plugin.process(self.cube, self.timestep)
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])

    def test_masked_input(self):
        """Test masked data is correctly advected and remasked"""
        mask = np.array([[True, True, True], [False, False, False],
                         [False, False, False], [False, False, False]])
        masked_data = np.ma.MaskedArray(self.cube.data, mask=mask)
        masked_cube = self.cube.copy(masked_data)

        expected_data = np.full((4, 3), np.nan, dtype=np.float32)
        expected_data[3, 1:] = np.array([1., 2.])
        expected_mask = ~np.isfinite(expected_data)

        result = self.plugin.process(masked_cube, self.timestep)
        self.assertIsInstance(result.data, np.ma.MaskedArray)
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])
        self.assertArrayEqual(result.data.mask, expected_mask)

    def test_mask_creation(self):
        """Test a mask is added if the fill value is NaN"""
        expected_output = np.array([[np.nan, np.nan, np.nan],
                                    [np.nan, np.nan, np.nan], [np.nan, 2., 3.],
                                    [np.nan, 1., 2.]])
        result = self.plugin.process(self.cube, self.timestep)
        self.assertIsInstance(result.data, np.ma.MaskedArray)
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_output[~result.data.mask])

    @ManageWarnings(record=True)
    def test_unmasked_nans(self, warning_list=None):
        """Test an array with unmasked nans raises a warning"""
        data_with_nan = self.cube.data
        data_with_nan[2, 1] = np.nan
        cube = self.cube.copy(data_with_nan)
        expected_data = np.full((4, 3), np.nan, dtype=np.float32)
        expected_data[2, 1:] = np.array([2., 3.])
        result = self.plugin.process(cube, self.timestep)
        warning_msg = "contains unmasked NaNs"
        self.assertTrue(
            any(item.category == UserWarning for item in warning_list))
        self.assertTrue(any(warning_msg in str(item) for item in warning_list))
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])

    def test_time_step(self):
        """Test outputs are OK for a time step with non-second components"""
        self.plugin.vel_x.data = self.plugin.vel_x.data / 6.
        self.plugin.vel_y.data = self.plugin.vel_y.data / 6.
        expected_data = np.array([[np.nan, np.nan, np.nan],
                                  [np.nan, np.nan, np.nan], [np.nan, 2., 3.],
                                  [np.nan, 1., 2.]])
        result = self.plugin.process(self.cube, datetime.timedelta(hours=1))
        self.assertArrayAlmostEqual(result.data[~result.data.mask],
                                    expected_data[~result.data.mask])

    def test_raises_grid_mismatch_error(self):
        """Test error is raised if cube grid does not match velocity grids"""
        x_coord = DimCoord(np.arange(5), 'projection_x_coordinate', units='km')
        y_coord = DimCoord(np.arange(4), 'projection_y_coordinate', units='km')
        cube = iris.cube.Cube(np.zeros(shape=(4, 5)),
                              standard_name='rainfall_rate',
                              units='mm h-1',
                              dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)])
        cube.add_aux_coord(self.time_coord)

        msg = "Input data grid does not match advection velocities"
        with self.assertRaisesRegex(InvalidCubeError, msg):
            self.plugin.process(cube, self.timestep)

    def test_validity_time(self):
        """Test output cube time is correctly updated"""
        result = self.plugin.process(self.cube, self.timestep)
        output_cube_time, = \
            (result.coord("time").units).num2date(result.coord("time").points)
        self.assertEqual(output_cube_time.year, 2018)
        self.assertEqual(output_cube_time.month, 2)
        self.assertEqual(output_cube_time.day, 20)
        self.assertEqual(output_cube_time.hour, 4)
        self.assertEqual(output_cube_time.minute, 10)

    def test_lead_time(self):
        """Test output cube has a forecast_period coordinate with the correct
        value and units"""
        result = self.plugin.process(self.cube, self.timestep)
        result.coord("forecast_period").convert_units("s")
        lead_time = result.coord("forecast_period").points
        self.assertEqual(len(lead_time), 1)
        self.assertEqual(lead_time[0], self.timestep.total_seconds())

    def test_units_and_datatypes_for_time_coordinates(self):
        """Test that the output cube contains the desired units and datatypes
        for the time, forecast_reference_time and forecast_period coordinate.
        In this instance, no forecast_reference_time coordinate is present on
        the input cube.
        """
        result = self.plugin.process(self.cube, self.timestep)
        self.assertEqual(result.coord("forecast_period").points, 600)
        # 2017-11-10 04:10:00
        self.assertEqual(result.coord("time").points, 1519099800)
        self.assertEqual(
            result.coord("forecast_reference_time").points, 1519099200)
        self.assertEqual(result.coord("forecast_period").units, "seconds")
        self.assertEqual(
            result.coord("time").units, "seconds since 1970-01-01 00:00:00")
        self.assertEqual(
            result.coord("forecast_reference_time").units,
            "seconds since 1970-01-01 00:00:00")
        self.assertEqual(result.coord("forecast_period").dtype, np.int32)
        self.assertEqual(result.coord("time").dtype, np.int64)
        self.assertEqual(
            result.coord("forecast_reference_time").dtype, np.int64)

    def test_time_unit_conversion(self):
        """Test that the output cube contains the desired units and datatypes
        for the time, forecast_reference_time and forecast_period coordinate,
        where a unit conversion has been required for the time and forecast
        reference time coordinates."""
        self.cube.coord("time").convert_units("hours since 1970-01-01 00:00")
        frt_coord = (DimCoord(1519099200,
                              standard_name="forecast_reference_time",
                              units='seconds since 1970-01-01 00:00:00'))
        frt_coord.convert_units("hours since 1970-01-01 00:00")
        self.cube.add_aux_coord(frt_coord)
        self.cube.coord("forecast_reference_time").convert_units(
            "hours since 1970-01-01 00:00")
        result = self.plugin.process(self.cube, self.timestep)
        self.assertEqual(result.coord("forecast_period").points, 600)
        # 2017-11-10 04:10:00
        self.assertEqual(result.coord("time").points, 1519099800)
        self.assertEqual(
            result.coord("forecast_reference_time").points, 1519099200)
        self.assertEqual(result.coord("forecast_period").units, "seconds")
        self.assertEqual(
            result.coord("time").units, "seconds since 1970-01-01 00:00:00")
        self.assertEqual(
            result.coord("forecast_reference_time").units,
            "seconds since 1970-01-01 00:00:00")
        self.assertEqual(result.coord("forecast_period").dtype, np.int32)
        self.assertEqual(result.coord("time").dtype, np.int64)
        self.assertEqual(
            result.coord("forecast_reference_time").dtype, np.int64)

    def test_floating_point_time_input(self):
        """Test that the output cube contains the desired units and datatypes
        for the time, forecast_reference_time and forecast_period coordinate,
        where a time and forecast_reference_time are input as floating point
        values, so that the impact of the rounding is clearer."""
        self.cube.coord("time").points = 1519099199.7
        frt_coord = (DimCoord(1519099199.7,
                              standard_name="forecast_reference_time",
                              units='seconds since 1970-01-01 00:00:00'))
        self.cube.add_aux_coord(frt_coord)
        result = self.plugin.process(self.cube, self.timestep)
        self.assertEqual(result.coord("forecast_period").points, 600)
        # 2017-11-10 04:10:00
        self.assertEqual(result.coord("time").points, 1519099800)
        self.assertEqual(
            result.coord("forecast_reference_time").points, 1519099200)
        self.assertEqual(result.coord("forecast_period").units, "seconds")
        self.assertEqual(
            result.coord("time").units, "seconds since 1970-01-01 00:00:00")
        self.assertEqual(
            result.coord("forecast_reference_time").units,
            "seconds since 1970-01-01 00:00:00")
        self.assertEqual(result.coord("forecast_period").dtype, np.int32)
        self.assertEqual(result.coord("time").dtype, np.int64)
        self.assertEqual(
            result.coord("forecast_reference_time").dtype, np.int64)
예제 #3
0
class Test_process(IrisTest):
    """Test dimensioned cube data is correctly advected"""

    def setUp(self):
        """Set up plugin instance and a cube to advect"""
        vel_x = set_up_xy_velocity_cube("advection_velocity_x")
        vel_y = vel_x.copy(data=2.*np.ones(shape=(4, 3)))
        vel_y.rename("advection_velocity_y")
        self.plugin = AdvectField(vel_x, vel_y)
        data = np.array([[2., 3., 4.],
                         [1., 2., 3.],
                         [0., 1., 2.],
                         [0., 0., 1.]])
        self.cube = iris.cube.Cube(
            data, standard_name='rainfall_rate', units='mm h-1',
            dim_coords_and_dims=[(self.plugin.y_coord, 0),
                                 (self.plugin.x_coord, 1)])

        # input time: [datetime.datetime(2018, 2, 20, 4, 0)]
        self.time_coord = DimCoord(1519099200, standard_name="time",
                                   units='seconds since 1970-01-01 00:00:00')
        self.cube.add_aux_coord(self.time_coord)

        self.timestep = datetime.timedelta(seconds=600)

    def test_basic(self):
        """Test plugin returns a cube"""
        result = self.plugin.process(self.cube, self.timestep)
        self.assertIsInstance(result, iris.cube.Cube)

    def test_advected_values(self):
        """Test output cube data is as expected"""
        expected_output = np.array([[0., 0., 0.],
                                    [0., 0., 0.],
                                    [0., 2., 3.],
                                    [0., 1., 2.]])
        result = self.plugin.process(self.cube, self.timestep)
        self.assertArrayAlmostEqual(result.data, expected_output)

    def test_fill_values(self):
        """Test output cube data is padded as expected where source grid
        points are out of bounds"""
        expected_output = np.array([[-1., -1., -1.],
                                    [-1., -1., -1.],
                                    [-1., 2., 3.],
                                    [-1., 1., 2.]])
        result = self.plugin.process(self.cube, self.timestep, fill_value=-1.)
        self.assertArrayAlmostEqual(result.data, expected_output)

    def test_time_step(self):
        """Test outputs are OK for a time step with non-second components"""
        self.plugin.vel_x.data = self.plugin.vel_x.data / 6.
        self.plugin.vel_y.data = self.plugin.vel_y.data / 6.
        expected_output = np.array([[0., 0., 0.],
                                    [0., 0., 0.],
                                    [0., 2., 3.],
                                    [0., 1., 2.]])
        result = self.plugin.process(self.cube, datetime.timedelta(hours=1))
        self.assertArrayAlmostEqual(result.data, expected_output)

    def test_raises_grid_mismatch_error(self):
        """Test error is raised if cube grid does not match velocity grids"""
        x_coord = DimCoord(np.arange(5), 'projection_x_coordinate', units='km')
        y_coord = DimCoord(np.arange(4), 'projection_y_coordinate', units='km')
        cube = iris.cube.Cube(np.zeros(shape=(4, 5)),
                              standard_name='rainfall_rate', units='mm h-1',
                              dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)])
        cube.add_aux_coord(self.time_coord)

        msg = "Input data grid does not match advection velocities"
        with self.assertRaisesRegex(InvalidCubeError, msg):
            self.plugin.process(cube, self.timestep)

    def test_validity_time(self):
        """Test output cube time is correctly updated"""
        result = self.plugin.process(self.cube, self.timestep)
        output_cube_time, = \
            (result.coord("time").units).num2date(result.coord("time").points)
        self.assertEqual(output_cube_time.year, 2018)
        self.assertEqual(output_cube_time.month, 2)
        self.assertEqual(output_cube_time.day, 20)
        self.assertEqual(output_cube_time.hour, 4)
        self.assertEqual(output_cube_time.minute, 10)