Beispiel #1
0
class Test_process(IrisTest):
    """Tests the process method"""
    def setUp(self):
        """Create dummy cubes for tests"""
        self.plugin = ResolveWindComponents()
        wind_speed_data = np.array([[6, 5, 4, 3], [8, 6, 4, 4], [12, 8, 6, 5]],
                                   dtype=np.float32)
        self.wind_speed_cube = set_up_cube(wind_speed_data, "wind_speed",
                                           "knots")

        wind_direction_data = np.array(
            [[138, 142, 141, 141], [141, 143, 140, 142], [142, 146, 141, 142]],
            dtype=np.float32)
        self.wind_direction_cube = set_up_cube(wind_direction_data,
                                               "wind_to_direction", "degrees")

        self.expected_u = np.array([[3.774919, 2.902824, 2.406847, 1.826062],
                                    [4.683339, 3.386423, 2.456537, 2.374629],
                                    [6.829466, 4.149148, 3.593584, 2.963243]],
                                   dtype=np.float32)

        self.expected_v = np.array(
            [[-4.663688, -4.071071, -3.194854, -2.380230],
             [-6.485857, -4.952993, -3.156806, -3.218872],
             [-9.867035, -6.839924, -4.804805, -4.027306]],
            dtype=np.float32)

    def test_basic(self):
        """Test plugin creates two output cubes with the correct metadata"""
        ucube, vcube = self.plugin.process(self.wind_speed_cube,
                                           self.wind_direction_cube)
        for cube in ucube, vcube:
            self.assertIsInstance(cube, iris.cube.Cube)
            self.assertEqual(cube.units, self.wind_speed_cube.units)
        self.assertEqual(ucube.name(), "grid_eastward_wind")
        self.assertEqual(vcube.name(), "grid_northward_wind")

    def test_values(self):
        """Test plugin generates expected wind values"""
        ucube, vcube = self.plugin.process(self.wind_speed_cube,
                                           self.wind_direction_cube)
        self.assertArrayAlmostEqual(ucube.data, self.expected_u)
        self.assertArrayAlmostEqual(vcube.data, self.expected_v)

    def test_coordinate_value_mismatch(self):
        """Test an error is raised if coordinate values are different for wind
        speed and direction cubes"""
        self.wind_direction_cube.coord(axis='y').convert_units("km")
        msg = 'Wind speed and direction cubes have unmatched coordinates'
        with self.assertRaisesRegex(ValueError, msg):
            _, _ = self.plugin.process(self.wind_speed_cube,
                                       self.wind_direction_cube)

    def test_projection_mismatch(self):
        """Test an error is raised if coordinate names are different for wind
        speed and direction cubes"""
        self.wind_speed_cube.coord(axis='x').rename('longitude')
        self.wind_speed_cube.coord(axis='y').rename('latitude')
        msg = 'Wind speed and direction cubes have unmatched coordinates'
        with self.assertRaisesRegex(ValueError, msg):
            _, _ = self.plugin.process(self.wind_speed_cube,
                                       self.wind_direction_cube)

    def test_height_levels(self):
        """Test a cube on more than one height level is correctly processed"""
        wind_speed_3d = add_new_dimension(self.wind_speed_cube, 3, "height",
                                          "km")
        wind_direction_3d = add_new_dimension(self.wind_direction_cube, 3,
                                              "height", "km")
        ucube, vcube = self.plugin.process(wind_speed_3d, wind_direction_3d)
        self.assertSequenceEqual(ucube.shape, (3, 3, 4))
        self.assertArrayAlmostEqual(ucube[1].data, self.expected_u)
        self.assertArrayAlmostEqual(vcube[2].data, self.expected_v)

    def test_wind_from_direction(self):
        """Test correct behaviour when wind direction is 'from' not 'to'.
        We do not get perfect direction inversion to the 7th decimal place here
        because we ignore imprecision in the iris rotate_winds calcuation near
        the corners of the domain, and regrid the available data linearly to
        fill the gap.  The output wind speeds (in m s-1) compare equal to the
        5th decimal place.
        """
        expected_u = -1. * self.expected_u
        expected_v = -1. * self.expected_v
        self.wind_direction_cube.rename("wind_from_direction")
        ucube, vcube = self.plugin.process(self.wind_speed_cube,
                                           self.wind_direction_cube)
        self.assertTrue(np.allclose(ucube.data, expected_u, atol=1e-6))
        self.assertTrue(np.allclose(vcube.data, expected_v, atol=1e-6))
Beispiel #2
0
 def test_basic(self):
     """Tests the output string is as expected"""
     result = str(ResolveWindComponents())
     self.assertEqual(result, '<ResolveWindComponents>')
