def test_central_point_not_in_allowed_range(self):
     """Test that an exception is generated when the central cube is not
        within the allowed range."""
     width = 1.0
     forecast_period = 2
     plugin = TriangularWeightedBlendAcrossAdjacentPoints(
         'forecast_period', forecast_period, 'hours', width)
     msg = "The central point 2 in units of hours"
     with self.assertRaisesRegex(ValueError, msg):
         plugin.process(self.cube)
Ejemplo n.º 2
0
    def test_works_two_thresh(self):
        """Test that the plugin works with a cube that contains multiple
           thresholds."""
        width = 2.0

        changes = {'points': [0.25], 'units': '1'}
        cube_with_thresh1 = add_coord(self.cube.copy(), 'threshold', changes)

        changes = {'points': [0.5], 'units': '1'}
        cube_with_thresh2 = add_coord(self.cube.copy(), 'threshold', changes)

        changes = {'points': [0.75], 'units': '1'}
        cube_with_thresh3 = add_coord(self.cube.copy(), 'threshold', changes)

        cubelist = iris.cube.CubeList([cube_with_thresh1, cube_with_thresh2,
                                       cube_with_thresh3])
        thresh_cubes = concatenate_cubes(cubelist,
                                         coords_to_slice_over='threshold')

        plugin = TriangularWeightedBlendAcrossAdjacentPoints(
            'forecast_period', self.forecast_period, 'hours', width,
            'weighted_mean')
        result = plugin.process(thresh_cubes)

        # Test that the result cube retains threshold co-ordinates
        # from origonal cube.
        self.assertEqual(thresh_cubes.coord('threshold'),
                         result.coord('threshold'))
    def test_works_one_thresh(self):
        """Test that the plugin retains the single threshold from the input
           cube."""

        # Creates a cube containing the expected outputs.
        fill_value = 1 + 1 / 3.0
        data = np.full((2, 2), fill_value)

        # Take a slice of the time coordinate.
        expected_cube = self.cube[0].copy(data.astype(np.float32))

        # Add threshold axis to expected output cube.
        changes = {'points': [0.5], 'units': '1', 'var_name': 'threshold'}
        expected_cube = add_coord(expected_cube, 'precipitation_amount',
                                  changes)

        # Add threshold axis to standard input cube.
        cube_with_thresh = add_coord(self.cube.copy(), 'precipitation_amount',
                                     changes)

        width = 2.0
        plugin = TriangularWeightedBlendAcrossAdjacentPoints(
            'forecast_period', self.forecast_period, 'hours', width)
        result = plugin.process(cube_with_thresh)

        # Test that the result cube retains threshold co-ordinates
        # from original cube.
        self.assertEqual(expected_cube.coord('precipitation_amount'),
                         result.coord('precipitation_amount'))
        self.assertArrayEqual(expected_cube.data, result.data)
        self.assertEqual(expected_cube, result)
 def test_basic_triangle_width_1(self):
     """Test that the plugin produces sensible results when the width
        of the triangle is 1. This is equivalent to no blending."""
     width = 1.0
     plugin = TriangularWeightedBlendAcrossAdjacentPoints(
         'forecast_period', self.forecast_period, 'hours', width)
     result = plugin.process(self.cube)
     self.assertEqual(self.central_cube.coord('forecast_period'),
                      result.coord('forecast_period'))
     self.assertEqual(self.central_cube.coord('time'), result.coord('time'))
     self.assertArrayEqual(self.central_cube.data, result.data)
 def test_basic_triangle_width_2(self):
     """Test that the plugin produces sensible results when the width
        of the triangle is 2 and there is some blending."""
     width = 2.0
     plugin = TriangularWeightedBlendAcrossAdjacentPoints(
         'forecast_period', self.forecast_period, 'hours', width)
     result = plugin.process(self.cube)
     expected_data = np.array([[1.333333, 1.333333], [1.333333, 1.333333]])
     self.assertEqual(self.central_cube.coord('forecast_period'),
                      result.coord('forecast_period'))
     self.assertEqual(self.central_cube.coord('time'), result.coord('time'))
     self.assertArrayAlmostEqual(expected_data, result.data)
 def test_alternative_parameter_units(self):
     """Test that the plugin produces sensible results when the width
        of the triangle is 7200 seconds. """
     forecast_period = 0
     width = 7200.0
     plugin = TriangularWeightedBlendAcrossAdjacentPoints(
         'forecast_period', forecast_period, 'seconds', width)
     result = plugin.process(self.cube)
     expected_data = np.array([[1.333333, 1.333333], [1.333333, 1.333333]])
     self.assertEqual(self.central_cube.coord('forecast_period'),
                      result.coord('forecast_period'))
     self.assertEqual(self.central_cube.coord('time'), result.coord('time'))
     self.assertArrayAlmostEqual(expected_data, result.data)
