def setUp(self): """Set up and populate a plugin instance""" self.plugin = OrographicEnhancement() x_coord = DimCoord(np.arange(5), "projection_x_coordinate", units="km") y_coord = DimCoord(np.arange(5), "projection_y_coordinate", units="km") # this is neighbourhood-processed as part of mask generation topography_data = np.array([ [0.0, 10.0, 20.0, 50.0, 100.0], [10.0, 20.0, 50.0, 100.0, 200.0], [25.0, 60.0, 80.0, 160.0, 220.0], [50.0, 80.0, 100.0, 200.0, 250.0], [50.0, 80.0, 100.0, 200.0, 250.0], ]) self.plugin.topography = iris.cube.Cube( topography_data, long_name="topography", units="m", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) humidity_data = np.full((5, 5), 0.9) humidity_data[1, 3] = 0.5 self.plugin.humidity = iris.cube.Cube( humidity_data, long_name="relhumidity", units="1", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) self.plugin.vgradz = np.full((5, 5), 0.01) self.plugin.vgradz[3:, :] = 0.0
def setUp(self): """Set up and populate a plugin instance""" x_coord = DimCoord(np.arange(3), 'projection_x_coordinate', units='km') y_coord = DimCoord(np.arange(3), 'projection_y_coordinate', units='km') temperature = np.array([[277.1, 278.2, 277.7], [278.6, 278.4, 278.9], [278.9, 279.0, 279.6]]) humidity = np.array([[0.74, 0.85, 0.94], [0.81, 0.82, 0.91], [0.86, 0.93, 0.97]]) self.plugin = OrographicEnhancement() self.plugin.temperature = iris.cube.Cube( temperature, long_name="temperature", units="kelvin", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)]) self.plugin.humidity = iris.cube.Cube( humidity, long_name="relhumidity", units="1", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)]) self.plugin.svp = np.array([[813.6, 878.0, 848.3], [903.2, 890.5, 922.2], [922.1, 928.4, 967.9]]) self.plugin.vgradz = np.array([[0.02, 0.08, 0.2], [-0.06, 0.12, 0.22], [0.08, 0.16, 0.23]]) topography_data = np.full((3, 3), 50., dtype=np.float32) self.plugin.topography = iris.cube.Cube( topography_data, long_name="topography", units="m", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)])
def setUp(self): """Set up a plugin with wind components""" x_coord = DimCoord(3.0 * np.arange(5), "projection_x_coordinate", units="km") y_coord = DimCoord(3.0 * np.arange(5), "projection_y_coordinate", units="km") uwind = np.full((5, 5), 20.0, dtype=np.float32) vwind = np.full((5, 5), 12.0, dtype=np.float32) self.plugin = OrographicEnhancement() self.plugin.uwind = iris.cube.Cube( uwind, long_name="grid_eastward_wind", units="m s-1", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) self.plugin.vwind = iris.cube.Cube( vwind, long_name="grid_northward_wind", units="m s-1", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) self.plugin.grid_spacing_km = 3.0 self.point_orogenh = np.array([ [4.1, 4.6, 5.6, 6.8, 5.5], [4.4, 4.6, 5.8, 6.2, 5.5], [5.2, 3.0, 3.4, 5.1, 3.3], [0.6, 2.0, 1.8, 4.2, 2.5], [0.0, 0.0, 0.2, 3.2, 1.8], ])
class Test__locate_source_points(IrisTest): """Test the _locate_source_points method""" def setUp(self): """Define input matrices and plugin""" self.wind_speed = np.ones((3, 4), dtype=np.float32) self.sin_wind_dir = np.full((3, 4), 0.4, dtype=np.float32) self.cos_wind_dir = np.full((3, 4), np.sqrt(0.84), dtype=np.float32) self.plugin = OrographicEnhancement() self.plugin.grid_spacing_km = 3.0 def test_basic(self): """Test location of source points""" distance = self.plugin._get_point_distances(self.wind_speed, self.cos_wind_dir) xsrc, ysrc = self.plugin._locate_source_points(self.wind_speed, distance, self.sin_wind_dir, self.cos_wind_dir) expected_xsrc = np.array([ [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]], [[0, 1, 2, 3], [0, 1, 2, 3], [0, 1, 2, 3]], [[0, 0, 1, 2], [0, 0, 1, 2], [0, 0, 1, 2]], [[0, 0, 1, 2], [0, 0, 1, 2], [0, 0, 1, 2]], ]) expected_ysrc = np.array([ [[0, 0, 0, 0], [1, 1, 1, 1], [2, 2, 2, 2]], [[0, 0, 0, 0], [0, 0, 0, 0], [1, 1, 1, 1]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], [[0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0]], ]) self.assertArrayEqual(xsrc, expected_xsrc) self.assertArrayEqual(ysrc, expected_ysrc)
def setUp(self): """Define input matrices and plugin""" self.wind_speed = np.ones((3, 4), dtype=np.float32) self.sin_wind_dir = np.full((3, 4), 0.4, dtype=np.float32) self.cos_wind_dir = np.full((3, 4), np.sqrt(0.84), dtype=np.float32) self.plugin = OrographicEnhancement() self.plugin.grid_spacing_km = 3.0
def setUp(self): """Define input matrices and plugin""" self.wind_speed = np.ones((3, 4), dtype=np.float32) sin_wind_dir = np.linspace(0, 1, 12).reshape(3, 4) cos_wind_dir = np.sqrt(1. - np.square(sin_wind_dir)) self.max_sin_cos = np.where(abs(sin_wind_dir) > abs(cos_wind_dir), abs(sin_wind_dir), abs(cos_wind_dir)) self.plugin = OrographicEnhancement() self.plugin.grid_spacing_km = 3.
class Test__point_orogenh(IrisTest): """Test the _point_orogenh method""" def setUp(self): """Set up and populate a plugin instance""" x_coord = DimCoord(np.arange(3), 'projection_x_coordinate', units='km') y_coord = DimCoord(np.arange(3), 'projection_y_coordinate', units='km') temperature = np.array([[277.1, 278.2, 277.7], [278.6, 278.4, 278.9], [278.9, 279.0, 279.6]]) humidity = np.array([[0.74, 0.85, 0.94], [0.81, 0.82, 0.91], [0.86, 0.93, 0.97]]) svp = np.array([[813.6, 878.0, 848.3], [903.2, 890.5, 922.2], [922.1, 928.4, 967.9]]) self.plugin = OrographicEnhancement() self.plugin.temperature = iris.cube.Cube(temperature, long_name="temperature", units="kelvin", dim_coords_and_dims=[ (y_coord, 0), (x_coord, 1) ]) self.plugin.humidity = iris.cube.Cube(humidity, long_name="relhumidity", units="1", dim_coords_and_dims=[ (y_coord, 0), (x_coord, 1) ]) self.plugin.svp = iris.cube.Cube( svp, long_name="saturation_vapour_pressure", units="Pa", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)]) self.plugin.vgradz = np.array([[0.02, 0.08, 0.2], [-0.06, 0.12, 0.22], [0.08, 0.16, 0.23]]) topography_data = np.full((3, 3), 50., dtype=np.float32) self.plugin.topography = iris.cube.Cube(topography_data, long_name="topography", units="m", dim_coords_and_dims=[ (y_coord, 0), (x_coord, 1) ]) def test_basic(self): """Test output is an array""" result = self.plugin._point_orogenh() self.assertIsInstance(result, np.ndarray) def test_values(self): """Test output values are as expected""" expected_values = np.array([[0., 1.67372072, 4.47886658], [0., 2.45468903, 5.1627059], [1.77400422, 3.86162901, 6.02323198]]) result = self.plugin._point_orogenh() self.assertArrayAlmostEqual(result, expected_values)
class Test__add_upstream_component(IrisTest): """Test the _add_upstream_component method""" def setUp(self): """Set up a plugin with wind components""" x_coord = DimCoord(3.0 * np.arange(5), "projection_x_coordinate", units="km") y_coord = DimCoord(3.0 * np.arange(5), "projection_y_coordinate", units="km") uwind = np.full((5, 5), 20.0, dtype=np.float32) vwind = np.full((5, 5), 12.0, dtype=np.float32) self.plugin = OrographicEnhancement() self.plugin.uwind = iris.cube.Cube( uwind, long_name="grid_eastward_wind", units="m s-1", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) self.plugin.vwind = iris.cube.Cube( vwind, long_name="grid_northward_wind", units="m s-1", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) self.plugin.grid_spacing_km = 3.0 self.point_orogenh = np.array([ [4.1, 4.6, 5.6, 6.8, 5.5], [4.4, 4.6, 5.8, 6.2, 5.5], [5.2, 3.0, 3.4, 5.1, 3.3], [0.6, 2.0, 1.8, 4.2, 2.5], [0.0, 0.0, 0.2, 3.2, 1.8], ]) def test_basic(self): """Test output is an array""" result = self.plugin._add_upstream_component(self.point_orogenh) self.assertIsInstance(result, np.ndarray) def test_values(self): """Test output values are sensible""" expected_values = np.array([ [0.953865, 1.039876, 1.241070, 1.506976, 1.355637], [1.005472, 1.039876, 1.275474, 1.403762, 1.355637], [1.161275, 0.782825, 0.863303, 1.226206, 0.942638], [0.418468, 0.659300, 0.496544, 0.927728, 0.735382], [0.036423, 0.036423, 0.152506, 0.660092, 0.558801], ]) result = self.plugin._add_upstream_component(self.point_orogenh) self.assertArrayAlmostEqual(result, expected_values)
def setUp(self): """Set up input cubes""" temperature = np.arange(6).reshape(2, 3) self.temperature_cube = set_up_variable_cube(temperature) orography = np.array([[20., 30., 40., 30., 25., 25.], [30., 50., 80., 60., 50., 45.], [50., 65., 90., 70., 60., 50.], [45., 60., 85., 65., 55., 45.]]) orography_cube = set_up_orography_cube(orography) self.plugin = OrographicEnhancement() self.plugin.topography = sort_coord_in_cube( orography_cube, orography_cube.coord(axis='y'))
def setUp(self): """Set up an input cube""" self.plugin = OrographicEnhancement() data = np.array([[200.0, 450.0, 850.0], [320.0, 500.0, 1000.0], [230.0, 600.0, 900.0]]) x_coord = DimCoord(np.arange(3), "projection_x_coordinate", units="km") y_coord = DimCoord(np.arange(3), "projection_y_coordinate", units="km") self.plugin.topography = iris.cube.Cube( data, long_name="topography", units="m", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], )
class Test__generate_mask(IrisTest): """Test the _generate_mask method""" def setUp(self): """Set up and populate a plugin instance""" self.plugin = OrographicEnhancement() x_coord = DimCoord(np.arange(5), "projection_x_coordinate", units="km") y_coord = DimCoord(np.arange(5), "projection_y_coordinate", units="km") # this is neighbourhood-processed as part of mask generation topography_data = np.array([ [0.0, 10.0, 20.0, 50.0, 100.0], [10.0, 20.0, 50.0, 100.0, 200.0], [25.0, 60.0, 80.0, 160.0, 220.0], [50.0, 80.0, 100.0, 200.0, 250.0], [50.0, 80.0, 100.0, 200.0, 250.0], ]) self.plugin.topography = iris.cube.Cube( topography_data, long_name="topography", units="m", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) humidity_data = np.full((5, 5), 0.9) humidity_data[1, 3] = 0.5 self.plugin.humidity = iris.cube.Cube( humidity_data, long_name="relhumidity", units="1", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) self.plugin.vgradz = np.full((5, 5), 0.01) self.plugin.vgradz[3:, :] = 0.0 def test_basic(self): """Test output is array""" result = self.plugin._generate_mask() self.assertIsInstance(result, np.ndarray) def test_values(self): """Test output mask is correct""" expected_output = np.full((5, 5), False, dtype=bool) expected_output[0, :2] = True # orography too low expected_output[1, 3] = True # humidity too low expected_output[3:, :] = True # vgradz too low result = self.plugin._generate_mask() self.assertArrayEqual(result, expected_output)
def process(temperature, humidity, pressure, wind_speed, wind_dir, orography): """Calculate orograhpic enhancement Uses the ResolveWindComponents() and OrographicEnhancement() plugins. Outputs data on the high resolution orography grid. Args: temperature (iris.cube.Cube): Cube containing temperature at top of boundary layer. humidity (iris.cube.Cube): Cube containing relative humidity at top of boundary layer. pressure (iris.cube.Cube): Cube containing pressure at top of boundary layer. wind_speed (iris.cube.Cube): Cube containing wind speed values. wind_dir (iris.cube.Cube): Cube containing wind direction values relative to true north. orography (iris.cube.Cube): Cube containing height of orography above sea level on high resolution (1 km) UKPP domain grid. Returns: iris.cube.Cube: Precipitation enhancement due to orography on the high resolution input orography grid. """ # resolve u and v wind components u_wind, v_wind = ResolveWindComponents().process(wind_speed, wind_dir) # calculate orographic enhancement return OrographicEnhancement().process(temperature, humidity, pressure, u_wind, v_wind, orography)
class Test__compute_weighted_values(IrisTest): """Test the _compute_weighted_values method""" def setUp(self): """Set up plugin and some inputs""" self.plugin = OrographicEnhancement() self.plugin.grid_spacing_km = 3.0 self.point_orogenh = np.array([ [4.1, 4.6, 5.6, 6.8, 5.5], [4.4, 4.6, 5.8, 6.2, 5.5], [5.2, 3.0, 3.4, 5.1, 3.3], [0.6, 2.0, 1.8, 4.2, 2.5], [0.0, 0.0, 0.2, 3.2, 1.8], ]) self.wind_speed = np.full((5, 5), 25.0, dtype=np.float32) sin_wind_dir = np.full((5, 5), 0.4, dtype=np.float32) cos_wind_dir = np.full((5, 5), np.sqrt(0.84), dtype=np.float32) self.distance = self.plugin._get_point_distances( self.wind_speed, cos_wind_dir) self.xsrc, self.ysrc = self.plugin._locate_source_points( self.wind_speed, self.distance, sin_wind_dir, cos_wind_dir) def test_basic(self): """Test output is two arrays""" orogenh, weights = self.plugin._compute_weighted_values( self.point_orogenh, self.xsrc, self.ysrc, self.distance, self.wind_speed) self.assertIsInstance(orogenh, np.ndarray) self.assertIsInstance(weights, np.ndarray) def test_values(self): """Test values are as expected""" expected_orogenh = np.array([ [6.0531969, 6.7725644, 8.2301264, 9.9942646, 8.1690931], [6.3531971, 6.7725644, 8.4301271, 9.3942642, 8.1690931], [7.2848172, 5.1725645, 6.1178742, 8.0310230, 5.9690924], [3.0469213, 3.4817038, 3.4649093, 6.6558237, 4.1816435], [0.4585612, 1.0727906, 1.1036499, 5.1721582, 3.0895371], ]) expected_weights = np.full((5, 5), 1.4763895, dtype=np.float32) orogenh, weights = self.plugin._compute_weighted_values( self.point_orogenh, self.xsrc, self.ysrc, self.distance, self.wind_speed) self.assertArrayAlmostEqual(orogenh, expected_orogenh) self.assertArrayAlmostEqual(weights, expected_weights)
def setUp(self): """Set up plugin and some inputs""" self.plugin = OrographicEnhancement() self.plugin.grid_spacing_km = 3. self.point_orogenh = np.array([[4.1, 4.6, 5.6, 6.8, 5.5], [4.4, 4.6, 5.8, 6.2, 5.5], [5.2, 3.0, 3.4, 5.1, 3.3], [0.6, 2.0, 1.8, 4.2, 2.5], [0.0, 0.0, 0.2, 3.2, 1.8]]) self.wind_speed = np.full((5, 5), 25., dtype=np.float32) sin_wind_dir = np.full((5, 5), 0.4, dtype=np.float32) cos_wind_dir = np.full((5, 5), np.sqrt(0.84), dtype=np.float32) self.distance = self.plugin._get_point_distances( self.wind_speed, cos_wind_dir) self.xsrc, self.ysrc = self.plugin._locate_source_points( self.wind_speed, self.distance, sin_wind_dir, cos_wind_dir)
def process(temperature: cli.inputcube, humidity: cli.inputcube, pressure: cli.inputcube, wind_speed: cli.inputcube, wind_direction: cli.inputcube, orography: cli.inputcube, *, boundary_height: float = 1000.0, boundary_height_units='m'): """Calculate orographic enhancement Uses the ResolveWindComponents() and OrographicEnhancement() plugins. Outputs data on the high resolution orography grid. Args: temperature (iris.cube.Cube): Cube containing temperature at top of boundary layer. humidity (iris.cube.Cube): Cube containing relative humidity at top of boundary layer. pressure (iris.cube.Cube): Cube containing pressure at top of boundary layer. wind_speed (iris.cube.Cube): Cube containing wind speed values. wind_direction (iris.cube.Cube): Cube containing wind direction values relative to true north. orography (iris.cube.Cube): Cube containing height of orography above sea level on high resolution (1 km) UKPP domain grid. boundary_height (float): Model height level to extract variables for calculating orographic enhancement, as proxy for the boundary layer. boundary_height_units (str): Units of the boundary height specified for extracting model levels. Returns: iris.cube.Cube: Precipitation enhancement due to orography on the high resolution input orography grid. """ from improver.orographic_enhancement import OrographicEnhancement from improver.wind_calculations.wind_components import \ ResolveWindComponents constraint_info = (boundary_height, boundary_height_units) temperature = extract_and_check(temperature, *constraint_info) humidity = extract_and_check(humidity, *constraint_info) pressure = extract_and_check(pressure, *constraint_info) wind_speed = extract_and_check(wind_speed, *constraint_info) wind_direction = extract_and_check(wind_direction, *constraint_info) # resolve u and v wind components u_wind, v_wind = ResolveWindComponents().process(wind_speed, wind_direction) # calculate orographic enhancement return OrographicEnhancement().process(temperature, humidity, pressure, u_wind, v_wind, orography)
def setUp(self): """Set up a plugin instance, data array and cubes""" self.plugin = OrographicEnhancement() topography = set_up_orography_cube(np.zeros((3, 4), dtype=np.float32)) self.plugin.topography = sort_coord_in_cube(topography, topography.coord(axis='y')) self.temperature = set_up_variable_cube(np.full((2, 4), 280.15), units='kelvin', xo=398000.) self.temperature.attributes['institution'] = 'Met Office' self.temperature.attributes['source'] = 'Met Office Unified Model' self.temperature.attributes['mosg__grid_type'] = 'standard' self.temperature.attributes['mosg__grid_version'] = '1.2.0' self.temperature.attributes['mosg__grid_domain'] = 'uk_extended' self.temperature.attributes['mosg__model_configuration'] = 'uk_det' self.orogenh = np.array([[1.1, 1.2, 1.5, 1.4], [1.0, 1.3, 1.4, 1.6], [0.8, 0.9, 1.2, 0.9]])
class Test__get_point_distances(IrisTest): """Test the _get_point_distances function""" def setUp(self): """Define input matrices and plugin""" self.wind_speed = np.ones((3, 4), dtype=np.float32) sin_wind_dir = np.linspace(0, 1, 12).reshape(3, 4) cos_wind_dir = np.sqrt(1.0 - np.square(sin_wind_dir)) self.max_sin_cos = np.where( abs(sin_wind_dir) > abs(cos_wind_dir), abs(sin_wind_dir), abs(cos_wind_dir)) self.plugin = OrographicEnhancement() self.plugin.grid_spacing_km = 3.0 def test_basic(self): """Test the function returns an array of the expected shape""" distance = self.plugin._get_point_distances(self.wind_speed, self.max_sin_cos) self.assertIsInstance(distance, np.ndarray) self.assertSequenceEqual(distance.shape, (5, 3, 4)) def test_values_with_nans(self): """Test for expected values including nans""" slice_0 = np.zeros((3, 4), dtype=np.float32) slice_1 = np.array([ [1.0, 1.00415802, 1.01695037, 1.03940225], [1.07349002, 1.12268281, 1.1931175, 1.2963624], [1.375, 1.22222221, 1.10000002, 1.0], ]) slice_2 = 2.0 * slice_1 slice_3 = 3.0 * slice_1 slice_3[1, 3] = np.nan slice_3[2, 0] = np.nan slice_4 = np.full_like(slice_0, np.nan) slice_4[0, 0] = 4.0 slice_4[-1, -1] = 4.0 expected_data = np.array([slice_0, slice_1, slice_2, slice_3, slice_4]) distance = self.plugin._get_point_distances(self.wind_speed, self.max_sin_cos) np.testing.assert_allclose(distance, expected_data, equal_nan=True)
class Test__orography_gradients(IrisTest): """Test the _orography_gradients method""" def setUp(self): """Set up an input cube""" self.plugin = OrographicEnhancement() data = np.array([[200.0, 450.0, 850.0], [320.0, 500.0, 1000.0], [230.0, 600.0, 900.0]]) x_coord = DimCoord(np.arange(3), "projection_x_coordinate", units="km") y_coord = DimCoord(np.arange(3), "projection_y_coordinate", units="km") self.plugin.topography = iris.cube.Cube( data, long_name="topography", units="m", dim_coords_and_dims=[(y_coord, 0), (x_coord, 1)], ) def test_basic(self): """Test outputs are cubes""" gradx, grady = self.plugin._orography_gradients() self.assertIsInstance(gradx, iris.cube.Cube) self.assertIsInstance(grady, iris.cube.Cube) def test_values(self): """Test output values and units""" expected_gradx = np.array([ [0.12333333, 0.33, 0.53666667], [0.2, 0.33333333, 0.46666667], [0.27666667, 0.33666667, 0.39666667], ]) expected_grady = np.array([ [0.15833333, 0.175, 0.19166667], [0.035, 0.03833333, 0.04166667], [-0.08833333, -0.09833333, -0.10833333], ]) gradx, grady = self.plugin._orography_gradients() self.assertArrayAlmostEqual(gradx.data, expected_gradx) self.assertArrayAlmostEqual(grady.data, expected_grady) for cube in [gradx, grady]: self.assertEqual(cube.units, "1")
def test_basic(self): """Test initialisation with no arguments""" plugin = OrographicEnhancement() self.assertAlmostEqual(plugin.orog_thresh_m, 20.) self.assertAlmostEqual(plugin.rh_thresh_ratio, 0.8) self.assertAlmostEqual(plugin.vgradz_thresh_ms, 0.0005) self.assertAlmostEqual(plugin.upstream_range_of_influence_km, 15.) self.assertAlmostEqual(plugin.efficiency_factor, 0.23265) self.assertAlmostEqual(plugin.cloud_lifetime_s, 102.) none_type_attributes = [ 'topography', 'temperature', 'humidity', 'pressure', 'uwind', 'vwind', 'svp', 'vgradz', 'grid_spacing_km'] for attr in none_type_attributes: self.assertIsNone(getattr(plugin, attr))
def setUp(self): """Set up a plugin instance, data array and cubes""" self.plugin = OrographicEnhancement() topography = set_up_orography_cube(np.zeros((3, 4), dtype=np.float32)) self.plugin.topography = sort_coord_in_cube(topography, topography.coord(axis="y")) t_attributes = { "institution": "Met Office", "source": "Met Office Unified Model", "mosg__grid_type": "standard", "mosg__grid_version": "1.2.0", "mosg__grid_domain": "uk_extended", "mosg__model_configuration": "uk_det", } self.temperature = set_up_variable_cube( np.full((2, 4), 280.15, dtype=np.float32), units="kelvin", xo=398000.0, attributes=t_attributes, ) self.orogenh = np.array([[1.1, 1.2, 1.5, 1.4], [1.0, 1.3, 1.4, 1.6], [0.8, 0.9, 1.2, 0.9]])
def setUp(self): """Set up input cubes""" temperature = np.arange(6).reshape(2, 3) self.temperature = set_up_variable_cube(temperature) humidity = np.arange(0.75, 0.86, 0.02).reshape(2, 3) self.humidity = set_up_variable_cube(humidity, 'relhumidity', '1') pressure = np.arange(820, 921, 20).reshape(2, 3) self.pressure = set_up_variable_cube(pressure, 'pressure', 'hPa') uwind = np.full((2, 3), 20., dtype=np.float32) self.uwind = set_up_variable_cube(uwind, 'wind-u', 'knots') vwind = np.full((2, 3), 12., dtype=np.float32) self.vwind = set_up_variable_cube(vwind, 'wind-v', 'knots') orography = np.array([[20., 30., 40., 30., 25., 25.], [30., 50., 80., 60., 50., 45.], [50., 65., 90., 70., 60., 50.], [45., 60., 85., 65., 55., 45.]]) self.orography_cube = set_up_orography_cube(orography) self.plugin = OrographicEnhancement()
def setUp(self): """Set up input cubes""" temperature = np.arange(6).reshape(2, 3) self.temperature = set_up_variable_cube(temperature) humidity = np.arange(0.75, 0.86, 0.02).reshape(2, 3) self.humidity = set_up_variable_cube(humidity, "relhumidity", "1") pressure = np.arange(820, 921, 20).reshape(2, 3) self.pressure = set_up_variable_cube(pressure, "pressure", "hPa") uwind = np.full((2, 3), 20.0, dtype=np.float32) self.uwind = set_up_variable_cube(uwind, "wind-u", "knots") vwind = np.full((2, 3), 12.0, dtype=np.float32) self.vwind = set_up_variable_cube(vwind, "wind-v", "knots") orography = np.array([ [20.0, 30.0, 40.0, 30.0, 25.0, 25.0], [30.0, 50.0, 80.0, 60.0, 50.0, 45.0], [50.0, 65.0, 90.0, 70.0, 60.0, 50.0], [45.0, 60.0, 85.0, 65.0, 55.0, 45.0], ]) self.orography_cube = set_up_orography_cube(orography) self.plugin = OrographicEnhancement()
def process(temperature, humidity, pressure, wind_speed, wind_dir, orography): """Calculate orograhpic enhancement Uses the ResolveWindComponents() and OrographicEnhancement() plugins. Outputs data on the high resolution orography grid and regrided to the coarser resolution of the input diagnostic variables. Args: temperature (iris.cube.Cube): Cube containing temperature at top of boundary layer. humidity (iris.cube.Cube): Cube containing relative humidity at top of boundary layer. pressure (iris.cube.Cube): Cube containing pressure at top of boundary layer. wind_speed (iris.cube.Cube): Cube containing wind speed values. wind_dir (iris.cube.Cube): Cube containing wind direction values relative to true north. orography (iris.cube.Cube): Cube containing height of orography above sea level on high resolution (1 km) UKPP domain grid. Returns: (tuple): tuple containing: **orogenh_high_res** (iris.cube.Cube): Precipitation enhancement due to orography in mm/h on the UK standard grid, padded with masked up np.nans where outside the UKPP domain. **orogenh_standard** (iris.cube.Cube): Precipitation enhancement due to orography in mm/h on the 1km Transverse Mercator UKPP grid domain. """ # resolve u and v wind components u_wind, v_wind = ResolveWindComponents().process(wind_speed, wind_dir) # calculate orographic enhancement orogenh_high_res, orogenh_standard = OrographicEnhancement().process( temperature, humidity, pressure, u_wind, v_wind, orography) return orogenh_high_res, orogenh_standard
class Test__create_output_cubes(IrisTest): """Test the _create_output_cube method""" def setUp(self): """Set up a plugin instance, data array and cubes""" self.plugin = OrographicEnhancement() topography = set_up_orography_cube(np.zeros((3, 4), dtype=np.float32)) self.plugin.topography = sort_coord_in_cube(topography, topography.coord(axis='y')) self.temperature = set_up_variable_cube(np.full((2, 4), 280.15), units='kelvin', xo=398000.) self.temperature.attributes['institution'] = 'Met Office' self.temperature.attributes['source'] = 'Met Office Unified Model' self.temperature.attributes['mosg__grid_type'] = 'standard' self.temperature.attributes['mosg__grid_version'] = '1.2.0' self.temperature.attributes['mosg__grid_domain'] = 'uk_extended' self.temperature.attributes['mosg__model_configuration'] = 'uk_det' self.orogenh = np.array([[1.1, 1.2, 1.5, 1.4], [1.0, 1.3, 1.4, 1.6], [0.8, 0.9, 1.2, 0.9]]) def test_basic(self): """Test that the cube is returned with float32 coords""" output = self.plugin._create_output_cube(self.orogenh, self.temperature) self.assertIsInstance(output, iris.cube.Cube) for coord in output.coords(dim_coords=True): self.assertEqual(coord.points.dtype, 'float32') def test_values(self): """Test the cube is changed only in units (to m s-1)""" original_converted = 2.7777778e-07 * self.orogenh output = self.plugin._create_output_cube(self.orogenh, self.temperature) self.assertArrayAlmostEqual(output.data, original_converted) def test_metadata(self): """Check output metadata on cube is as expected""" hi_res_attributes = self.temperature.attributes for key, val in self.plugin.topography.attributes.items(): hi_res_attributes[key] = val output = self.plugin._create_output_cube(self.orogenh, self.temperature) for axis in ['x', 'y']: self.assertEqual(output.coord(axis=axis), self.plugin.topography.coord(axis=axis)) self.assertEqual(output.name(), 'orographic_enhancement') self.assertEqual(output.units, 'm s-1') for t_coord in ['time', 'forecast_period', 'forecast_reference_time']: self.assertEqual(output.coord(t_coord), self.temperature.coord(t_coord)) self.assertDictEqual(output.attributes, hi_res_attributes) def test_grid_metadata(self): """Test specific grid and model metadata inheritance""" output = self.plugin._create_output_cube(self.orogenh, self.temperature) for attr in [ 'mosg__grid_type', 'mosg__grid_version', 'mosg__grid_domain' ]: self.assertEqual(output.attributes[attr], self.plugin.topography.attributes[attr]) self.assertEqual( output.attributes['mosg__model_configuration'], self.temperature.attributes['mosg__model_configuration'])
class Test__create_output_cube(IrisTest): """Test the _create_output_cube method""" def setUp(self): """Set up a plugin instance, data array and cubes""" self.plugin = OrographicEnhancement() topography = set_up_orography_cube(np.zeros((3, 4), dtype=np.float32)) self.plugin.topography = sort_coord_in_cube(topography, topography.coord(axis="y")) t_attributes = { "institution": "Met Office", "source": "Met Office Unified Model", "mosg__grid_type": "standard", "mosg__grid_version": "1.2.0", "mosg__grid_domain": "uk_extended", "mosg__model_configuration": "uk_det", } self.temperature = set_up_variable_cube( np.full((2, 4), 280.15, dtype=np.float32), units="kelvin", xo=398000.0, attributes=t_attributes, ) self.orogenh = np.array([[1.1, 1.2, 1.5, 1.4], [1.0, 1.3, 1.4, 1.6], [0.8, 0.9, 1.2, 0.9]]) def test_basic(self): """Test that the cube is returned with float32 coords""" output = self.plugin._create_output_cube(self.orogenh, self.temperature) self.assertIsInstance(output, iris.cube.Cube) for coord in output.coords(dim_coords=True): self.assertEqual(coord.points.dtype, "float32") def test_values(self): """Test the cube is changed only in units (to m s-1)""" original_converted = 2.7777778e-07 * self.orogenh output = self.plugin._create_output_cube(self.orogenh, self.temperature) self.assertArrayAlmostEqual(output.data, original_converted) def test_metadata(self): """Check output metadata on cube is as expected""" expected_attributes = { "title": MANDATORY_ATTRIBUTE_DEFAULTS["title"], "source": self.temperature.attributes["source"], "institution": self.temperature.attributes["institution"], } for attr in MOSG_GRID_ATTRIBUTES: expected_attributes[attr] = self.plugin.topography.attributes[attr] output = self.plugin._create_output_cube(self.orogenh, self.temperature) for axis in ["x", "y"]: self.assertEqual(output.coord(axis=axis), self.plugin.topography.coord(axis=axis)) self.assertEqual(output.name(), "orographic_enhancement") self.assertEqual(output.units, "m s-1") for t_coord in ["time", "forecast_period", "forecast_reference_time"]: self.assertEqual(output.coord(t_coord), self.temperature.coord(t_coord)) self.assertDictEqual(output.attributes, expected_attributes)
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)
class Test__create_output_cubes(IrisTest): """Test the _create_output_cubes method""" def setUp(self): """Set up a plugin instance, data array and cubes""" self.plugin = OrographicEnhancement() topography = set_up_orography_cube(np.zeros((3, 4), dtype=np.float32)) self.plugin.topography = sort_coord_in_cube(topography, topography.coord(axis='y')) self.temperature = set_up_variable_cube(np.full((2, 4), 280.15), units='kelvin', xo=398000.) self.temperature.attributes['institution'] = 'Met Office' self.temperature.attributes['source'] = 'Met Office Unified Model' self.temperature.attributes['mosg__grid_type'] = 'standard' self.temperature.attributes['mosg__grid_version'] = '1.2.0' self.temperature.attributes['mosg__grid_domain'] = 'uk_extended' self.temperature.attributes['mosg__model_configuration'] = 'uk_det' self.orogenh = np.array([[1.1, 1.2, 1.5, 1.4], [1.0, 1.3, 1.4, 1.6], [0.8, 0.9, 1.2, 0.9]]) def test_basic(self): """Test that two cubes are returned with float32 coords""" output, regridded_output = self.plugin._create_output_cubes( self.orogenh, self.temperature) for cube in [output, regridded_output]: self.assertIsInstance(cube, iris.cube.Cube) for coord in cube.coords(dim_coords=True): self.assertEqual(coord.points.dtype, 'float32') def test_values(self): """Test first cube is unchanged and regridded output cube is masked as expected""" expected_data = np.array([[np.nan, 1.0, 1.4, np.nan], [np.nan, np.nan, np.nan, np.nan]]) expected_mask = np.where(np.isfinite(expected_data), False, True) output, regridded_output = self.plugin._create_output_cubes( self.orogenh, self.temperature) self.assertArrayAlmostEqual(output.data, self.orogenh) self.assertTrue( np.allclose(regridded_output.data.data, expected_data, equal_nan=True)) self.assertArrayEqual(regridded_output.data.mask, expected_mask) def test_metadata(self): """Check output metadata on both cubes is as expected""" hi_res_attributes = self.temperature.attributes for key, val in self.plugin.topography.attributes.items(): hi_res_attributes[key] = val tref = sort_coord_in_cube(self.temperature, self.temperature.coord(axis='y')) output, regridded_output = self.plugin._create_output_cubes( self.orogenh, self.temperature) for axis in ['x', 'y']: self.assertEqual(output.coord(axis=axis), self.plugin.topography.coord(axis=axis)) self.assertEqual(regridded_output.coord(axis=axis), tref.coord(axis=axis)) for cube in [output, regridded_output]: self.assertEqual(cube.name(), 'orographic_enhancement') self.assertEqual(cube.units, 'mm h-1') for t_coord in [ 'time', 'forecast_period', 'forecast_reference_time' ]: self.assertEqual(cube.coord(t_coord), self.temperature.coord(t_coord)) self.assertDictEqual(regridded_output.attributes, self.temperature.attributes) self.assertDictEqual(output.attributes, hi_res_attributes) def test_grid_metadata(self): """Test specific grid and model metadata inheritance""" output, regridded_output = self.plugin._create_output_cubes( self.orogenh, self.temperature) for attr in [ 'mosg__grid_type', 'mosg__grid_version', 'mosg__grid_domain' ]: self.assertEqual(regridded_output.attributes[attr], self.temperature.attributes[attr]) self.assertEqual(output.attributes[attr], self.plugin.topography.attributes[attr]) for cube in [output, regridded_output]: self.assertEqual( cube.attributes['mosg__model_configuration'], self.temperature.attributes['mosg__model_configuration'])
def test_basic(self): """Test string representation of plugin""" plugin = OrographicEnhancement() self.assertEqual(str(plugin), "<OrographicEnhancement()>")
class Test__regrid_variable(IrisTest): """Test the _regrid_variable method""" def setUp(self): """Set up input cubes""" temperature = np.arange(6).reshape(2, 3) self.temperature_cube = set_up_variable_cube(temperature) orography = np.array([ [20.0, 30.0, 40.0, 30.0, 25.0, 25.0], [30.0, 50.0, 80.0, 60.0, 50.0, 45.0], [50.0, 65.0, 90.0, 70.0, 60.0, 50.0], [45.0, 60.0, 85.0, 65.0, 55.0, 45.0], ]) orography_cube = set_up_orography_cube(orography) self.plugin = OrographicEnhancement() self.plugin.topography = sort_coord_in_cube( orography_cube, orography_cube.coord(axis="y")) def test_basic(self): """Test cube of the correct shape and type is returned""" expected_data = np.array([ [4.5, 5.0, 5.5, 6.0, 6.5, 7.0], [3.0, 3.5, 4.0, 4.5, 5.0, 5.5], [1.5, 2.0, 2.5, 3.0, 3.5, 4.0], [0.0, 0.5, 1.0, 1.5, 2.0, 2.5], ]) result = self.plugin._regrid_variable(self.temperature_cube, "degC") self.assertIsInstance(result, iris.cube.Cube) self.assertArrayAlmostEqual(result.data, expected_data) self.assertEqual(result.data.dtype, "float32") def test_axis_inversion(self): """Test axes are output in ascending order""" result = self.plugin._regrid_variable(self.temperature_cube, "degC") x_points = result.coord(axis="x").points y_points = result.coord(axis="y").points self.assertTrue(x_points[1] > x_points[0]) self.assertTrue(y_points[1] > y_points[0]) def test_unit_conversion(self): """Test units are correctly converted""" expected_data = np.array( [ [277.65, 278.15, 278.65, 279.15, 279.65, 280.15], [276.15, 276.65, 277.15, 277.65, 278.15, 278.65], [274.65, 275.15, 275.65, 276.15, 276.65, 277.15], [273.15, 273.65, 274.15, 274.65, 275.15, 275.65], ], dtype=np.float32, ) result = self.plugin._regrid_variable(self.temperature_cube, "kelvin") self.assertEqual(result.units, "kelvin") self.assertArrayAlmostEqual(result.data, expected_data) def test_null(self): """Test cube is unchanged if axes and grid are already correct""" correct_cube = self.plugin.topography.copy() result = self.plugin._regrid_variable(correct_cube, "m") self.assertArrayAlmostEqual(result.data, correct_cube.data) self.assertEqual(result.metadata, correct_cube.metadata) def test_input_unchanged(self): """Test the input cube is not modified in place""" reference_cube = self.temperature_cube.copy() _ = self.plugin._regrid_variable(self.temperature_cube, "degC") self.assertArrayAlmostEqual(self.temperature_cube.data, reference_cube.data) self.assertEqual(self.temperature_cube.metadata, reference_cube.metadata)