コード例 #1
0
    def create(cls, symbol, arguments):
        '''Create an instance of the calling class given valid instances of a
        DataTypeSymbol and a list of child nodes for its arguments.

        :param symbol: the name of the kernel type that this object \
            references.
        :type symbol: py:class:`psyclone.psyir.symbols.DataTypeSymbol`
        :param arguments: the arguments to this routine. These are \
            added as child nodes.
        :type arguments: list of :py:class:`psyclone.psyir.nodes.DataNode`

        :returns: an instance of the calling class.
        :rtype: :py:class:`psyclone.psyir.nodes.Call` or subclass thereof.

        '''
        if not isinstance(symbol, DataTypeSymbol):
            raise GenerationError(
                "KernelFunctor create() symbol argument should be a "
                "DataTypeSymbol but found '{0}'.".format(
                    type(symbol).__name__))
        if not isinstance(arguments, list):
            raise GenerationError(
                "KernelFunctor create() arguments argument should be a list "
                "but found '{0}'.".format(type(arguments).__name__))

        call = cls(symbol)
        call.children = arguments
        return call
コード例 #2
0
ファイル: node.py プロジェクト: mfkiwl/PSyclone
    def replace_with(self, node):
        '''Removes self, and its descendants, from the PSyIR tree to which it
        is connected, and replaces it with the supplied node (and its
        descendants).

        :param node: the node that will replace self in the PSyIR \
            tree.
        :type node: :py:class:`psyclone.psyir.nodes.node`

        :raises TypeError: if the argument 'node' is not a Node.
        :raises GenerationError: if this node does not have a parent.
        :raises GenerationError: if the argument 'node' has a parent.

        '''
        if not isinstance(node, Node):
            raise TypeError(
                "The argument node in method replace_with in the Node class "
                "should be a Node but found '{0}'."
                "".format(type(node).__name__))
        if not self.parent:
            raise GenerationError(
                "This node should have a parent if its replace_with method "
                "is called.")
        if node.parent is not None:
            raise GenerationError(
                "The parent of argument node in method replace_with in the "
                "Node class should be None but found '{0}'."
                "".format(type(node.parent).__name__))

        node.parent = self.parent
        self.parent.children[self.position] = node
        self.parent = None
コード例 #3
0
ファイル: gen_kernel_stub.py プロジェクト: xyuan/PSyclone
def generate(filename, api=""):
    '''
    Generates an empty kernel subroutine with the required arguments
    and datatypes (which we call a stub) when presented with Kernel
    Metadata. This is useful for Kernel developers to make sure
    they are using the correct arguments in the correct order.  The
    Kernel Metadata must be presented in the standard Kernel
    format.

    :param str filename: the name of the file for which to create a \
                         kernel stub for.
    :param str api: the name of the API for which to create a kernel \
                    stub. Must be one of the supported stub APIs.

    :returns: root of fparser1 parse tree for the stub routine.
    :rtype: :py:class:`fparser.one.block_statements.Module`

    :raises GenerationError: if an invalid stub API is specified.
    :raises IOError: if filename does not specify a file.
    :raises ParseError: if the given file could not be parsed.
    :raises GenerationError: if a kernel stub does not have a supported \
                             iteration space (currently only "cells").

    '''
    if api == "":
        api = Config.get().default_stub_api
    if api not in Config.get().supported_stub_apis:
        raise GenerationError(
            "Kernel stub generator: Unsupported API '{0}' specified. "
            "Supported APIs are {1}.".format(api,
                                             Config.get().supported_stub_apis))

    if not os.path.isfile(filename):
        raise IOError(
            "Kernel stub generator: File '{0}' not found.".format(filename))

    # Drop cache
    fparser.one.parsefortran.FortranParser.cache.clear()
    fparser.logging.disable(fparser.logging.CRITICAL)
    try:
        ast = fparser.api.parse(filename, ignore_comments=False)

    except (fparser.common.utils.AnalyzeError, AttributeError) as error:
        raise ParseError("Kernel stub generator: Code appears to be invalid "
                         "Fortran: {0}.".format(str(error)))

    metadata = DynKernMetadata(ast)
    kernel = DynKern()
    kernel.load_meta(metadata)

    # Check kernel iteration space before generating code
    if (api == "dynamo0.3"
            and kernel.iterates_over not in USER_KERNEL_ITERATION_SPACES):
        raise GenerationError(
            "The LFRic API kernel stub generator supports kernels that operate"
            " on one of {0}, but found '{1}' in kernel '{2}'.".format(
                USER_KERNEL_ITERATION_SPACES, kernel.iterates_over,
                kernel.name))

    return kernel.gen_stub
コード例 #4
0
    def create(cls, routine, arguments):
        '''Create an instance of class cls given valid instances of a routine
        symbol, and a list of child nodes for its arguments.

        :param routine: the routine that class cls calls.
        :type routine: py:class:`psyclone.psyir.symbols.RoutineSymbol`
        :param arguments: the arguments to this routine. These are \
            added as child nodes.
        :type arguments: list of :py:class:`psyclone.psyir.nodes.DataNode`

        :returns: an instance of cls.
        :rtype: :py:class:`psyclone.psyir.nodes.Call` or a subclass thereof.

        '''
        if not isinstance(routine, RoutineSymbol):
            raise GenerationError(
                "Call create routine argument should be a RoutineSymbol but "
                "found '{0}'.".format(type(routine).__name__))
        if not isinstance(arguments, list):
            raise GenerationError(
                "Call create arguments argument should be a list but found "
                "'{0}'.".format(type(arguments).__name__))

        call = cls(routine)
        call.children = arguments
        for child in call.children:
            child.parent = call
        return call
