def is_written(self): ''':returns: True if this variable is written (at least once). :rtype: bool ''' return any(access_info.access_type in AccessType.all_write_accesses() for access_info in self._accesses)
def __init__(self, section): # Set a default mapping, this way the test cases all work without # having to specify those mappings. self._access_mapping = { "read": "read", "write": "write", "readwrite": "readwrite", "inc": "inc", "sum": "sum" } # Get the mapping and convert it into a dictionary. The input is in # the format: key1:value1, key2=value2, ... mapping = section.get("ACCESS_MAPPING") if mapping: self._access_mapping = \ APISpecificConfig.create_dict_from_string(mapping) # Now convert the string type ("read" etc) to AccessType # TODO (issue #710): Add checks for duplicate or missing access # key-value pairs from psyclone.core.access_type import AccessType for api_access_name, access_type in self._access_mapping.items(): try: self._access_mapping[api_access_name] = \ AccessType.from_string(access_type) except ValueError: # Raised by from_string() raise ConfigurationError("Unknown access type '{0}' found " "for key '{1}'".format( access_type, api_access_name)) # Now create the reverse lookup (for better error messages): self._reverse_access_mapping = { v: k for k, v in self._access_mapping.items() }
def is_read(self): '''Checks if the specified variable name is at least read once. :returns: true if the specified variable name is read (at least once). :rtype: bool ''' for access_info in self._accesses: if access_info.access_type in AccessType.all_read_accesses(): return True return False
def is_written(self): '''Checks if the specified variable name is at least written once. :returns: true if the specified variable name is written (at least \ once). :rtype: bool ''' for access_info in self._accesses: if access_info.access_type in AccessType.all_write_accesses(): return True return False
def test_from_string(): '''Test the from_string method.''' assert AccessType.from_string("inc") == AccessType.INC assert AccessType.from_string("write") == AccessType.WRITE assert AccessType.from_string("read") == AccessType.READ assert AccessType.from_string("readwrite") == AccessType.READWRITE assert AccessType.from_string("unknown") == AccessType.UNKNOWN assert AccessType.from_string("sum") == AccessType.SUM with pytest.raises(ValueError) as err: AccessType.from_string("invalid") assert "Unknown access type 'invalid'. Valid values are ['inc', 'read',"\ " 'readwrite', 'sum', 'unknown', 'write']" in str(err)
def validate(self, node1, node2, options=None): ''' Performs various checks to ensure that it is valid to apply the LFRicLoopFuseTrans transformation to the supplied loops. :param node1: the first Loop to fuse. :type node1: :py:class:`psyclone.dynamo0p3.DynLoop` :param node2: the second Loop to fuse. :type node2: :py:class:`psyclone.dynamo0p3.DynLoop` :param options: a dictionary with options for transformations. :type options: dictionary of string:values or None :param bool options["same_space"]: this optional flag, set to `True`, \ asserts that an unknown iteration space (i.e. `ANY_SPACE`) \ matches the other iteration space. This is set at the user's own \ risk. If both iteration spaces are discontinuous the loops can be \ fused without having to use the `same_space` flag. :raises TransformationError: if either of the supplied loops contains \ an inter-grid kernel. :raises TransformationError: if one or both function spaces have \ invalid names. :raises TransformationError: if the `same_space` flag was set, but \ does not apply because neither field \ is on `ANY_SPACE` or the spaces are not \ the same. :raises TransformationError: if one or more of the iteration spaces \ is unknown (`ANY_SPACE`) and the \ `same_space` flag is not set to `True`. :raises TransformationError: if the loops are over different spaces \ that are not both discontinuous and \ the loops both iterate over cells. :raises TransformationError: if the loops' upper bound names are \ not the same. :raises TransformationError: if the halo-depth indices of two loops \ are not the same. :raises TransformationError: if each loop already contains a reduction. :raises TransformationError: if the first loop has a reduction and \ the second loop reads the result of \ the reduction. ''' # pylint: disable=too-many-locals,too-many-branches # Call the parent class validation first if not options: options = {} same_space = options.get("same_space", False) if same_space and not isinstance(same_space, bool): raise TransformationError( "Error in {0} transformation: The value of the 'same_space' " "flag must be either bool or None type, but the type of " "flag provided was '{1}'.". format(self.name, type(same_space).__name__)) super(LFRicLoopFuseTrans, self).validate(node1, node2, options=options) # Now test for Dynamo-specific constraints # 1) Check that we don't have an inter-grid kernel check_intergrid(node1) check_intergrid(node2) # 2) Check function space names node1_fs_name = node1.field_space.orig_name node2_fs_name = node2.field_space.orig_name # 2.1) Check that both function spaces are valid const = LFRicConstants() if not (node1_fs_name in const.VALID_FUNCTION_SPACE_NAMES and node2_fs_name in const.VALID_FUNCTION_SPACE_NAMES): raise TransformationError( "Error in {0} transformation: One or both function " "spaces '{1}' and '{2}' have invalid names.". format(self.name, node1_fs_name, node2_fs_name)) # Check whether any of the spaces is ANY_SPACE. Loop fusion over # ANY_SPACE is allowed only when the 'same_space' flag is set node_on_any_space = node1_fs_name in \ const.VALID_ANY_SPACE_NAMES or \ node2_fs_name in const.VALID_ANY_SPACE_NAMES # 2.2) If 'same_space' is true check that both function spaces are # the same or that at least one of the nodes is on ANY_SPACE. The # former case is convenient when loop fusion is applied generically. if same_space: if node1_fs_name == node2_fs_name: pass elif not node_on_any_space: raise TransformationError( "Error in {0} transformation: The 'same_space' " "flag was set, but does not apply because " "neither field is on 'ANY_SPACE'.".format(self)) # 2.3) If 'same_space' is not True then make further checks else: # 2.3.1) Check whether one or more of the function spaces # is ANY_SPACE without the 'same_space' flag if node_on_any_space: raise TransformationError( "Error in {0} transformation: One or more of the " "iteration spaces is unknown ('ANY_SPACE') so loop " "fusion might be invalid. If you know the spaces " "are the same then please set the 'same_space' " "optional argument to 'True'.".format(self.name)) # 2.3.2) Check whether specific function spaces are the # same. If they are not, the loop fusion is still possible # but only when both function spaces are discontinuous # (w3, w2v, wtheta or any_discontinuous_space) and the upper # loop bounds are the same (checked further below). if node1_fs_name != node2_fs_name: if not (node1_fs_name in const.VALID_DISCONTINUOUS_NAMES and node2_fs_name in const.VALID_DISCONTINUOUS_NAMES): raise TransformationError( "Error in {0} transformation: Cannot fuse loops " "that are over different spaces '{1}' and '{2}' " "unless they are both discontinuous.". format(self.name, node1_fs_name, node2_fs_name)) # 3) Check upper loop bounds if node1.upper_bound_name != node2.upper_bound_name: raise TransformationError( "Error in {0} transformation: The upper bound names " "are not the same. Found '{1}' and '{2}'.". format(self.name, node1.upper_bound_name, node2.upper_bound_name)) # 4) Check halo depths if node1.upper_bound_halo_depth != node2.upper_bound_halo_depth: raise TransformationError( "Error in {0} transformation: The halo-depth indices " "are not the same. Found '{1}' and '{2}'.". format(self.name, node1.upper_bound_halo_depth, node2.upper_bound_halo_depth)) # 5) Check for reductions arg_types = const.VALID_SCALAR_NAMES all_reductions = AccessType.get_valid_reduction_modes() node1_red_args = node1.args_filter(arg_types=arg_types, arg_accesses=all_reductions) node2_red_args = node2.args_filter(arg_types=arg_types, arg_accesses=all_reductions) if node1_red_args and node2_red_args: raise TransformationError( "Error in {0} transformation: Cannot fuse loops " "when each loop already contains a reduction.". format(self.name)) if node1_red_args: for reduction_arg in node1_red_args: other_args = node2.args_filter() for arg in other_args: if reduction_arg.name == arg.name: raise TransformationError( "Error in {0} transformation: Cannot fuse " "loops as the first loop has a reduction " "and the second loop reads the result of " "the reduction.".format(self.name))
def _init_scalar(self, arg_type): ''' Validates metadata descriptors for scalar arguments and initialises scalar argument properties accordingly. :param arg_type: LFRic API scalar argument type. :type arg_type: :py:class:`psyclone.expression.FunctionVar` :raises InternalError: if argument type other than a scalar is \ passed in. :raises ParseError: if there are not exactly 3 metadata arguments. :raises InternalError: if a scalar argument has an invalid data type. :raises ParseError: if scalar arguments do not have a read-only or a reduction access. :raises ParseError: if a scalar argument that is not a real \ scalar has a reduction access. ''' # Check whether something other than a scalar is passed in if self._argument_type not in LFRicArgDescriptor.VALID_SCALAR_NAMES: raise InternalError( "LFRicArgDescriptor._init_scalar(): expected a scalar " "argument but got an argument of type '{0}'.".format( arg_type.args[0])) # There must be 3 argument descriptors to describe a scalar. # TODO in #874: Remove support for the old-style 2 descriptors. min_scalar_nargs = 2 + self._offset if self._nargs != min_scalar_nargs: raise ParseError( "In the LFRic API each 'meta_arg' entry must have {0} " "arguments if its first argument is 'gh_{{r,i}}scalar', but " "found {1} in '{2}'.".format(min_scalar_nargs, self._nargs, arg_type)) # Check whether an invalid data type for a scalar argument is passed # in. Valid data types for scalars are valid data types in LFRic API. # TODO in #874: Remove the support for old-style scalar metadata that # assigns the data type from the scalar name (the 1st # argument). # Note: The main check for the valid scalar data types # will be ParseError in the class constructor and this # scalar init method only needs to check for # InternalError. if not self._data_type and self._offset == 0: self._data_type = arg_type.args[0].name # Translate the old-style argument type into the current one self._argument_type = "gh_scalar" if (self._data_type not in LFRicArgDescriptor.VALID_SCALAR_DATA_TYPES): raise InternalError( "LFRicArgDescriptor._init_scalar(): expected one of {0} " "as the data type but got '{1}'.".format( LFRicArgDescriptor.VALID_SCALAR_DATA_TYPES, self._data_type)) # Test allowed accesses for scalars (read_only or reduction) scalar_accesses = [AccessType.READ] + \ AccessType.get_valid_reduction_modes() # Convert generic access types to GH_* names for error messages api_config = Config.get().api_conf(API) rev_access_mapping = api_config.get_reverse_access_mapping() if self._access_type not in scalar_accesses: api_specific_name = rev_access_mapping[self._access_type] valid_reductions = AccessType.get_valid_reduction_names() raise ParseError( "In the LFRic API scalar arguments must have read-only " "('gh_read') or a reduction {0} access but found '{1}' " "in '{2}'.".format(valid_reductions, api_specific_name, arg_type)) # Reduction access is currently only valid for real scalar arguments if self._data_type != "gh_real" and self._access_type in \ AccessType.get_valid_reduction_modes(): raise ParseError( "In the LFRic API a reduction access '{0}' is only valid " "with a real scalar argument, but a scalar argument with " "'{1}' data type was found in '{2}'.".format( self._access_type.api_specific_name(), self._data_type, arg_type)) # Scalars don't have vector size self._vector_size = 0
def _init_scalar(self, arg_type): ''' Validates metadata descriptors for scalar arguments and initialises scalar argument properties accordingly. :param arg_type: LFRic API scalar argument type. :type arg_type: :py:class:`psyclone.expression.FunctionVar` :raises InternalError: if argument type other than a scalar is \ passed in. :raises ParseError: if there are not exactly 3 metadata arguments. :raises InternalError: if a scalar argument has an invalid data type. :raises ParseError: if scalar arguments do not have a read-only or a reduction access. :raises ParseError: if a scalar argument that is not a real \ scalar has a reduction access. ''' const = LFRicConstants() # Check whether something other than a scalar is passed in if self._argument_type not in const.VALID_SCALAR_NAMES: raise InternalError( "Expected a scalar argument but got an argument of type " "'{0}'.".format(arg_type.args[0])) # There must be 3 argument descriptors to describe a scalar. nargs_scalar = 3 if self._nargs != nargs_scalar: raise ParseError( "In the LFRic API each 'meta_arg' entry must have {0} " "arguments if its first argument is 'gh_scalar', but " "found {1} in '{2}'.".format(nargs_scalar, self._nargs, arg_type)) # Check whether an invalid data type for a scalar argument is passed # in. Valid data types for scalars are valid data types in LFRic API. if self._data_type not in const.VALID_SCALAR_DATA_TYPES: raise InternalError( "Expected one of {0} as the scalar data type but got '{1}'.". format(const.VALID_SCALAR_DATA_TYPES, self._data_type)) # Test allowed accesses for scalars (read_only or reduction) scalar_accesses = [AccessType.READ] + \ AccessType.get_valid_reduction_modes() # Convert generic access types to GH_* names for error messages api_config = Config.get().api_conf(API) rev_access_mapping = api_config.get_reverse_access_mapping() if self._access_type not in scalar_accesses: api_specific_name = rev_access_mapping[self._access_type] valid_reductions = AccessType.get_valid_reduction_names() raise ParseError( "In the LFRic API scalar arguments must have read-only " "('gh_read') or a reduction {0} access but found '{1}' " "in '{2}'.".format(valid_reductions, api_specific_name, arg_type)) # Reduction access is currently only valid for real scalar arguments if self._data_type != "gh_real" and self._access_type in \ AccessType.get_valid_reduction_modes(): raise ParseError( "In the LFRic API a reduction access '{0}' is only valid " "with a real scalar argument, but a scalar argument with " "'{1}' data type was found in '{2}'.".format( self._access_type.api_specific_name(), self._data_type, arg_type)) # Scalars don't have vector size self._vector_size = 0