def main(argv=None):
    """Calculate orographic enhancement of precipitation from model pressure,
    temperature, relative humidity and wind input files"""

    parser = ArgParser(description='Calculate orographic enhancement using the'
                       ' ResolveWindComponents() and OrographicEnhancement() '
                       'plugins. Outputs data on the high resolution orography'
                       ' grid and regridded to the coarser resolution of the '
                       'input diagnostic variables.')

    parser.add_argument('temperature_filepath',
                        metavar='TEMPERATURE_FILEPATH',
                        help='Full path to input NetCDF file of temperature on'
                        ' height levels')
    parser.add_argument('humidity_filepath',
                        metavar='HUMIDITY_FILEPATH',
                        help='Full path to input NetCDF file of relative '
                        'humidity on height levels')
    parser.add_argument('pressure_filepath',
                        metavar='PRESSURE_FILEPATH',
                        help='Full path to input NetCDF file of pressure on '
                        'height levels')
    parser.add_argument('windspeed_filepath',
                        metavar='WINDSPEED_FILEPATH',
                        help='Full path to input NetCDF file of wind speed on '
                        'height levels')
    parser.add_argument('winddir_filepath',
                        metavar='WINDDIR_FILEPATH',
                        help='Full path to input NetCDF file of wind direction'
                        ' on height levels')
    parser.add_argument('orography_filepath',
                        metavar='OROGRAPHY_FILEPATH',
                        help='Full path to input NetCDF high resolution '
                        'orography ancillary. This should be on the same or a '
                        'finer resolution grid than the input variables, and '
                        'defines the grid on which the orographic enhancement '
                        'will be calculated.')
    parser.add_argument('output_dir',
                        metavar='OUTPUT_DIR',
                        help='Directory '
                        'to write output orographic enhancement files')
    parser.add_argument('--boundary_height',
                        type=float,
                        default=1000.,
                        help='Model height level to extract variables for '
                        'calculating orographic enhancement, as proxy for '
                        'the boundary layer.')
    parser.add_argument('--boundary_height_units',
                        type=str,
                        default='m',
                        help='Units of the boundary height specified for '
                        'extracting model levels.')

    args = parser.parse_args(args=argv)

    constraint_info = (args.boundary_height, args.boundary_height_units)

    temperature = load_and_extract(args.temperature_filepath, *constraint_info)
    humidity = load_and_extract(args.humidity_filepath, *constraint_info)
    pressure = load_and_extract(args.pressure_filepath, *constraint_info)
    wind_speed = load_and_extract(args.windspeed_filepath, *constraint_info)
    wind_dir = load_and_extract(args.winddir_filepath, *constraint_info)

    # resolve u and v wind components
    uwind, vwind = ResolveWindComponents().process(wind_speed, wind_dir)

    # load high resolution orography
    orography = load_cube(args.orography_filepath)

    # calculate orographic enhancement
    orogenh_high_res, orogenh_standard = OrographicEnhancement().process(
        temperature, humidity, pressure, uwind, vwind, orography)

    # generate file names
    fname_standard = os.path.join(args.output_dir,
                                  generate_file_name(orogenh_standard))
    fname_high_res = os.path.join(
        args.output_dir,
        generate_file_name(orogenh_high_res,
                           parameter="orographic_enhancement_high_resolution"))

    # save output files
    save_netcdf(orogenh_standard, fname_standard)
    save_netcdf(orogenh_high_res, fname_high_res)