コード例 #5
0
    def create(oper, children):
        '''Create an NaryOperator instance given an operator and a list of
        Node instances.

        :param operator: the operator used in the operation.
        :type operator: :py:class:`psyclone.psyir.nodes.NaryOperation.Operator`
        :param children: a list of PSyIR nodes that the operator \
            operates on.
        :type children: list of :py:class:`psyclone.psyir.nodes.Node`

        :returns: an NaryOperator instance.
        :rtype: :py:class:`psyclone.psyir.nodes.NaryOperation`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        if not isinstance(oper, NaryOperation.Operator):
            raise GenerationError(
                "oper argument in create method of NaryOperation class "
                "should be a PSyIR NaryOperation Operator but found '{0}'."
                "".format(type(oper).__name__))
        if not isinstance(children, list):
            raise GenerationError(
                "children argument in create method of NaryOperation class "
                "should be a list but found '{0}'."
                "".format(type(children).__name__))

        nary_op = NaryOperation(oper)
        nary_op.children = children
        for child in children:
            child.parent = nary_op
        return nary_op
コード例 #6
0
    def create(member_name, indices):
        '''
        Construct an ArrayMember instance describing an array access to a
        member of a structure.

        e.g. for the Fortran `grid%subdomains(1,2)`, `subdomains` must be an
        array and we are accessing element (1,2) of it. We would therefore
        create the ArrayMember for this access by calling:

        >>> amem = ArrayMember.create("subdomains",
                                      [Literal("1", INTEGER_TYPE),
                                       Literal("2", INTEGER_TYPE)])

        :param str member_name: the name of the member of the structure that \
            is being accessed.
        :param indices: the array-index expressions.
        :type indices: list of :py:class:`psyclone.psyir.nodes.DataNode` or
            :py:class:`psyclone.psyir.nodes.Range`

        :raises GenerationError: if the supplied `indices` argument is not \
                                 a list.
        '''
        if not isinstance(indices, list):
            raise GenerationError(
                "indices argument in create method of ArrayMember class "
                "should be a list but found '{0}'."
                "".format(type(indices).__name__))

        obj = ArrayMember(member_name)
        # Add any array-index expressions as children
        for child in indices:
            obj.addchild(child)
        return obj
コード例 #7
0
    def create(oper, lhs, rhs):
        '''Create a BinaryOperator instance given an operator and lhs and rhs
        child instances.

        :param operator: the operator used in the operation.
        :type operator: \
            :py:class:`psyclone.psyir.nodes.BinaryOperation.Operator`
        :param lhs: the PSyIR node containing the left hand side of \
            the assignment.
        :type lhs: :py:class:`psyclone.psyir.nodes.Node`
        :param rhs: the PSyIR node containing the right hand side of \
            the assignment.
        :type rhs: :py:class:`psyclone.psyir.nodes.Node`

        :returns: a BinaryOperator instance.
        :rtype: :py:class:`psyclone.psyir.nodes.BinaryOperation`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        if not isinstance(oper, BinaryOperation.Operator):
            raise GenerationError(
                "oper argument in create method of BinaryOperation class "
                "should be a PSyIR BinaryOperation Operator but found '{0}'."
                "".format(type(oper).__name__))

        binary_op = BinaryOperation(oper)
        binary_op.children = [lhs, rhs]
        lhs.parent = binary_op
        rhs.parent = binary_op
        return binary_op