Ejemplo n.º 7
0
    def test_input_cube_no_change(self):
        """Test that the plugin does not change the origonal input cube."""

        # Add threshold axis to standard input cube.
        changes = {'points': [0.5], 'units': '1'}
        cube_with_thresh = add_coord(self.cube.copy(), 'threshold', changes)
        original_cube = cube_with_thresh.copy()

        width = 2.0
        plugin = TriangularWeightedBlendAcrossAdjacentPoints(
            'forecast_period', self.forecast_period, 'hours', width,
            'weighted_mean')
        _ = plugin.process(cube_with_thresh)

        # Test that the input cube is unchanged by the function.
        self.assertEqual(cube_with_thresh, original_cube)
Ejemplo n.º 8
0
 def test_basic_triangle_width_2_max_mode(self):
     """Test that the plugin produces sensible results when the width
        of the triangle is 2 and there is some blending. This time
        use the weighted_maximum mode"""
     width = 2.0
     plugin = TriangularWeightedBlendAcrossAdjacentPoints(
         'forecast_period', width, 'hours', 'weighted_maximum')
     result = plugin.process(self.cube)
     expected_data = np.array([[[0.6666666, 0.6666666],
                                [0.6666666, 0.6666666]],
                               [[1.3333333, 1.3333333],
                                [1.3333333, 1.3333333]]])
     self.assertEqual(self.cube.coord('forecast_period'),
                      result.coord('forecast_period'))
     self.assertEqual(self.cube.coord('time'), result.coord('time'))
     self.assertArrayAlmostEqual(expected_data, result.data)
Ejemplo n.º 9
0
    def test_works_one_thresh(self):
        """Test that the plugin retains the single threshold from the input
           cube."""

        # Creates a cube containing the expected outputs.
        fill_value = 1 + 1/3.0
        data = np.full((2, 2), fill_value)

        expected_cube = (Cube(data, units='m',
                         standard_name='lwe_thickness_of_precipitation_amount')
                         )
        expected_cube.add_dim_coord(DimCoord(np.linspace(-45.0, 45.0, 2),
                                    'latitude', units='degrees'), 0)
        expected_cube.add_dim_coord(DimCoord(np.linspace(120, 180, 2),
                                    'longitude', units='degrees'), 1)

        time_origin = 'hours since 1970-01-01 00:00:00'
        calendar = 'gregorian'
        tunit = Unit(time_origin, calendar)
        expected_cube.add_aux_coord(DimCoord([402192.5], 'time', units=tunit))
        expected_cube.add_aux_coord(DimCoord([0], 'forecast_period',
                                             units='hours'))
        # Add threshold axis to expected output cube.
        changes = {'points': [0.5], 'units': '1'}
        expected_cube = add_coord(expected_cube, 'threshold', changes)

        # Add threshold axis to standard input cube.
        cube_with_thresh = add_coord(self.cube.copy(), 'threshold', changes)

        width = 2.0
        plugin = TriangularWeightedBlendAcrossAdjacentPoints(
            'forecast_period', self.forecast_period, 'hours', width,
            'weighted_mean')
        result = plugin.process(cube_with_thresh)

        # Test that the result cube retains threshold co-ordinates
        # from origonal cube.
        self.assertEqual(expected_cube.coord('threshold'),
                         result.coord('threshold'))
        self.assertArrayEqual(expected_cube.data, result.data)
        self.assertEqual(expected_cube, result)