Beispiel #4
0
def process(input_cube,
            u_cube,
            v_cube,
            speed_cube,
            direction_cube,
            orographic_enhancement_cube=None,
            metadata_dict=None,
            max_lead_time=360,
            lead_time_interval=15,
            accumulation_fidelity=0,
            accumulation_period=15,
            accumulation_units='m'):
    """Module  to extrapolate input cubes given advection velocity fields.

    Args:
        input_cube (iris.cube.Cube):
            The input Cube to be processed.
        u_cube (iris.cube.Cube):
            Cube with the velocities in the x direction.
            Must be used with v_cube.
            s_cube and d_cube must be None.
        v_cube (iris.cube.Cube):
            Cube with the velocities in the y direction.
            Must be used with u_cube.
            s_cube and d_cube must be None.
        speed_cube (iris.cube.Cube):
            Cube containing advection speeds, usually wind speed.
            Must be used with d_cube.
            u_cube and v_cube must be None.
        direction_cube (iris.cube.Cube):
            Cube from which advection speeds are coming. The directions
            should be on the same grid as the input speeds, including the same
            vertical levels.
            Must be used with d_cube.
            u_cube and v_cube must be None.
        orographic_enhancement_cube (iris.cube.Cube):
            Cube containing the orographic enhancement fields. May have data
            for multiple times in the cube.
            Default is None.
        metadata_dict (dict):
            Dictionary containing the required changes to the metadata.
            Information describing the intended contents of the dictionary
            is available in improver.utilities.cube_metadata.amend_metadata.
            Every output cube will have the metadata_dict applied.
            Default is None.
        max_lead_time (int):
            Maximum lead time required (mins).
            Default is 360.
        lead_time_interval (int):
            Interval between required lead times (mins).
            Default is 15.
        accumulation_fidelity (int):
            If set, this will additionally return accumulations calculated
            from the advected fields. This fidelity specifies the time
            interval in minutes between advected fields that is used to
            calculate these accumulations. This interval must be a factor of
            the lead_time_interval.
            Default is 0.
        accumulation_period (int):
            The period over which the accumulation is calculated (mins).
            Only full accumulation periods will be computed. At lead times
            that are shorter than the accumulation period, no accumulation
            output will be produced.
        accumulation_units (str):
            Desired units in which the accumulations should be expressed.
            e.g. 'mm'
            Default is 'm'.

    Returns:
        (tuple) tuple containing:
            **accumulation_cubes** (iris.cube.Cubelist):
                A cubelist containing precipitation accumulation cubes where
                the accumulation periods are determined by the
                lead_time_interval.
            **forecast_to_return** (iris.cube.Cubelist):
                New cubes with updated time and extrapolated data.

    Raises:
        ValueError:
            can either use s_cube and d_cube or u_cube and v_cube.
            Therefore: (s and d)⊕(u and v)
        ValueError:
            If accumulation_fidelity is greater than 0 and max_lead_time is not
            cleanly divisible by accumulation_fidelity.
    """

    if (speed_cube and direction_cube) and not (u_cube or v_cube):
        u_cube, v_cube = ResolveWindComponents().process(
            speed_cube, direction_cube)
    elif (u_cube or v_cube) and (speed_cube or direction_cube):
        raise ValueError('Cannot mix advection component velocities with speed'
                         ' and direction')
    # generate list of lead times in minutes
    lead_times = np.arange(0, max_lead_time + 1, lead_time_interval)

    # determine whether accumulations are also to be returned.
    time_interval = lead_time_interval
    if accumulation_fidelity > 0:
        fraction, _ = np.modf(max_lead_time / accumulation_fidelity)
        if fraction != 0:
            msg = ("The specified lead_time_interval ({}) is not cleanly "
                   "divisible by the specified accumulation_fidelity ({}). As "
                   "a result the lead_time_interval cannot be constructed from"
                   " accumulation cubes at this fidelity.")
            raise ValueError(
                msg.format(lead_time_interval, accumulation_fidelity))

        time_interval = accumulation_fidelity
        lead_times = np.arange(0, max_lead_time + 1, time_interval)

    lead_time_filter = lead_time_interval // time_interval
    forecast_plugin = CreateExtrapolationForecast(
        input_cube,
        u_cube,
        v_cube,
        orographic_enhancement_cube=orographic_enhancement_cube,
        metadata_dict=metadata_dict)

    # extrapolate input data to required lead times
    forecast_cubes = iris.cube.CubeList()
    for lead_time in lead_times:
        forecast_cubes.append(
            forecast_plugin.extrapolate(leadtime_minutes=lead_time))

    forecast_to_return = forecast_cubes[::lead_time_filter].copy()
    # return rate cubes
    # calculate accumulations if required
    accumulation_cubes = None
    if accumulation_fidelity > 0:
        lead_times = (np.arange(lead_time_interval, max_lead_time + 1,
                                lead_time_interval))
        plugin = Accumulation(accumulation_units=accumulation_units,
                              accumulation_period=accumulation_period * 60,
                              forecast_periods=lead_times * 60)
        accumulation_cubes = plugin.process(forecast_cubes)

    return accumulation_cubes, forecast_to_return