コード例 #8
0
    def create(oper, child):
        '''Create a UnaryOperation instance given oper and child instances.

        :param oper: the specified operator.
        :type oper: :py:class:`psyclone.psyir.nodes.UnaryOperation.Operator`
        :param child: the PSyIR node that oper operates on.
        :type child: :py:class:`psyclone.psyir.nodes.Node`

        :returns: a UnaryOperation instance.
        :rtype: :py:class:`psyclone.psyir.nodes.UnaryOperation`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        if not isinstance(oper, UnaryOperation.Operator):
            raise GenerationError(
                "oper argument in create method of UnaryOperation class "
                "should be a PSyIR UnaryOperation Operator but found '{0}'."
                "".format(type(oper).__name__))

        unary_op = UnaryOperation(oper)
        unary_op.children = [child]
        child.parent = unary_op
        return unary_op
コード例 #9
0
ファイル: node.py プロジェクト: mfkiwl/PSyclone
    def _validate_item(self, index, item):
        '''
        Validates the provided index and item before continuing inserting the
        item into the list.

        :param int index: position where the item is inserted into.
        :param item: object that needs to be validated in the given position.
        :type item: :py:class:`psyclone.psyir.nodes.Node`

        :raises GenerationError: if the given index and item are not valid \
            children for this list.
        '''
        if not self._validation_function(index, item):
            errmsg = "Item '{0}' can't be child {1} of '{2}'.".format(
                item.__class__.__name__, index,
                self._node_reference.coloured_name(False))
            if self._validation_text == "<LeafNode>":
                errmsg = errmsg + " {0} is a LeafNode and doesn't accept " \
                    "children.".format(
                        self._node_reference.coloured_name(False))
            else:
                errmsg = errmsg + " The valid format is: '{0}'.".format(
                    self._validation_text)

            raise GenerationError(errmsg)
コード例 #10
0
ファイル: function_space.py プロジェクト: xyuan/PSyclone
    def get_operator_name(self, operator_name, qr_var=None, on_space=None):
        '''
        Returns the name of the specified operator (orientation, basis or
        differential basis) for this FunctionSpace.

        :param str operator_name: name (type) of the operator.
        :param str qr_var: the name of the Quadrature Object for which the \
                           operator is required.
        :param on_space: the function space at which the operator is required.
        :type on_space: :py:class:`psyclone.domain.lfric.FunctionSpace`

        :returns: name for the Fortran arry holding the named operator \
                  for the specified function space.
        :rtype: str

        '''
        if operator_name == "gh_orientation":
            return self.orientation_name
        if operator_name == "gh_basis":
            return self.get_basis_name(qr_var=qr_var, on_space=on_space)
        if operator_name == "gh_diff_basis":
            return self.get_diff_basis_name(qr_var=qr_var, on_space=on_space)

        raise GenerationError(
            "Unsupported name '{0}' found. Expected one of {1}".format(
                operator_name, FunctionSpace.VALID_METAFUNC_NAMES))
コード例 #11
0
ファイル: node.py プロジェクト: mfkiwl/PSyclone
    def dag(self, file_name='dag', file_format='svg'):
        '''
        Create a dag of this node and its children, write it to file and
        return the graph object.

        :param str file_name: name of the file to create.
        :param str file_format: format of the file to create. (Must be one \
                                recognised by Graphviz.)

        :returns: the graph object or None (if the graphviz bindings are not \
                  installed).
        :rtype: :py:class:`graphviz.Digraph` or NoneType

        :raises GenerationError: if the specified file format is not \
                                 recognised by Graphviz.

        '''
        digraph = _graphviz_digraph_class()
        if digraph is None:
            return None
        try:
            graph = digraph(format=file_format)
        except ValueError:
            raise GenerationError(
                "unsupported graphviz file format '{0}' provided".format(
                    file_format))
        self.dag_gen(graph)
        graph.render(filename=file_name)
        return graph
コード例 #12
0
ファイル: kern_call_arg_list.py プロジェクト: xyuan/PSyclone
    def field_bcs_kernel(self, function_space, var_accesses=None):
        '''Implement the boundary_dofs array fix for a field. If supplied it
        also stores this access in var_accesses.

        :param function_space: the function space for which boundary dofs \
            are required.
        :type function_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
        :param var_accesses: optional VariablesAccessInfo instance to store \
            the information about variable accesses.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`

        :raises GenerationError: if the bcs kernel does not contain \
            a field as argument (but e.g. an operator).

        '''
        fspace = None
        for fspace in self._kern.arguments.unique_fss:
            if fspace.orig_name == "any_space_1":
                break
        farg = self._kern.arguments.get_arg_on_space(fspace)
        # Sanity check - expect the enforce_bc_code kernel to only have
        # a field argument.
        if not farg.is_field:
            raise GenerationError(
                "Expected an argument of {0} type from which to look-up "
                "boundary dofs for kernel {1} but got '{2}'".format(
                    LFRicArgDescriptor.VALID_FIELD_NAMES, self._kern.name,
                    farg.argument_type))

        base_name = "boundary_dofs_" + farg.name
        name = self._kern.root.symbol_table.name_from_tag(base_name)
        self.append(name, var_accesses)
コード例 #13
0
    def create(cls, routine, arguments, index):
        '''Create an instance of the calling class given valid instances of a
        routine symbol, a list of child nodes for its arguments and an
        index.

        :param routine: the routine that the calling class calls.
        :type routine: py:class:`psyclone.psyir.symbols.RoutineSymbol`
        :param arguments: the arguments to this routine. These are \
            added as child nodes.
        :type arguments: list of :py:class:`psyclone.psyir.nodes.DataNode`
        :param int index: the position of this invoke call relative to \
            other invokes in the algorithm layer.

        :raises GenerationError: if the arguments argument is not a \
            list.

        :returns: an instance of the calling class.
        :rtype: :py:class:`psyclone.psyir.nodes.AlgorithmInvokeCall` \
            or a subclass thereof.

        '''
        if not isinstance(arguments, list):
            raise GenerationError(
                "AlgorithmInvokeCall create arguments argument should be a "
                "list but found '{0}'.".format(type(arguments).__name__))

        call = cls(routine, index)
        call.children = arguments
        return call
コード例 #14
0
    def lower_to_language_level(self):
        '''Transform this node and its children into an appropriate Call
        node.

        '''
        self.create_psylayer_symbols()

        arguments = []
        arguments_str = []
        for kern in self.children:
            for arg in kern.children:
                if isinstance(arg, Literal):
                    # Literals are not passed by argument.
                    pass
                elif isinstance(arg, (Reference, ArrayReference)):
                    # TODO #753 use a better check for equivalence (math_equal)
                    if str(arg).lower() not in arguments_str:
                        arguments_str.append(str(arg).lower())
                        arguments.append(arg.copy())
                else:
                    raise GenerationError(
                        "Expected Algorithm-layer kernel arguments to be "
                        "a literal, reference or array reference, but "
                        "found '{0}'.".format(type(arg).__name__))

        symbol_table = self.scope.symbol_table
        routine_symbol = self.psylayer_routine_symbol
        container_symbol = routine_symbol.interface.container_symbol
        symbol_table.add(container_symbol)
        symbol_table.add(routine_symbol)
        call = Call.create(routine_symbol, arguments)
        self.replace_with(call)
コード例 #15
0
    def create(if_condition, if_body, else_body=None):
        '''Create an IfBlock instance given valid instances of an
        if_condition, an if_body and an optional else_body.

        :param if_condition: the PSyIR node containing the if \
            condition of the if block.
        :type if_condition: :py:class:`psyclone.psyir.nodes.Node`
        :param if_body: the PSyIR nodes representing the if body of \
            the if block.
        :type if_body: list of :py:class:`psyclone.psyir.nodes.Node`
        :param else_body: PSyIR nodes representing the else body of the \
            if block of None if there is no else body (defaults to None).
        :type else_body: list of :py:class:`psyclone.psyir.nodes.Node` or \
            NoneType

        :returns: an IfBlock instance.
        :rtype: :py:class:`psyclone.psyir.nodes.IfBlock`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        if not isinstance(if_body, list):
            raise GenerationError(
                "if_body argument in create method of IfBlock class should be "
                "a list.")
        if else_body is not None and not isinstance(else_body, list):
            raise GenerationError(
                "else_body argument in create method of IfBlock class should "
                "be a list.")

        if_stmt = IfBlock()
        if_schedule = Schedule(parent=if_stmt)
        if_schedule.children = if_body
        for node in if_body:
            node.parent = if_schedule
        if else_body is not None:
            else_schedule = Schedule(parent=if_stmt)
            else_schedule.children = else_body
            for node in else_body:
                node.parent = else_schedule
            if_stmt.children = [if_condition, if_schedule, else_schedule]
        else:
            if_stmt.children = [if_condition, if_schedule]
        if_condition.parent = if_stmt
        return if_stmt
コード例 #16
0
ファイル: container.py プロジェクト: mfkiwl/PSyclone
    def create(name, symbol_table, children):
        '''Create a Container instance given a name, a symbol table and a
        list of child nodes.

        :param str name: the name of the Container.
        :param symbol_table: the symbol table associated with this \
            Container.
        :type symbol_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
        :param children: a list of PSyIR nodes contained in the \
            Container. These must be Containers or KernelSchedules.
        :type children: list of :py:class:`psyclone.psyir.nodes.Container` \
            or :py:class:`psyclone.psyir.nodes.KernelSchedule`

        :returns: a Container instance.
        :rtype: :py:class:`psyclone.psyir.nodes.Container`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        if not isinstance(name, str):
            raise GenerationError(
                "name argument in create method of Container class "
                "should be a string but found '{0}'."
                "".format(type(name).__name__))
        if not isinstance(symbol_table, SymbolTable):
            raise GenerationError(
                "symbol_table argument in create method of Container class "
                "should be a SymbolTable but found '{0}'."
                "".format(type(symbol_table).__name__))
        if not isinstance(children, list):
            raise GenerationError(
                "children argument in create method of Container class "
                "should be a list but found '{0}'."
                "".format(type(children).__name__))

        container = Container(name)
        container._symbol_table = symbol_table
        symbol_table._node = container
        container.children = children
        for child in children:
            child.parent = container
        return container
コード例 #17
0
    def create(symbol, indices):
        '''Create an ArrayReference instance given a symbol and a list of Node
        array indices.

        :param symbol: the symbol that this array is associated with.
        :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol`
        :param indices: a list of Nodes describing the array indices.
        :type indices: list of :py:class:`psyclone.psyir.nodes.Node`

        :returns: an ArrayReference instance.
        :rtype: :py:class:`psyclone.psyir.nodes.ArrayReference`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        if not isinstance(symbol, DataSymbol):
            raise GenerationError(
                "symbol argument in create method of ArrayReference class "
                "should be a DataSymbol but found '{0}'.".format(
                    type(symbol).__name__))
        if not isinstance(indices, list):
            raise GenerationError(
                "indices argument in create method of ArrayReference class "
                "should be a list but found '{0}'."
                "".format(type(indices).__name__))
        if not symbol.is_array:
            raise GenerationError(
                "expecting the symbol to be an array, not a scalar.")
        if len(symbol.shape) != len(indices):
            raise GenerationError(
                "the symbol should have the same number of dimensions as "
                "indices (provided in the 'indices' argument). "
                "Expecting '{0}' but found '{1}'.".format(
                    len(indices), len(symbol.shape)))

        array = ArrayReference(symbol)
        for child in indices:
            array.addchild(child)
            child.parent = array
        return array
