def process(*cubes: cli.inputcube, wxtree="high_resolution", model_id_attr: str = None): """ Processes cube for Weather symbols. Args: cubes (iris.cube.CubeList): A cubelist containing the diagnostics required for the weather symbols decision tree, these at co-incident times. wxtree (str): Weather Code tree: high_resolution or global. model_id_attr (str): Name of attribute recording source models that should be inherited by the output cube. The source models are expected as a space-separated string. Returns: iris.cube.Cube: A cube of weather symbols. """ from iris.cube import CubeList from improver.wxcode.weather_symbols import WeatherSymbols if not cubes: raise RuntimeError("Not enough input arguments. " "See help for more information.") return WeatherSymbols(wxtree=wxtree, model_id_attr=model_id_attr)(CubeList(cubes))
def test_day_night(self): """Test process returns the right values for night. """ plugin = WeatherSymbols() for i, cube in enumerate(self.cubes): self.cubes[i].coord("time").points = cube.coord("time").points + 3600 * 12 result = plugin.process(self.cubes) self.assertArrayEqual(result.data, self.expected_wxcode_night)
def test_sleet(self): """Test process returns the sleet weather code.""" plugin = WeatherSymbols() data_snow = np.zeros_like(self.cubes[0].data) data_sleet = np.ones_like(self.cubes[0].data) data_rain = np.zeros_like(self.cubes[0].data) # pylint: disable=no-member data_precip = np.maximum.reduce([data_snow, data_sleet, data_rain]) data_precipv = np.ones_like(self.cubes[0].data) data_cloud = np.ones_like(self.cubes[4].data) data_cld_low = np.ones_like(self.cubes[5].data) data_vis = np.zeros_like(self.cubes[6].data) data_lightning = np.zeros_like(self.cubes[7].data) expected = np.ones_like(self.expected_wxcode_alternate) * 18 cubes = self.cubes cubes[0].data = data_snow cubes[1].data = data_sleet cubes[2].data = data_rain cubes[3].data = data_precipv cubes[4].data = data_cloud cubes[5].data = data_cld_low cubes[6].data = data_vis cubes[7].data = data_lightning cubes[8].data = data_precip result = plugin.process(cubes) self.assertArrayEqual(result.data, expected)
def test_basic(self): """Test cube is constructed with appropriate metadata""" result = WeatherSymbols().create_symbol_cube([self.cube]) self.assertIsInstance(result, iris.cube.Cube) self.assertArrayEqual(result.attributes["weather_code"], self.wxcode) self.assertEqual(result.attributes["weather_code_meaning"], self.wxmeaning) self.assertTrue((result.data == -1).all())
def test_raises_error_missing_cubes(self): """Test check_input_cubes method raises error if data is missing""" plugin = WeatherSymbols() cubes = self.cubes.pop() msg = 'Weather Symbols input cubes are missing' with self.assertRaisesRegex(IOError, msg): plugin.check_input_cubes(cubes)
def test_works_with_lists(self): """Test that the construct_condition method works with a list of Constraints. """ plugin = WeatherSymbols() constraint_list = [ iris.Constraint( name='probability_of_lwe_snowfall_rate_above_threshold', coord_values={'threshold': 0.03}), iris.Constraint( name='probability_of_rainfall_rate_above_threshold', coord_values={'threshold': 0.03}) ] condition = '<' prob_threshold = 0.5 gamma = 0.7 expected = ("(cubes.extract(Constraint(name=" "'probability_of_lwe_snowfall_rate_above_threshold', " "coord_values={'threshold': 0.03}))[0].data - " "cubes.extract(Constraint(name=" "'probability_of_rainfall_rate_above_threshold', " "coord_values={'threshold': 0.03}))[0].data * 0.7) < 0.5") result = plugin.construct_condition(constraint_list, condition, prob_threshold, gamma) self.assertIsInstance(result, str) self.assertEqual(result, expected)
def test_raises_error_missing_cubes_global(self): """Test check_input_cubes method raises error if data is missing""" plugin = WeatherSymbols(wxtree='global') cubes = set_up_wxcubes_global()[0:3] msg = 'Weather Symbols input cubes are missing' with self.assertRaisesRegex(IOError, msg): plugin.check_input_cubes(cubes)
def test_raises_error_missing_cubes_global(self): """Test check_input_cubes method raises error if data is missing""" plugin = WeatherSymbols(wxtree="global") cubes = self.cubes.extract(self.gbl)[0:3] msg = "Weather Symbols input cubes are missing" with self.assertRaisesRegex(IOError, msg): plugin.check_input_cubes(cubes)
def test_old_naming_convention(self): """Test construct_extract_constraint can return a constraint with a "threshold" coordinate""" plugin = WeatherSymbols() diagnostic = 'probability_of_rainfall_rate_above_threshold' threshold = AuxCoord(0.03, units='mm hr-1') result = plugin.construct_extract_constraint(diagnostic, threshold, True) expected = ("iris.Constraint(" "name='probability_of_rainfall_rate_above_threshold', " "threshold=lambda cell: 0.03 * {t_min} < cell < 0.03 * " "{t_max})".format( t_min=(1. - WeatherSymbols().float_tolerance), t_max=(1. + WeatherSymbols().float_tolerance))) self.assertIsInstance(result, str) self.assertEqual(result, expected)
def test_weather_data_global(self): """Test process returns the right weather values global part2 """ plugin = WeatherSymbols(wxtree='global') data_snow = np.array([ 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.1, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 ]).reshape(3, 1, 3, 3) data_rain = np.array([ 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]).reshape(3, 1, 3, 3) data_cloud = np.array([ 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0 ]).reshape(2, 1, 3, 3) data_cld_low = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).reshape(1, 1, 3, 3) data_vis = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]).reshape(2, 1, 3, 3) cubes = set_up_wxcubes_global() cubes[0].data = data_snow cubes[1].data = data_rain cubes[2].data = data_cloud cubes[3].data = data_cld_low cubes[4].data = data_vis result = plugin.process(cubes) expected_wxcode = np.array([14, 15, 17, 18, 23, 24, 26, 27, 27]).reshape(1, 3, 3) self.assertArrayEqual(result.data, expected_wxcode)
def test_basic(self): """Test find_all_routes returns a list of expected nodes.""" plugin = WeatherSymbols() result = plugin.find_all_routes(self.test_graph, 'start_node', 3) expected_nodes = [['start_node', 'fail_0', 3]] self.assertIsInstance(result, list) self.assertListEqual(result, expected_nodes)
def test_basic_global(self): """Test process returns a wxcode cube with right values for global. """ plugin = WeatherSymbols(wxtree="global") cubes = self.cubes.extract(self.gbl) result = plugin.process(cubes) self.assertArrayAndMaskEqual(result.data, self.expected_wxcode_no_lightning)
def test_no_lightning(self): """Test process returns right values if no lightning. """ plugin = WeatherSymbols() cubes = self.cubes.extract(self.uk_no_lightning) result = plugin.process(cubes) self.assertArrayAndMaskEqual(result.data, self.expected_wxcode_no_lightning)
def test_basic(self): """Test construct_extract_constraint returns a iris.Constraint.""" plugin = WeatherSymbols() diagnostic = "probability_of_rainfall_rate_above_threshold" threshold = AuxCoord(0.03, units="mm hr-1") result = plugin.construct_extract_constraint(diagnostic, threshold, False) expected = ("iris.Constraint(" "name='probability_of_rainfall_rate_above_threshold', " "rainfall_rate=lambda cell: 0.03 * {t_min} < cell < " "0.03 * {t_max})".format( t_min=(1.0 - WeatherSymbols().float_tolerance), t_max=(1.0 + WeatherSymbols().float_tolerance), )) self.assertIsInstance(result, str) self.assertEqual(result, expected)
def test_basic(self): """Test create_condition_chain returns a list of strings.""" plugin = WeatherSymbols() test_condition = self.dummy_queries['significant_precipitation'] result = plugin.create_condition_chain(test_condition) expected = ("(cubes.extract(iris.Constraint(name='probability_of_" "rainfall_rate', threshold=lambda cell: 0.03 * {t_min} < " "cell < 0.03 * {t_max}))[0].data >= 0.5) | (cubes.extract" "(iris.Constraint(name='probability_of_lwe_snowfall_rate'," " threshold=lambda cell: 0.03 * {t_min} < cell < 0.03 * " "{t_max}))[0].data >= 0.5)".format( t_min=(1. - WeatherSymbols().float_tolerance), t_max=(1. + WeatherSymbols().float_tolerance))) self.assertIsInstance(result, list) self.assertIsInstance(result[0], str) self.assertEqual(result[0], expected)
def test_weather_data_global(self): """Test process returns the right weather values global part2 """ plugin = WeatherSymbols(wxtree='global') data_snow = np.array([ 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.1, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 1.0, 1.0, 0.0, 0.0, 1.0, 1.0, 1.0 ]).reshape((3, 3, 3)) data_rain = np.array([ 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]).reshape((3, 3, 3)) data_cloud = np.array([ 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 1.0 ]).reshape((2, 3, 3)) data_cld_low = np.array([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]).reshape((1, 3, 3)) data_vis = np.array([ 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0 ]).reshape((2, 3, 3)) cubes = self.cubes.extract(self.gbl) cubes[0].data = data_snow cubes[1].data = data_rain cubes[2].data = data_cloud cubes[3].data = data_cld_low cubes[4].data = data_vis result = plugin.process(cubes) self.assertArrayEqual(result.data, self.expected_wxcode_alternate)
def process( *cubes: cli.inputcube, wxtree: cli.inputjson = None, model_id_attr: str = None, record_run_attr: str = None, target_period: int = None, check_tree: bool = False, ): """ Processes cube for Weather symbols. Args: cubes (iris.cube.CubeList): A cubelist containing the diagnostics required for the weather symbols decision tree, these at co-incident times. wxtree (dict): A JSON file containing a weather symbols decision tree definition. model_id_attr (str): Name of attribute recording source models that should be inherited by the output cube. The source models are expected as a space-separated string. record_run_attr: Name of attribute used to record models and cycles used in constructing the weather symbols. target_period: The period in seconds that the weather symbol being produced should represent. This should correspond with any period diagnostics, e.g. precipitation accumulation, being used as input. This is used to scale any threshold values that are defined with an associated period in the decision tree. It will only be used if the decision tree provided has threshold values defined with an associated period. check_tree (bool): If set, the decision tree will be checked to see if it conforms to the expected format and that all nodes can be reached; the only other argument required is the path to the decision tree. If the tree is found to be valid the required inputs will be listed. Setting this flag will prevent the CLI performing any other actions. Returns: iris.cube.Cube: A cube of weather symbols. """ if check_tree: from improver.wxcode.utilities import check_tree return check_tree(wxtree, target_period=target_period) from iris.cube import CubeList from improver.wxcode.weather_symbols import WeatherSymbols if not cubes: raise RuntimeError("Not enough input arguments. See help for more information.") return WeatherSymbols( wxtree, model_id_attr=model_id_attr, record_run_attr=record_run_attr, target_period=target_period, )(CubeList(cubes))
def test_multiple_routes(self): """Test finds multiple routes.""" plugin = WeatherSymbols() result = plugin.find_all_routes(self.test_graph, 'start_node', 1) expected_nodes = [['start_node', 'success_1', 'success_1_1', 1], ['start_node', 'fail_0', 'success_0_1', 1]] self.assertIsInstance(result, list) self.assertListEqual(result, expected_nodes)
def test_no_lightning(self): """Test check_input_cubes raises no error if lightning missing""" plugin = WeatherSymbols() cubes = self.cubes.extract(self.uk_no_lightning) result = plugin.check_input_cubes(cubes) self.assertIsInstance(result, dict) self.assertEqual(len(result), 1) self.assertTrue("lightning" in result)
def test_basic(self): """Test that the format_condition_chain method returns a string.""" plugin = WeatherSymbols() conditions = ['condition1', 'condition2'] expected = '(condition1) & (condition2)' result = plugin.format_condition_chain(conditions) self.assertIsInstance(result, str) self.assertEqual(result, expected)
def test_raises_error_missing_threshold(self): """Test check_input_cubes method raises error if data is missing""" plugin = WeatherSymbols() cubes = self.cubes cubes[0] = cubes[0][0] msg = "Weather Symbols input cubes are missing" with self.assertRaisesRegex(IOError, msg): plugin.check_input_cubes(cubes)
def test_works_with_or(self): """Test that the format_condition_chain method works with OR.""" plugin = WeatherSymbols() conditions = ["condition1", "condition2"] expected = "(condition1) | (condition2)" result = plugin.format_condition_chain(conditions, condition_combination="OR") self.assertIsInstance(result, str) self.assertEqual(result, expected)
def test_incorrect_units(self): """Test that check_input_cubes method raises an error if the units are incompatible between the input cube and the decision tree.""" plugin = WeatherSymbols() msg = "Unable to convert from" self.cubes[0].coord('threshold').units = Unit('mm kg-1') with self.assertRaisesRegexp(ValueError, msg): plugin.check_input_cubes(self.cubes)
def test_basic(self): """Test that the invert_condition method returns a tuple of strings.""" plugin = WeatherSymbols() tree = plugin.queries result = plugin.invert_condition(tree[list(tree.keys())[0]]) self.assertIsInstance(result, tuple) self.assertEqual(len(result), 2) self.assertIsInstance(result[0], str) self.assertIsInstance(result[1], str)
def test_day_night(self): """Test process returns the right values for night. """ plugin = WeatherSymbols() for i, cube in enumerate(self.cubes): self.cubes[i].coord('time').points = (cube.coord('time').points + 11.5) result = plugin.process(self.cubes) expected_wxcode = np.array([0, 2, 5, 6, 7, 8, 9, 11, 12]).reshape(1, 3, 3) self.assertArrayEqual(result.data, expected_wxcode)
def test_basic(self): """Test construct_extract_constraint method returns a iris.Constraint. or list of iris.Constraint""" plugin = WeatherSymbols() result = plugin.create_symbol_cube(self.cube[0]) self.assertIsInstance(result, iris.cube.Cube) self.assertArrayEqual(result.attributes['weather_code'], self.wxcode) self.assertEqual(result.attributes['weather_code_meaning'], self.wxmeaning)
def test_invert_combination_correctly(self): """Test invert_condition inverts combination correctly.""" plugin = WeatherSymbols() node = {'threshold_condition': '>=', 'condition_combination': ''} possible_inputs = ['AND', 'OR', ''] inverse_outputs = ['OR', 'AND', ''] for i, val in enumerate(possible_inputs): node['condition_combination'] = val result = plugin.invert_condition(node) self.assertEqual(result[1], inverse_outputs[i])
def test_omit_nodes_blocked(self): """Test find_all_routes where omitted node is no longer accessible.""" omit_nodes = {"fail_0": 3} plugin = WeatherSymbols() result = plugin.find_all_routes( self.test_graph, "start_node", 5, omit_nodes=omit_nodes, ) expected_nodes = [] self.assertIsInstance(result, list) self.assertListEqual(result, expected_nodes)
def test_omit_nodes_multi(self): """Test find_all_routes where multiple omitted nodes.""" omit_nodes = {"fail_0": 3, "success_1": "success_1_1"} plugin = WeatherSymbols() result = plugin.find_all_routes( self.test_graph, "start_node", 1, omit_nodes=omit_nodes, ) expected_nodes = [["start_node", "success_1_1", 1]] self.assertIsInstance(result, list) self.assertListEqual(result, expected_nodes)
def test_basic(self): """Test process returns a weather code cube with right values and type. """ plugin = WeatherSymbols() result = plugin.process(self.cubes) self.assertIsInstance(result, iris.cube.Cube) self.assertArrayEqual(result.attributes["weather_code"], self.wxcode) self.assertEqual(result.attributes["weather_code_meaning"], self.wxmeaning) self.assertArrayEqual(result.data, self.expected_wxcode) self.assertEqual(result.dtype, np.int32)