Beispiel #5
0
def main(argv=None):
    """Extrapolate data forward in time."""

    parser = ArgParser(
        description="Extrapolate input data to required lead times.")
    parser.add_argument("input_filepath",
                        metavar="INPUT_FILEPATH",
                        type=str,
                        help="Path to input NetCDF file.")

    group = parser.add_mutually_exclusive_group()
    group.add_argument("--output_dir",
                       metavar="OUTPUT_DIR",
                       type=str,
                       default="",
                       help="Directory to write output files.")
    group.add_argument("--output_filepaths",
                       nargs="+",
                       type=str,
                       help="List of full paths to output nowcast files, in "
                       "order of increasing lead time.")

    optflw = parser.add_argument_group('Advect using files containing the x '
                                       ' and y components of the velocity')
    optflw.add_argument("--eastward_advection_filepath",
                        type=str,
                        help="Path"
                        " to input file containing Eastward advection "
                        "velocities.")
    optflw.add_argument("--northward_advection_filepath",
                        type=str,
                        help="Path"
                        " to input file containing Northward advection "
                        "velocities.")

    speed = parser.add_argument_group('Advect using files containing speed and'
                                      ' direction')
    speed.add_argument("--advection_speed_filepath",
                       type=str,
                       help="Path"
                       " to input file containing advection speeds,"
                       " usually wind speeds, on multiple pressure levels.")
    speed.add_argument("--advection_direction_filepath",
                       type=str,
                       help="Path to input file containing the directions from"
                       " which advection speeds are coming (180 degrees from"
                       " the direction in which the speed is directed). The"
                       " directions should be on the same grid as the input"
                       " speeds, including the same vertical levels.")
    speed.add_argument("--pressure_level",
                       type=int,
                       default=75000,
                       help="The"
                       " pressure level in Pa to extract from the multi-level"
                       " advection_speed and advection_direction files. The"
                       " velocities at this level are used for advection.")
    parser.add_argument("--orographic_enhancement_filepaths",
                        nargs="+",
                        type=str,
                        default=None,
                        help="List or wildcarded "
                        "file specification to the input orographic "
                        "enhancement files. Orographic enhancement files are "
                        "compulsory for precipitation fields.")
    parser.add_argument("--json_file",
                        metavar="JSON_FILE",
                        default=None,
                        help="Filename for the json file containing "
                        "required changes to the metadata. Information "
                        "describing the intended contents of the json file "
                        "is available in "
                        "improver.utilities.cube_metadata.amend_metadata."
                        "Every output cube will have the metadata_dict "
                        "applied. Defaults to None.",
                        type=str)
    parser.add_argument("--max_lead_time",
                        type=int,
                        default=360,
                        help="Maximum lead time required (mins).")
    parser.add_argument("--lead_time_interval",
                        type=int,
                        default=15,
                        help="Interval between required lead times (mins).")
    args = parser.parse_args(args=argv)

    upath, vpath = (args.eastward_advection_filepath,
                    args.northward_advection_filepath)
    spath, dpath = (args.advection_speed_filepath,
                    args.advection_direction_filepath)

    # load files and initialise advection plugin
    input_cube = load_cube(args.input_filepath)
    if (upath and vpath) and not (spath or dpath):
        ucube = load_cube(upath)
        vcube = load_cube(vpath)
    elif (spath and dpath) and not (upath or vpath):
        level_constraint = Constraint(pressure=args.pressure_level)
        try:
            scube = load_cube(spath, constraints=level_constraint)
            dcube = load_cube(dpath, constraints=level_constraint)
        except ValueError as err:
            raise ValueError(
                '{} Unable to extract specified pressure level from given '
                'speed and direction files.'.format(err))

        ucube, vcube = ResolveWindComponents().process(scube, dcube)
    else:
        raise ValueError('Cannot mix advection component velocities with speed'
                         ' and direction')

    oe_cube = None
    if args.orographic_enhancement_filepaths:
        oe_cube = load_cube(args.orographic_enhancement_filepaths)

    metadata_dict = None
    if args.json_file:
        # Load JSON file for metadata amendments.
        with open(args.json_file, 'r') as input_file:
            metadata_dict = json.load(input_file)

    # generate list of lead times in minutes
    lead_times = np.arange(0, args.max_lead_time + 1, args.lead_time_interval)

    if args.output_filepaths:
        if len(args.output_filepaths) != len(lead_times):
            raise ValueError("Require exactly one output file name for each "
                             "forecast lead time")

    forecast_plugin = CreateExtrapolationForecast(
        input_cube,
        ucube,
        vcube,
        orographic_enhancement_cube=oe_cube,
        metadata_dict=metadata_dict)
    # extrapolate input data to required lead times
    for i, lead_time in enumerate(lead_times):
        forecast_cube = forecast_plugin.extrapolate(leadtime_minutes=lead_time)

        # save to a suitably-named output file
        if args.output_filepaths:
            file_name = args.output_filepaths[i]
        else:
            file_name = os.path.join(args.output_dir,
                                     generate_file_name(forecast_cube))
        save_netcdf(forecast_cube, file_name)
 def setUp(self):
     """Set up a target cube with OSGB projection"""
     wind_angle = np.zeros((3, 5), dtype=np.float32)
     self.directions = set_up_cube(wind_angle, "wind_to_direction", "degrees")
     self.plugin = ResolveWindComponents()
