def test_basic(self): """Test method returns an array with correct data""" plugin = FallingSnowLevel() expected = np.array([[10.0, 7.5], [25.0, 20.5]]) result = plugin.find_falling_level(self.wb_int_data, self.orog_data, self.height_points) self.assertIsInstance(result, np.ndarray) self.assertArrayEqual(result, expected)
def test_land_points(self): """Test it returns arrays of zeros if points are land.""" plugin = FallingSnowLevel() sea_points = np.ones((3, 3)) * False gradients, intercepts = plugin.linear_wet_bulb_fit( self.wet_bulb_temperature, self.heights, sea_points) self.assertArrayAlmostEqual(np.zeros((3, 3)), gradients) self.assertArrayAlmostEqual(np.zeros((3, 3)), intercepts)
def test_basic(self): """Test it fills in the points it's meant to.""" plugin = FallingSnowLevel() plugin.fill_in_sea_points(self.snow_falling_level, self.land_sea, self.max_wb_integral, self.wet_bulb_temperature, self.heights) self.assertArrayAlmostEqual(self.snow_falling_level.data, self.expected_snow_falling_level)
def test_outside_range(self): """Test method returns an nan if data outside range""" plugin = FallingSnowLevel() wb_int_data = self.wb_int_data wb_int_data[2, 1, 1] = 70.0 result = plugin.find_falling_level(wb_int_data, self.orog_data, self.height_points) self.assertTrue(np.isnan(result[1, 1]))
def test_basic(self): """Test we find the correct gradient and intercepts for simple case""" plugin = FallingSnowLevel() gradients, intercepts = plugin.linear_wet_bulb_fit( self.wet_bulb_temperature, self.heights, self.sea_points) self.assertArrayAlmostEqual(self.expected_gradients, gradients) self.assertArrayAlmostEqual(self.expected_intercepts, intercepts)
def test_no_sea(self): """Test it only fills in sea points, and ignores a land point""" plugin = FallingSnowLevel() expected = np.ones((3, 3)) * np.nan land_sea = np.ones((3, 3)) plugin.fill_in_sea_points(self.snow_falling_level, land_sea, self.max_wb_integral, self.wet_bulb_temperature, self.heights) self.assertArrayAlmostEqual(self.snow_falling_level.data, expected)
def test_basic(self): """Test we fill in the correct snow falling levels for a simple case""" plugin = FallingSnowLevel() plugin.find_extrapolated_falling_level( self.max_wb_integral, self.gradients, self.intercepts, self.snow_falling_level, self.sea_points) self.assertArrayAlmostEqual(self.expected_snow_falling_level, self.snow_falling_level)
def setUp(self): """ Set up arrays for testing.""" self.snow_level_data = np.array([[1.0, 1.0, 2.0], [1.0, np.nan, 2.0], [1.0, 2.0, 2.0]]) self.orog_data = np.array([[6.0, 6.0, 6.0], [6.0, 7.0, 6.0], [6.0, 6.0, 6.0]]) self.max_in_nbhood_orog = np.array([[7.0, 7.0, 7.0], [7.0, 7.0, 7.0], [7.0, 7.0, 7.0]]) self.plugin = FallingSnowLevel()
def test_basic(self): """Test fills in missing data with orography + highest height""" plugin = FallingSnowLevel() self.highest_wb_int[1, 1] = 100.0 expected = np.array([[1.0, 1.0, 2.0], [1.0, 301.0, 2.0], [1.0, 2.0, 2.0]]) plugin.fill_in_high_snow_falling_levels(self.snow_level_data, self.orog, self.highest_wb_int, self.highest_height) self.assertArrayEqual(self.snow_level_data, expected)
def test_basic(self): """Test that process returns a cube with the right name and units.""" result = FallingSnowLevel().process( self.temperature_cube, self.relative_humidity_cube, self.pressure_cube, self.orog) expected = np.ones((2, 3, 3)) * 66.88732723 self.assertIsInstance(result, iris.cube.Cube) self.assertEqual(result.name(), "falling_snow_level_asl") self.assertEqual(result.units, Unit('m')) self.assertArrayAlmostEqual(result.data, expected)
def test_gradients_zero(self): """Test we do nothing if all gradients are zero""" plugin = FallingSnowLevel() gradients = np.zeros((3, 3)) plugin.find_extrapolated_falling_level( self.max_wb_integral, gradients, self.intercepts, self.snow_falling_level, self.sea_points) expected_snow_falling_level = np.ones((3, 3))*np.nan self.assertArrayAlmostEqual(expected_snow_falling_level, self.snow_falling_level)
def test_no_fill_if_conditions_not_met(self): """Test it doesn't fill in NaN if the heighest wet bulb integral value is less than the threshold.""" plugin = FallingSnowLevel() expected = np.array([[1.0, 1.0, 2.0], [1.0, np.nan, 2.0], [1.0, 2.0, 2.0]]) plugin.fill_in_high_snow_falling_levels(self.snow_level_data, self.orog, self.highest_wb_int, self.highest_height) self.assertArrayEqual(self.snow_level_data, expected)
def test_basic(self): """Test method returns an array with correct data""" plugin = FallingSnowLevel() expected = np.array([[1.0, 1.0, 2.0], [1.0, 1.5, 2.0], [1.0, 2.0, 2.0]]) result = plugin.fill_in_missing_data(self.snow_level_data, self.orog, self.highest_wb_int, self.highest_height) self.assertIsInstance(result, np.ndarray) self.assertArrayEqual(result, expected)
def test_all_above_threshold(self): """Test it doesn't change points that are all above the threshold""" plugin = FallingSnowLevel() self.max_wb_integral[0, 1] = 100 self.snow_falling_level[0, 1] = 100 self.expected_snow_falling_level[0, 1] = 100 plugin.fill_in_sea_points(self.snow_falling_level, self.land_sea, self.max_wb_integral, self.wet_bulb_temperature, self.heights) self.assertArrayAlmostEqual(self.snow_falling_level.data, self.expected_snow_falling_level)
def test_freezing_points(self): """Test with integral below threshold sets snow level to missing_value where the data can not be interpolated from other points and points are not a sea-level points.""" plugin = FallingSnowLevel() expected = np.array([[-300.0, -300.0, -300.0], [1.0, 1.5, 2.0], [1.0, 2.0, -300.0]]) result = plugin.fill_in_missing_data(self.snow_data_no_interp, self.orog, self.highest_wb_int, self.highest_height) self.assertIsInstance(result, np.ndarray) self.assertArrayEqual(result, expected)
def test_freezing_sealevel_point(self): """Test sea point with integral below threshold sets snow level to 0""" plugin = FallingSnowLevel() orog = self.orog orog[1, 1] = 0.0 expected = np.array([[1.0, 1.0, 2.0], [1.0, 0.0, 2.0], [1.0, 2.0, 2.0]]) result = plugin.fill_in_missing_data(self.snow_level_data, orog, self.highest_wb_int, self.highest_height) self.assertIsInstance(result, np.ndarray) self.assertArrayEqual(result, expected)
def test_nonfreezing_points(self): """Test with integral above threshold sets snow level to highest_level plus orograpy where the data can not be interpolated from other points and points are not sea-level points.""" plugin = FallingSnowLevel() highest_wb_int = self.highest_wb_int * 100.0 expected = np.array([[301.0, 301.0, 301.0], [1.0, 1.5, 2.0], [1.0, 2.0, 301.0]]) result = plugin.fill_in_missing_data(self.snow_data_no_interp, self.orog, highest_wb_int, self.highest_height) self.assertIsInstance(result, np.ndarray) self.assertArrayEqual(result, expected)
def test_basic(self): """Test that the __repr__ returns the expected string.""" result = str(FallingSnowLevel()) msg = ('<FallingSnowLevel: ' 'precision:0.005, falling_level_threshold:90.0,' ' grid_point_radius: 2>') self.assertEqual(result, msg)
def test_data(self): """Test that the falling snow level process returns a cube containing the expected data when points at sea-level.""" expected = np.ones((2, 3, 3)) * 65.88732723 expected[:, 1, 1] = 0.0 orog = self.orog orog.data = orog.data * 0.0 result = FallingSnowLevel().process( self.temperature_cube, self.relative_humidity_cube, self.pressure_cube, self.orog) self.assertIsInstance(result, iris.cube.Cube) self.assertArrayAlmostEqual(result.data, expected)
def process(temperature, relative_humidity, pressure, orog, land_sea, precision=0.005, falling_level_threshold=90.0): """Module to calculate continuous snow falling level. Calculate the wet-bulb temperature integral by firstly calculating the wet-bulb temperature from the inputs provided and then calculating the vertical integral of the wet-bulb temperature. Find the falling_snow_level by finding the height above sea level corresponding to the falling_level_threshold in the integral data. Args: temperature (iris.cube.Cube): Cube of air temperature at heights (m) at the points for which the continuous falling snow level is being calculated. relative_humidity (iris.cube.Cube): Cube of relative humidities at heights (m) at the points for which the continuous falling snow level is being calculated. pressure (iris.cube.Cube): Cube of air pressure at heights (m) at the points for which the continuous falling snow level is being calculated. orog (iris.cube.Cube): Cube of the orography height in m of the terrain over which the continuous falling snow level is being calculated. land_sea (iris.cube.Cube): Cube containing the binary land-sea mask for the points for which the continuous falling snow level is being calculated. Land points are set to 1, sea points are set to 0. precision (float): Precision to which the wet-bulb temperature is required: This is used by the Newton iteration. Default is 0.005. falling_level_threshold (float): Cutoff threshold for the wet-bulb integral used to calculate the falling snow level. This threshold indicates the level at which falling snow is deemed to have melted to become rain. Default is 90.0. Returns: iris.cube.Cube: Processed Cube of falling snow level above sea level. """ result = FallingSnowLevel( precision=precision, falling_level_threshold=falling_level_threshold).process( temperature, relative_humidity, pressure, orog, land_sea) return result
def test_data(self): """Test that the falling snow level process returns a cube containing the expected data when points at sea-level.""" expected = np.ones((2, 3, 3), dtype=np.float32) * 65.88566 orog = self.orog orog.data = orog.data * 0.0 orog.data[1, 1] = 100.0 land_sea = self.land_sea land_sea = land_sea * 0.0 result = FallingSnowLevel().process(self.temperature_cube, self.relative_humidity_cube, self.pressure_cube, orog, land_sea) self.assertIsInstance(result, iris.cube.Cube) self.assertArrayAlmostEqual(result.data, expected)
def test_basic(self): """Test the function does what it's meant to in a simple case.""" plugin = FallingSnowLevel(grid_point_radius=1) result = plugin.find_max_in_nbhood_orography(self.cube) self.assertArrayAlmostEqual(result.data, self.expected_data)
def main(argv=None): """Load in arguments and get going.""" parser = ArgParser( description="Calculate the continuous falling snow level ") parser.add_argument("temperature", metavar="TEMPERATURE", help="Path to a NetCDF file of air temperatures at" " heights (m) at the points for which the continuous " "falling snow level is being calculated.") parser.add_argument("relative_humidity", metavar="RELATIVE_HUMIDITY", help="Path to a NetCDF file of relative_humidities at" " heights (m) at the points for which the continuous " "falling snow level is being calculated.") parser.add_argument("pressure", metavar="PRESSURE", help="Path to a NetCDF file of air pressures at" " heights (m) at the points for which the continuous " "falling snow level is being calculated.") parser.add_argument("orography", metavar="OROGRAPHY", help="Path to a NetCDF file containing " "the orography height in m of the terrain " "over which the continuous falling snow level is " "being calculated.") parser.add_argument("land_sea_mask", metavar="LAND_SEA_MASK", help="Path to a NetCDF file containing " "the binary land-sea mask for the points " "for which the continuous falling snow level is " "being calculated. Land points are set to 1, sea " "points are set to 0.") parser.add_argument("output_filepath", metavar="OUTPUT_FILE", help="The output path for the processed NetCDF") parser.add_argument("--precision", metavar="NEWTON_PRECISION", default=0.005, type=float, help="Precision to which the wet bulb temperature " "is required: This is used by the Newton iteration " "default value is 0.005") parser.add_argument("--falling_level_threshold", metavar="FALLING_LEVEL_THRESHOLD", default=90.0, type=float, help=("Cutoff threshold for the wet-bulb integral used" " to calculate the falling snow level. This " "threshold indicates the level at which falling " "snow is deemed to have melted to become rain. " "The default value is 90.0, an empirically " "derived value.")) args = parser.parse_args(args=argv) temperature = load_cube(args.temperature, no_lazy_load=True) relative_humidity = load_cube(args.relative_humidity, no_lazy_load=True) pressure = load_cube(args.pressure, no_lazy_load=True) orog = load_cube(args.orography, no_lazy_load=True) land_sea = load_cube(args.land_sea_mask, no_lazy_load=True) result = FallingSnowLevel( precision=args.precision, falling_level_threshold=args.falling_level_threshold).process( temperature, relative_humidity, pressure, orog, land_sea) save_netcdf(result, args.output_filepath)
class Test_fill_in_by_horizontal_interpolation(IrisTest): """Test the fill_in_by_horizontal_interpolation method""" def setUp(self): """ Set up arrays for testing.""" self.snow_level_data = np.array([[1.0, 1.0, 2.0], [1.0, np.nan, 2.0], [1.0, 2.0, 2.0]]) self.orog_data = np.array([[6.0, 6.0, 6.0], [6.0, 7.0, 6.0], [6.0, 6.0, 6.0]]) self.max_in_nbhood_orog = np.array([[7.0, 7.0, 7.0], [7.0, 7.0, 7.0], [7.0, 7.0, 7.0]]) self.plugin = FallingSnowLevel() def test_basic(self): """Test when all the points around the missing data are the same.""" snow_level_data = np.ones((3, 3)) snow_level_data[1, 1] = np.nan expected = np.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_different_data(self): """Test when the points around the missing data have different values.""" expected = np.array([[1.0, 1.0, 2.0], [1.0, 1.5, 2.0], [1.0, 2.0, 2.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( self.snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_lots_missing(self): """Test when there's an extra missing value at the corner of the grid. This point can't be filled in by linear interpolation, but is instead filled by nearest neighbour extrapolation.""" self.snow_level_data[2, 2] = np.nan expected = np.array([[1.0, 1.0, 2.0], [1.0, 1.5, 2.0], [1.0, 2.0, 2.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( self.snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_all_above_max_orography(self): """Test that nothing is filled in if all the snow falling levels are above the maximum orography""" max_in_nbhood_orog = np.zeros((3, 3)) orography = np.zeros((3, 3)) expected = np.array([[1.0, 1.0, 2.0], [1.0, np.nan, 2.0], [1.0, 2.0, 2.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( self.snow_level_data, max_in_nbhood_orog, orography) self.assertArrayEqual(snow_level_updated, expected) def test_set_to_orography(self): """Test when the linear interpolation gives values that are higher than the orography the snow falling level is set back to the orography""" snow_falling_level = np.array([[10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0]]) orography = np.array([[0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0]]) max_in_nbhood_orog = np.ones((5, 5)) * 30.0 expected = np.array([[10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( snow_falling_level, max_in_nbhood_orog, orography) self.assertArrayEqual(snow_level_updated, expected)
class Test_fill_in_by_horizontal_interpolation(IrisTest): """Test the fill_in_by_horizontal_interpolation method""" def setUp(self): """ Set up arrays for testing.""" self.snow_level_data = np.array([[1.0, 1.0, 2.0], [1.0, np.nan, 2.0], [1.0, 2.0, 2.0]]) self.orog_data = np.array([[6.0, 6.0, 6.0], [6.0, 7.0, 6.0], [6.0, 6.0, 6.0]]) self.max_in_nbhood_orog = np.array([[7.0, 7.0, 7.0], [7.0, 7.0, 7.0], [7.0, 7.0, 7.0]]) self.plugin = FallingSnowLevel() def test_basic(self): """Test when all the points around the missing data are the same.""" snow_level_data = np.ones((3, 3)) snow_level_data[1, 1] = np.nan expected = np.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_not_enough_points_to_fill(self): """Test when there are not enough points to fill the gaps. This raises a QhullError if there are less than 3 points available to use for the interpolation. The QhullError is different to the one raised by test_badly_arranged_valid_data""" snow_level_data = np.array([[np.nan, 1, np.nan], [np.nan, np.nan, np.nan], [np.nan, 1, np.nan]]) expected = np.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_badly_arranged_valid_data(self): """Test when there are enough points but they aren't arranged in a suitable way to allow horizontal interpolation. This raises a QhullError that we want to ignore and use nearest neighbour interpolation instead. This QhullError is different to the one raised by test_not_enough_points_to_fill.""" snow_level_data = np.array([[np.nan, 1, np.nan], [np.nan, 1, np.nan], [np.nan, 1, np.nan]]) expected = np.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.0], [1.0, 1.0, 1.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_different_data(self): """Test when the points around the missing data have different values.""" expected = np.array([[1.0, 1.0, 2.0], [1.0, 1.5, 2.0], [1.0, 2.0, 2.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( self.snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_lots_missing(self): """Test when there's an extra missing value at the corner of the grid. This point can't be filled in by linear interpolation, but is instead filled by nearest neighbour extrapolation.""" self.snow_level_data[2, 2] = np.nan expected = np.array([[1.0, 1.0, 2.0], [1.0, 1.5, 2.0], [1.0, 2.0, 2.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( self.snow_level_data, self.max_in_nbhood_orog, self.orog_data) self.assertArrayEqual(snow_level_updated, expected) def test_all_above_max_orography(self): """Test that nothing is filled in if all the snow falling levels are above the maximum orography""" max_in_nbhood_orog = np.zeros((3, 3)) orography = np.zeros((3, 3)) expected = np.array([[1.0, 1.0, 2.0], [1.0, np.nan, 2.0], [1.0, 2.0, 2.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( self.snow_level_data, max_in_nbhood_orog, orography) self.assertArrayEqual(snow_level_updated, expected) def test_set_to_orography(self): """Test when the linear interpolation gives values that are higher than the orography the snow falling level is set back to the orography""" snow_falling_level = np.array([[10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0], [10.0, np.nan, np.nan, np.nan, 20.0]]) orography = np.array([[0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0], [0.0, 30.0, 12.0, 30.0, 0.0]]) max_in_nbhood_orog = np.ones((5, 5)) * 30.0 expected = np.array([[10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0], [10.0, 12.5, 12.0, 17.5, 20.0]]) snow_level_updated = self.plugin.fill_in_by_horizontal_interpolation( snow_falling_level, max_in_nbhood_orog, orography) self.assertArrayEqual(snow_level_updated, expected)