Example #1
0
 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)
Example #2
0
    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()
        }
Example #3
0
    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
Example #4
0
    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
Example #5
0
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)
Example #6
0
    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))
Example #7
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.

        '''
        # 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
Example #8
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