class Test_process(IrisTest):
    """Tests the process method"""

    def setUp(self):
        """Create dummy cubes for tests"""
        self.plugin = ResolveWindComponents()
        wind_speed_data = np.array(
            [[6, 5, 4, 3], [8, 6, 4, 4], [12, 8, 6, 5]], dtype=np.float32
        )
        self.wind_speed_cube = set_up_cube(wind_speed_data, "wind_speed", "knots")

        wind_direction_data = np.array(
            [[138, 142, 141, 141], [141, 143, 140, 142], [142, 146, 141, 142]],
            dtype=np.float32,
        )
        self.wind_direction_cube = set_up_cube(
            wind_direction_data, "wind_to_direction", "degrees"
        )

        self.expected_u = np.array(
            [
                [3.804214, 2.917800, 2.410297, 1.822455],
                [4.711193, 3.395639, 2.454748, 2.365005],
                [6.844465, 4.144803, 3.580219, 2.943424],
            ],
            dtype=np.float32,
        )

        self.expected_v = np.array(
            [
                [-4.639823, -4.060351, -3.1922507, -2.382994],
                [-6.465651, -4.946681, -3.1581972, -3.225949],
                [-9.856638, -6.842559, -4.8147717, -4.041813],
            ],
            dtype=np.float32,
        )

    def test_basic(self):
        """Test plugin creates two output cubes with the correct metadata"""
        ucube, vcube = self.plugin.process(
            self.wind_speed_cube, self.wind_direction_cube
        )
        for cube in ucube, vcube:
            self.assertIsInstance(cube, iris.cube.Cube)
            self.assertEqual(cube.units, self.wind_speed_cube.units)
        self.assertEqual(ucube.name(), "grid_eastward_wind")
        self.assertEqual(vcube.name(), "grid_northward_wind")

    def test_values(self):
        """Test plugin generates expected wind values"""
        ucube, vcube = self.plugin.process(
            self.wind_speed_cube, self.wind_direction_cube
        )
        self.assertArrayAlmostEqual(ucube.data, self.expected_u, decimal=5)
        self.assertArrayAlmostEqual(vcube.data, self.expected_v, decimal=5)

    def test_coordinate_value_mismatch(self):
        """Test an error is raised if coordinate values are different for wind
        speed and direction cubes"""
        self.wind_direction_cube.coord(axis="y").convert_units("km")
        msg = "Wind speed and direction cubes have unmatched coordinates"
        with self.assertRaisesRegex(ValueError, msg):
            _, _ = self.plugin.process(self.wind_speed_cube, self.wind_direction_cube)

    def test_projection_mismatch(self):
        """Test an error is raised if coordinate names are different for wind
        speed and direction cubes"""
        self.wind_speed_cube.coord(axis="x").rename("longitude")
        self.wind_speed_cube.coord(axis="y").rename("latitude")
        msg = "Wind speed and direction cubes have unmatched coordinates"
        with self.assertRaisesRegex(ValueError, msg):
            _, _ = self.plugin.process(self.wind_speed_cube, self.wind_direction_cube)

    def test_height_levels(self):
        """Test a cube on more than one height level is correctly processed"""
        wind_speed_3d = add_new_dimension(self.wind_speed_cube, 3, "height", "km")
        wind_direction_3d = add_new_dimension(
            self.wind_direction_cube, 3, "height", "km"
        )
        ucube, vcube = self.plugin.process(wind_speed_3d, wind_direction_3d)
        self.assertSequenceEqual(ucube.shape, (3, 3, 4))
        self.assertArrayAlmostEqual(ucube[1].data, self.expected_u, decimal=5)
        self.assertArrayAlmostEqual(vcube[2].data, self.expected_v, decimal=5)

    def test_wind_from_direction(self):
        """Test correct behaviour when wind direction is 'from' not 'to'.
        We do not get perfect direction inversion to the 7th decimal place here
        because we ignore imprecision in the iris rotate_winds calcuation near
        the corners of the domain, and regrid the available data linearly to
        fill the gap.  The output wind speeds (in m s-1) compare equal to the
        5th decimal place.
        """
        expected_u = -1.0 * self.expected_u
        expected_v = -1.0 * self.expected_v
        self.wind_direction_cube.rename("wind_from_direction")
        ucube, vcube = self.plugin.process(
            self.wind_speed_cube, self.wind_direction_cube
        )
        self.assertArrayAllClose(ucube.data, expected_u, atol=1e-5)
        self.assertArrayAllClose(vcube.data, expected_v, atol=1e-5)