コード例 #18
0
    def loop_type(self, value):
        '''
        Set the type of this Loop.

        :param str value: the type of this loop.
        :raises GenerationError: if the specified value is not a recognised \
                                 loop type.
        '''
        if value not in self._valid_loop_types:
            raise GenerationError(
                "Error, loop_type value ({0}) is invalid. Must be one of "
                "{1}.".format(value, self._valid_loop_types))
        self._loop_type = value
コード例 #19
0
    def create(variable, start, stop, step, children):
        '''Create a NemoLoop instance given valid instances of a variable,
        start, stop and step nodes, and a list of child nodes for the
        loop body.

        :param variable: the PSyIR node containing the variable \
            of the loop iterator.
        :type variable: :py:class:`psyclone.psyir.symbols.DataSymbol`
        :param start: the PSyIR node determining the value for the \
            start of the loop.
        :type start: :py:class:`psyclone.psyir.nodes.Node`
        :param end: the PSyIR node determining the value for the end \
            of the loop.
        :type end: :py:class:`psyclone.psyir.nodes.Node`
        :param step: the PSyIR node determining the value for the loop \
            step.
        :type step: :py:class:`psyclone.psyir.nodes.Node`
        :param children: a list of PSyIR nodes contained in the \
            loop.
        :type children: list of :py:class:`psyclone.psyir.nodes.Node`

        :returns: a NemoLoop instance.
        :rtype: :py:class:`psyclone.nemo.NemoLoop`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        Loop._check_variable(variable)

        if not isinstance(children, list):
            raise GenerationError(
                "children argument in create method of NemoLoop class "
                "should be a list but found '{0}'."
                "".format(type(children).__name__))

        # Create the loop
        loop = NemoLoop(variable=variable)
        schedule = Schedule(parent=loop, children=children)
        loop.children = [start, stop, step, schedule]
        for child in children:
            child.parent = schedule
        start.parent = loop
        stop.parent = loop
        step.parent = loop

        # Indicate the type of loop
        loop_type_mapping = Config.get().api_conf("nemo") \
                                        .get_loop_type_mapping()
        loop.loop_type = loop_type_mapping.get(variable.name, "unknown")
        return loop
コード例 #20
0
    def _check_variable(variable):
        '''The loop variable should be a scalar integer. Check that this is
        the case and raise an exception if not.

        :param variable: the loop iterator.
        :type variable: :py:class:`psyclone.psyir.symbols.DataSymbol`

        :raises GenerationError: if the supplied variable is not a \
            scalar integer.

        '''
        if not isinstance(variable, DataSymbol):
            raise GenerationError(
                "variable property in Loop class should be a DataSymbol but "
                "found '{0}'.".format(type(variable).__name__))
        if not isinstance(variable.datatype, ScalarType):
            raise GenerationError(
                "variable property in Loop class should be a ScalarType but "
                "found '{0}'.".format(type(variable.datatype).__name__))
        if variable.datatype.intrinsic != ScalarType.Intrinsic.INTEGER:
            raise GenerationError(
                "variable property in Loop class should be a scalar integer "
                "but found '{0}'.".format(variable.datatype.intrinsic.name))
コード例 #21
0
    def create(variable, start, stop, step, children):
        '''Create a Loop instance given valid instances of a variable,
        start, stop and step nodes, and a list of child nodes for the
        loop body.

        :param variable: the PSyIR node containing the variable \
            of the loop iterator.
        :type variable: :py:class:`psyclone.psyir.symbols.DataSymbol`
        :param start: the PSyIR node determining the value for the \
            start of the loop.
        :type start: :py:class:`psyclone.psyir.nodes.Node`
        :param end: the PSyIR node determining the value for the end \
            of the loop.
        :type end: :py:class:`psyclone.psyir.nodes.Node`
        :param step: the PSyIR node determining the value for the loop \
            step.
        :type step: :py:class:`psyclone.psyir.nodes.Node`
        :param children: a list of PSyIR nodes contained in the \
            loop.
        :type children: list of :py:class:`psyclone.psyir.nodes.Node`

        :returns: a Loop instance.
        :rtype: :py:class:`psyclone.psyir.nodes.Loop`

        :raises GenerationError: if the arguments to the create method \
            are not of the expected type.

        '''
        Loop._check_variable(variable)

        if not isinstance(children, list):
            raise GenerationError(
                "children argument in create method of Loop class "
                "should be a list but found '{0}'."
                "".format(type(children).__name__))

        loop = Loop(variable=variable)
        schedule = Schedule(parent=loop, children=children)
        loop.children = [start, stop, step, schedule]
        return loop
コード例 #22
0
    def set_options(options):
        '''Sets the option the user required.
        :param options: List of options selected by the user, or None to
                        disable all automatic profiling.
        :type options: List of strings.
        :raises GenerationError: If any option is not KERNELS or INVOKES.
        '''
        # Test that all options are valid
        if options is None:
            options = []  # Makes it easier to test
        for index, option in enumerate(options):
            if option not in [Profiler.INVOKES, Profiler.KERNELS]:
                # Create a 'nice' representation of the allowed options.
                # [1:-1] cuts out the '[' and ']' that surrounding the
                # string of the list.
                allowed_options = str(Profiler.SUPPORTED_OPTIONS)[1:-1]
                raise GenerationError("Error in Profiler.setOptions: options "
                                      "must be one of {0} but found '{1}' "
                                      "at {2}".format(allowed_options,
                                                      str(option), index))

        # Store options so they can be queried later
        Profiler._options = options
コード例 #23
0
ファイル: node.py プロジェクト: mfkiwl/PSyclone
    def is_valid_location(self, new_node, position="before"):
        '''If this Node can be moved to the new_node
        (where position determines whether it is before of after the
        new_node) without breaking any data dependencies then return True,
        otherwise return False.

        :param new_node: Node to which this node should be moved.
        :type new_node: :py:class:`psyclone.psyir.nodes.Node`
        :param str position: either 'before' or 'after'.

        :raises GenerationError: if new_node is not an\
                instance of :py:class:`psyclone.psyir.nodes.Node`.
        :raises GenerationError: if position is not 'before' or 'after'.
        :raises GenerationError: if self and new_node do not have the same\
                parent.
        :raises GenerationError: self and new_node are the same Node.

        :returns: whether or not the specified location is valid for this node.
        :rtype: bool

        '''
        # First perform correctness checks
        # 1: check new_node is a Node
        if not isinstance(new_node, Node):
            raise GenerationError(
                "In the psyir.nodes.Node.is_valid_location() method the "
                "supplied argument is not a Node, it is a '{0}'.".format(
                    type(new_node).__name__))

        # 2: check position has a valid value
        valid_positions = ["before", "after"]
        if position not in valid_positions:
            raise GenerationError(
                "The position argument in the psyGenNode.is_valid_location() "
                "method must be one of {0} but found '{1}'".format(
                    valid_positions, position))

        # 3: check self and new_node have the same parent
        if not self.sameParent(new_node):
            raise GenerationError(
                "In the psyir.nodes.Node.is_valid_location() method "
                "the node and the location do not have the same parent")

        # 4: check proposed new position is not the same as current position
        new_position = new_node.position
        if new_position < self.position and position == "after":
            new_position += 1
        elif new_position > self.position and position == "before":
            new_position -= 1

        if self.position == new_position:
            raise GenerationError(
                "In the psyir.nodes.Node.is_valid_location() method, the "
                "node and the location are the same so this transformation "
                "would have no effect.")

        # Now determine whether the new location is valid in terms of
        # data dependencies
        # Treat forward and backward dependencies separately
        if new_position < self.position:
            # the new_node is before this node in the schedule
            prev_dep_node = self.backward_dependence()
            if not prev_dep_node:
                # There are no backward dependencies so the move is valid
                return True
            # return (is the dependent node before the new_position?)
            return prev_dep_node.position < new_position
        # new_node.position > self.position
        # the new_node is after this node in the schedule
        next_dep_node = self.forward_dependence()
        if not next_dep_node:
            # There are no forward dependencies so the move is valid
            return True
        # return (is the dependent node after the new_position?)
        return next_dep_node.position > new_position
コード例 #24
0
ファイル: visitor_test.py プロジェクト: stfc/PSyclone
 def validate_global_constraints(self):
     raise GenerationError("Fail for testing purposes")
コード例 #25
0
    def generate(self, var_accesses=None):
        # pylint: disable=too-many-statements, too-many-branches
        '''
        Specifies which arguments appear in an argument list, their type
        and their ordering. Calls methods for each type of argument
        that can be specialised by a child class for its particular need.
        If the optional argument var_accesses is supplied, this function
        will also add variable access information for each implicit argument
        (i.e. that is not explicitly listed in kernel metadata) that is
        added. These accesses will be marked as read.

        :param var_accesses: optional VariablesAccessInfo instance that \
            stores the information about variable accesses.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`

        :raises GenerationError: if the kernel arguments break the \
                                 rules for the LFRic API.

        '''
        # Setting this first is important, since quite a few derived classes
        # will access self.arglist during generate() (e.g. to test if an
        # argument is already contained in the argument list).
        self._generate_called = True
        if self._kern.arguments.has_operator():
            # All operator types require the cell index to be provided
            self.cell_position(var_accesses=var_accesses)
        # Pass the number of layers in the mesh unless this kernel is
        # applying a CMA operator or doing a CMA matrix-matrix calculation
        if self._kern.cma_operation not in ["apply", "matrix-matrix"]:
            self.mesh_height(var_accesses=var_accesses)
        # Pass the number of cells in the mesh if this kernel has a
        # LMA operator argument
        # TODO this code should replace the code that currently includes
        # this quantity for *every* operator it encounters.
        # if self._kern.arguments.has_operator(op_type="gh_operator"):
        #     self.mesh_ncell3d()
        # Pass the number of columns in the mesh if this kernel operates on
        # the 'domain' or has a CMA operator argument
        if (self._kern.iterates_over == "domain"
                or self._kern.arguments.has_operator(
                    op_type="gh_columnwise_operator")):
            self.mesh_ncell2d(var_accesses=var_accesses)

        if self._kern.is_intergrid:
            # Inter-grid kernels require special arguments
            # The cell-map for the current column providing the mapping from
            # the coarse to the fine mesh.
            self.cell_map(var_accesses=var_accesses)

        # For each argument in the order they are specified in the
        # kernel metadata, call particular methods depending on what
        # type of argument we find (field, field vector, operator or
        # scalar). If the argument is a field or field vector and also
        # has a stencil access then also call appropriate stencil
        # methods.
        const = LFRicConstants()
        for arg in self._kern.arguments.args:
            if arg.is_field:
                if arg.vector_size > 1:
                    self.field_vector(arg, var_accesses=var_accesses)
                else:
                    self.field(arg, var_accesses=var_accesses)
                if arg.descriptor.stencil:
                    if not arg.descriptor.stencil['extent']:
                        if arg.descriptor.stencil['type'] == "cross2d":
                            # stencil extent is not provided in the
                            # metadata so must be passed from the Algorithm
                            # layer.
                            self.stencil_2d_unknown_extent(
                                arg, var_accesses=var_accesses)
                            # Due to the nature of the stencil extent array
                            # the max size of a stencil branch must be passed
                            # from the Algorithm layer.
                            self.stencil_2d_max_extent(
                                arg, var_accesses=var_accesses)
                        else:
                            # stencil extent is not provided in the
                            # metadata so must be passed from the Algorithm
                            # layer.
                            self.stencil_unknown_extent(
                                arg, var_accesses=var_accesses)
                    if arg.descriptor.stencil['type'] == "xory1d":
                        # if "xory1d is specified then the actual
                        # direction must be passed from the Algorithm layer.
                        self.stencil_unknown_direction(arg, var_accesses)
                    # stencil information that is always passed from the
                    # Algorithm layer.
                    if arg.descriptor.stencil['type'] == "cross2d":
                        self.stencil_2d(arg, var_accesses=var_accesses)
                    else:
                        self.stencil(arg, var_accesses=var_accesses)
            elif arg.argument_type == "gh_operator":
                self.operator(arg, var_accesses=var_accesses)
            elif arg.argument_type == "gh_columnwise_operator":
                self.cma_operator(arg, var_accesses=var_accesses)
            elif arg.is_scalar:
                self.scalar(arg, var_accesses=var_accesses)
            else:
                raise GenerationError(
                    "ArgOrdering.generate(): Unexpected argument type found. "
                    "Expected one of '{0}' but found '{1}'".format(
                        const.VALID_ARG_TYPE_NAMES, arg.argument_type))
        # For each function space (in the order they appear in the
        # metadata arguments)
        for unique_fs in self._kern.arguments.unique_fss:
            # Provide arguments common to LMA operators and fields on
            # a space *unless* this is an inter-grid or CMA
            # matrix-matrix kernel
            if self._kern.cma_operation not in ["matrix-matrix"] and \
               not self._kern.is_intergrid:
                self.fs_common(unique_fs, var_accesses=var_accesses)
            # Provide additional arguments if there is a
            # field on this space
            if unique_fs.field_on_space(self._kern.arguments):
                if self._kern.is_intergrid:
                    self.fs_intergrid(unique_fs, var_accesses=var_accesses)
                else:
                    self.fs_compulsory_field(unique_fs,
                                             var_accesses=var_accesses)
            cma_op = unique_fs.cma_on_space(self._kern.arguments)
            if cma_op:
                if self._kern.cma_operation == "assembly":
                    # CMA-assembly requires banded dofmaps
                    self.banded_dofmap(unique_fs, var_accesses=var_accesses)
                elif self._kern.cma_operation == "apply":
                    # Applying a CMA operator requires indirection dofmaps
                    self.indirection_dofmap(unique_fs,
                                            operator=cma_op,
                                            var_accesses=var_accesses)

            # Provide any optional arguments. These arguments are
            # associated with the keyword arguments (basis function
            # and differential basis function) for a function space.
            if self._kern.fs_descriptors.exists(unique_fs):
                descriptors = self._kern.fs_descriptors
                descriptor = descriptors.get_descriptor(unique_fs)
                if descriptor.requires_basis:
                    self.basis(unique_fs, var_accesses=var_accesses)
                if descriptor.requires_diff_basis:
                    self.diff_basis(unique_fs, var_accesses=var_accesses)
            # Fix for boundary_dofs array to the boundary condition
            # kernel (enforce_bc_kernel) arguments
            if self._kern.name.lower() == "enforce_bc_code" and \
               unique_fs.orig_name.lower() == "any_space_1":
                self.field_bcs_kernel(unique_fs, var_accesses=var_accesses)

        # Add boundary dofs array to the operator boundary condition
        # kernel (enforce_operator_bc_kernel) arguments
        if self._kern.name.lower() == "enforce_operator_bc_code":
            # Sanity checks - this kernel should only have a single LMA
            # operator as argument
            if len(self._kern.arguments.args) > 1:
                raise GenerationError(
                    "Kernel {0} has {1} arguments when it should only have 1 "
                    "(an LMA operator)".format(self._kern.name,
                                               len(self._kern.arguments.args)))
            op_arg = self._kern.arguments.args[0]
            if op_arg.argument_type != "gh_operator":
                raise GenerationError(
                    "Expected an LMA operator from which to look-up boundary "
                    "dofs but kernel {0} has argument {1}.".format(
                        self._kern.name, op_arg.argument_type))
            if op_arg.access != AccessType.READWRITE:
                raise GenerationError(
                    "Kernel {0} is recognised as a kernel which applies "
                    "boundary conditions to an operator. However its operator "
                    "argument has access {1} rather than gh_readwrite.".format(
                        self._kern.name, op_arg.access.api_specific_name()))
            self.operator_bcs_kernel(op_arg.function_space_to,
                                     var_accesses=var_accesses)

        # Reference-element properties
        if self._kern.reference_element:
            self.ref_element_properties(var_accesses=var_accesses)

        # Mesh properties
        if self._kern.mesh:
            self.mesh_properties(var_accesses=var_accesses)

        # Provide qr arguments if required
        if self._kern.qr_required:
            self.quad_rule(var_accesses=var_accesses)
コード例 #26
0
def adduse(location, name, only=None, funcnames=None):
    '''Add a Fortran 'use' statement to an existing fparser2 parse
    tree. This will be added at the first valid location before the
    current location.

    This function should be part of the fparser2 replacement for
    f2pygen (which uses fparser1) but is kept here until this is
    developed, see issue #240.

    :param location: the current location (node) in the parse tree to which \
                     to add a USE.
    :type location: :py:class:`fparser.two.utils.Base`
    :param str name: the name of the use statement.
    :param bool only: whether to include the 'only' clause in the use \
        statement or not. Defaults to None which will result in only being \
        added if funcnames has content and not being added otherwise.
    :param funcnames: a list of names to include in the use statement's \
        only list. If the list is empty or None then nothing is \
        added. Defaults to None.
    :type funcnames: list of str

    :raises GenerationError: if no suitable enclosing program unit is found \
                             for the provided location.
    :raises NotImplementedError: if the type of parent node is not supported.
    :raises InternalError: if the parent node does not have the expected \
                           structure.
    '''
    # pylint: disable=too-many-locals
    # pylint: disable=too-many-branches
    from fparser.two.Fortran2003 import Main_Program, Module, \
        Subroutine_Subprogram, Function_Subprogram, Use_Stmt, \
        Specification_Part
    from fparser.two.utils import Base
    from psyclone.errors import GenerationError, InternalError

    if not isinstance(location, Base):
        raise GenerationError(
            "alg_gen.py:adduse: Location argument must be a sub-class of "
            "fparser.two.utils.Base but got: {0}.".format(
                type(location).__name__))

    if funcnames:
        # funcnames have been provided for the only clause.
        if only is False:
            # However, the only clause has been explicitly set to False.
            raise GenerationError(
                "alg_gen.py:adduse: If the 'funcnames' argument is provided "
                "and has content, then the 'only' argument must not be set "
                "to 'False'.")
        if only is None:
            # only has not been specified so set it to True as it is
            # required when funcnames has content.
            only = True

    if only is None:
        # only has not been specified and we can therefore infer that
        # funcnames is empty or is not provided (as earlier code would
        # have set only to True otherwise) so only is not required.
        only = False

    # Create the specified use statement
    only_str = ""
    if only:
        only_str = ", only :"
    my_funcnames = funcnames
    if funcnames is None:
        my_funcnames = []
    use = Use_Stmt("use {0}{1} {2}".format(name, only_str,
                                           ", ".join(my_funcnames)))

    # find the parent program statement containing the specified location
    parent_prog_statement = None
    current = location
    while current:
        if isinstance(current, (Main_Program, Module, Subroutine_Subprogram,
                                Function_Subprogram)):
            parent_prog_statement = current
            break
        current = current.parent
    else:
        raise GenerationError(
            "The specified location is invalid as it has no parent in the "
            "parse tree that is a program, module, subroutine or function.")

    if not isinstance(parent_prog_statement,
                      (Main_Program, Subroutine_Subprogram,
                       Function_Subprogram)):
        # We currently only support program, subroutine and function
        # as ancestors
        raise NotImplementedError(
            "alg_gen.py:adduse: Unsupported parent code found '{0}'. "
            "Currently support is limited to program, subroutine and "
            "function.".format(str(type(parent_prog_statement))))
    if not isinstance(parent_prog_statement.content[1], Specification_Part):
        raise InternalError(
            "alg_gen.py:adduse: The second child of the parent code "
            "(content[1]) is expected to be a specification part but "
            "found '{0}'.".format(repr(parent_prog_statement.content[1])))

    # add the use statement as the first child of the specification
    # part of the program
    spec_part = parent_prog_statement.content[1]
    spec_part.content.insert(0, use)
コード例 #27
0
def handle_script(script_name, psy):
    '''Loads and applies the specified script to the given psy layer.
    The 'trans' function of the script is called with psy as parameter.
    :param script_name: Name of the script to load.
    :type script_name: string
    :param psy: The psy layer to which the script is applied.
    :type psy: :py:class:`psyclone.psyGen.PSy`
    :raises IOError: If the file is not found.
    :raises GenerationError: if the file does not have .py extension
        or can not be imported.
    :raises GenerationError: if trans() can not be called.
    :raises GenerationError: if any exception is raised when trans()
        was called.
    '''
    sys_path_appended = False
    try:
        # a script has been provided
        filepath, filename = os.path.split(script_name)
        if filepath:
            # a path to a file has been provided
            # we need to check the file exists
            if not os.path.isfile(script_name):
                raise IOError(
                    "script file '{0}' not found".format(script_name))
            # it exists so we need to add the path to the python
            # search path
            sys_path_appended = True
            sys.path.append(filepath)
        filename, fileext = os.path.splitext(filename)
        if fileext != '.py':
            raise GenerationError(
                "generator: expected the script file '{0}' to have "
                "the '.py' extension".format(filename))
        try:
            transmod = __import__(filename)
        except ImportError:
            raise GenerationError(
                "generator: attempted to import '{0}' but script file "
                "'{1}' has not been found".format(filename, script_name))
        except SyntaxError:
            raise GenerationError(
                "generator: attempted to import '{0}' but script file "
                "'{1}' is not valid python".format(filename, script_name))
        if callable(getattr(transmod, 'trans', None)):
            try:
                psy = transmod.trans(psy)
            except Exception:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                lines = traceback.format_exception(exc_type, exc_value,
                                                   exc_traceback)
                e_str = '{\n' +\
                    ''.join('    ' + line for line in lines[2:]) + '}'
                raise GenerationError(
                    "Generator: script file '{0}'\nraised the "
                    "following exception during execution "
                    "...\n{1}\nPlease check your script".format(
                        script_name, e_str))
        else:
            raise GenerationError(
                "generator: attempted to import '{0}' but script file "
                "'{1}' does not contain a 'trans()' function".format(
                    filename, script_name))
    except Exception as msg:
        if sys_path_appended:
            os.sys.path.pop()
        raise msg
    if sys_path_appended:
        os.sys.path.pop()
コード例 #28
0
def generate(filename,
             api="",
             kernel_path="",
             script_name=None,
             line_length=False,
             distributed_memory=None,
             kern_out_path="",
             kern_naming="multiple"):
    # pylint: disable=too-many-arguments
    '''Takes a PSyclone algorithm specification as input and outputs the
    associated generated algorithm and psy codes suitable for
    compiling with the specified kernel(s) and support
    infrastructure. Uses the :func:`parse.algorithm.parse` function to
    parse the algorithm specification, the :class:`psyGen.PSy` class
    to generate the PSy code and the :class:`alg_gen.Alg` class to
    generate the modified algorithm code.

    :param str filename: The file containing the algorithm specification.
    :param str kernel_path: The directory from which to recursively \
                            search for the files containing the kernel \
                            source (if different from the location of the \
                            algorithm specification).
    :param str script_name: A script file that can apply optimisations \
                            to the PSy layer (can be a path to a file or \
                            a filename that relies on the PYTHONPATH to \
                            find the module).
    :param bool line_length: A logical flag specifying whether we care \
                             about line lengths being longer than 132 \
                             characters. If so, the input (algorithm \
                             and kernel) code is checked to make sure \
                             that it conforms. The default is False.
    :param bool distributed_memory: A logical flag specifying whether to \
                                    generate distributed memory code. The \
                                    default is set in the config.py file.
    :param str kern_out_path: Directory to which to write transformed \
                              kernel code.
    :param bool kern_naming: the scheme to use when re-naming transformed \
                             kernels.
    :return: 2-tuple containing fparser1 ASTs for the algorithm code and \
             the psy code.
    :rtype: (:py:class:`fparser.one.block_statements.BeginSource`, \
             :py:class:`fparser.one.block_statements.Module`)

    :raises IOError: if the filename or search path do not exist
    :raises GenerationError: if an invalid API is specified.
    :raises GenerationError: if an invalid kernel-renaming scheme is specified.

    For example:

    >>> from psyclone.generator import generate
    >>> alg, psy = generate("algspec.f90")
    >>> alg, psy = generate("algspec.f90", kernel_path="src/kernels")
    >>> alg, psy = generate("algspec.f90", script_name="optimise.py")
    >>> alg, psy = generate("algspec.f90", line_length=True)
    >>> alg, psy = generate("algspec.f90", distributed_memory=False)

    '''

    if distributed_memory is None:
        distributed_memory = Config.get().distributed_memory

    # pylint: disable=too-many-statements, too-many-locals, too-many-branches
    if api == "":
        api = Config.get().default_api
    else:
        if api not in Config.get().supported_apis:
            raise GenerationError(
                "generate: Unsupported API '{0}' specified. Supported "
                "types are {1}.".format(api,
                                        Config.get().supported_apis))

    # Store Kernel-output options in our Configuration object
    Config.get().kernel_output_dir = kern_out_path
    try:
        Config.get().kernel_naming = kern_naming
    except ValueError as verr:
        raise GenerationError(
            "Invalid kernel-renaming scheme supplied: {0}".format(str(verr)))

    if not os.path.isfile(filename):
        raise IOError("file '{0}' not found".format(filename))
    if kernel_path and not os.access(kernel_path, os.R_OK):
        raise IOError("kernel search path '{0}' not found".format(kernel_path))
    try:
        from psyclone.alg_gen import Alg
        ast, invoke_info = parse(filename,
                                 api=api,
                                 invoke_name="invoke",
                                 kernel_path=kernel_path,
                                 line_length=line_length)
        psy = PSyFactory(api, distributed_memory=distributed_memory)\
            .create(invoke_info)
        if script_name is not None:
            handle_script(script_name, psy)

        # Add profiling nodes to schedule if automatic profiling has
        # been requested.
        from psyclone.psyir.nodes import Loop
        for invoke in psy.invokes.invoke_list:
            Profiler.add_profile_nodes(invoke.schedule, Loop)

        if api not in API_WITHOUT_ALGORITHM:
            alg_gen = Alg(ast, psy).gen
        else:
            alg_gen = None
    except Exception:
        raise

    return alg_gen, psy.gen