Ejemplo n.º 10
0
def process(*cubes: cli.inputcube,
            coordinate,
            central_point: float,
            units=None,
            width: float = None,
            calendar='gregorian',
            blend_time_using_forecast_period=False):
    """Runs weighted blending across adjacent points.

    Uses the TriangularWeightedBlendAcrossAdjacentPoints to blend across
    a particular coordinate. It does not collapse the coordinate, but
    instead blends across adjacent points and puts the blended values back
    in the original coordinate, with adjusted bounds.

    Args:
        cubes (list of iris.cube.Cube):
            A list of cubes including and surrounding the central point.
        coordinate (str):
            The coordinate over which the blending will be applied.
        central_point (float):
            Central point at which the output from the triangular weighted
            blending will be calculated. This should be in the units of the
            units argument that is passed in. This value should be a point
            on the coordinate for blending over.
        units (str):
            Units of the central_point and width
        width (float):
            Width of the triangular weighting function used in the blending,
            in the units of the units argument.
        calendar (str)
            Calendar for parameter_unit if required.
        blend_time_using_forecast_period (bool):
            If True, we are blending over time but using the forecast
            period coordinate as a proxy. Note, this should only be used when
            time and forecast_period share a dimension: i.e when all cubes
            provided are from the same forecast cycle.

    Returns:
        iris.cube.Cube:
            A processed Cube

    Raises:
        ValueError:
            If coordinate has "time" in it.
        ValueError:
            If blend_time_forecast_period is not used with forecast_period
            coordinate.

    """
    from cf_units import Unit
    from iris.cube import CubeList

    from improver.blending.blend_across_adjacent_points import \
        TriangularWeightedBlendAcrossAdjacentPoints
    from improver.utilities.cube_manipulation import MergeCubes

    # TriangularWeightedBlendAcrossAdjacentPoints can't currently handle
    # blending over times where iris reads the coordinate points as datetime
    # objects. Fail here to avoid unhelpful errors downstream.
    if "time" in coordinate:
        msg = ("Cannot blend over {} coordinate (points encoded as datetime "
               "objects)".format(coordinate))
        raise ValueError(msg)

    # This is left as a placeholder for when we have this capability
    if coordinate == 'time':
        units = Unit(units, calendar)

    cubes = CubeList(cubes)

    if blend_time_using_forecast_period and coordinate == 'forecast_period':
        cube = MergeCubes().process(cubes, check_time_bounds_ranges=True)
    elif blend_time_using_forecast_period:
        msg = ('"--blend-time-using-forecast-period" can only be used with '
               '"forecast_period" coordinate')
        raise ValueError(msg)
    else:
        cube = MergeCubes().process(cubes)

    blending_plugin = TriangularWeightedBlendAcrossAdjacentPoints(
        coordinate, central_point, units, width)
    result = blending_plugin.process(cube)
    return result
def main(argv=None):
    """Load in arguments and ensure they are set correctly.
       Then run Triangular weighted blending across the given coordinate."""
    parser = ArgParser(
        description='Use the TriangularWeightedBlendAcrossAdjacentPoints to '
                    'blend across a particular coordinate. It does not '
                    'collapse the coordinate, but instead blends across '
                    'adjacent points and puts the blended values back in the '
                    'original coordinate, with adjusted bounds.')
    parser.add_argument('coordinate', type=str,
                        metavar='COORDINATE_TO_BLEND_OVER',
                        help='The coordinate over which the blending '
                             'will be applied.')
    parser.add_argument('central_point', metavar='CENTRAL_POINT', type=float,
                        help='Central point at which the output from the '
                             'triangular weighted blending will be '
                             'calculated. This should be in the units of the '
                             'units argument that is passed in. '
                             'This value should be a point on the '
                             'coordinate for blending over.')
    parser.add_argument('--units', metavar='UNIT_STRING', required=True,
                        help='Units of the the central_point and width.')
    parser.add_argument('--calendar', metavar='CALENDAR',
                        default='gregorian',
                        help='Calendar for parameter_unit if required. '
                             'Default=gregorian')
    parser.add_argument('--width', metavar='TRIANGLE_WIDTH', type=float,
                        required=True,
                        help='Width of the triangular weighting function used '
                             'in the blending, in the units of the '
                             'units argument passed in.')
    parser.add_argument('--blend_time_using_forecast_period',
                        default=False, action='store_true', help='Flag that '
                        'we are blending over time but using the forecast '
                        'period coordinate as a proxy.  Note this should only '
                        'be used when time and forecast_period share a '
                        'dimension: ie when all files provided are from the '
                        'same forecast cycle.')
    parser.add_argument('input_filepaths', metavar='INPUT_FILES', nargs="+",
                        help='Paths to input NetCDF files including and '
                             'surrounding the central_point.')
    parser.add_argument('output_filepath', metavar='OUTPUT_FILE',
                        help='The output path for the processed NetCDF.')

    args = parser.parse_args(args=argv)

    # TriangularWeightedBlendAcrossAdjacentPoints can't currently handle
    # blending over times where iris reads the coordinate points as datetime
    # objects.  Fail here to avoid unhelpful errors downstream.
    if "time" in args.coordinate:
        msg = ("Cannot blend over {} coordinate (points encoded as datetime "
               "objects)".format(args.coordinate))
        raise ValueError(msg)

    # This is left as a placeholder for when we have this capability
    if args.coordinate == 'time':
        units = Unit(args.units, args.calendar)
    else:
        units = args.units

    cubelist = load_cubelist(args.input_filepaths)

    if (args.blend_time_using_forecast_period and
            args.coordinate == 'forecast_period'):
        cube = MergeCubes().process(cubelist, check_time_bounds_ranges=True)
    elif args.blend_time_using_forecast_period:
        msg = ('"--blend_time_using_forecast_period" can only be used with '
               '"forecast_period" coordinate')
        raise ValueError(msg)
    else:
        cube = MergeCubes().process(cubelist)

    BlendingPlugin = TriangularWeightedBlendAcrossAdjacentPoints(
        args.coordinate, args.central_point, units, args.width)
    result = BlendingPlugin.process(cube)
    save_netcdf(result, args.output_filepath)