Пример #1
0
def test_progunitgen_multiple_use1():
    '''Check that we correctly handle the case where duplicate use statements
    are added but one is specific'''
    module = ModuleGen(name="testmodule")
    sub = SubroutineGen(module, name="testsubroutine")
    module.add(sub)
    sub.add(UseGen(sub, name="fred"))
    sub.add(UseGen(sub, name="fred", only=True, funcnames=["astaire"]))
    assert count_lines(sub.root, "USE fred") == 1
Пример #2
0
def test_progunitgen_multiple_generic_use():
    '''Check that we correctly handle the case where duplicate use statements
    are added'''
    module = ModuleGen(name="testmodule")
    sub = SubroutineGen(module, name="testsubroutine")
    module.add(sub)
    sub.add(UseGen(sub, name="fred"))
    sub.add(UseGen(sub, name="fred"))
    assert count_lines(sub.root, "USE fred") == 1
Пример #3
0
def test_progunitgen_multiple_use2():
    '''Check that we correctly handle the case where the same module
    appears in two use statements but, because the first use is
    specific, the second, generic use is included.

    '''
    module = ModuleGen(name="testmodule")
    sub = SubroutineGen(module, name="testsubroutine")
    module.add(sub)
    sub.add(UseGen(sub, name="fred", only=True, funcnames=["astaire"]))
    sub.add(UseGen(sub, name="fred"))
    assert count_lines(sub.root, "USE fred") == 2
Пример #4
0
def test_progunit_multiple_use3():
    '''Check that we correctly handle the case where the same module is
    specified in two UseGen objects statements both of which are
    specific and they have overlapping variable names.

    '''
    module = ModuleGen(name="testmodule")
    sub = SubroutineGen(module, name="testsubroutine")
    module.add(sub)
    funcnames = ["a", "b", "c"]
    sub.add(UseGen(sub, name="fred", only=True, funcnames=funcnames))
    funcnames = ["c", "d"]
    sub.add(UseGen(sub, name="fred", only=True, funcnames=funcnames))
    gen = str(sub.root)
    expected = ("      USE fred, ONLY: d\n" "      USE fred, ONLY: a, b, c")
    assert expected in gen
    assert count_lines(sub.root, "USE fred") == 2
    # ensure that the input list does not get modified
    assert funcnames == ["c", "d"]
Пример #5
0
    def gen(self):
        '''
        Generate PSy code for the GOcean api v.1.0.

        :rtype: ast

        '''
        from psyclone.f2pygen import ModuleGen, UseGen

        # create an empty PSy layer module
        psy_module = ModuleGen(self.name)
        # include the kind_params module
        psy_module.add(UseGen(psy_module, name="kind_params_mod"))
        # include the field_mod module
        psy_module.add(UseGen(psy_module, name="field_mod"))
        # add in the subroutines for each invocation
        self.invokes.gen_code(psy_module)
        # inline kernels where requested
        self.inline(psy_module)
        return psy_module.root
Пример #6
0
def test_imp_none_in_module_with_use_and_decs():
    ''' test that implicit none is added after any use statements
    and before any declarations in a module when auto (the
    default) is used for insertion'''
    module = ModuleGen(name="testmodule", implicitnone=False)
    module.add(DeclGen(module, datatype="integer", entity_decls=["var1"]))
    module.add(TypeDeclGen(module, datatype="my_type", entity_decls=["type1"]))
    module.add(UseGen(module, "fred"))
    module.add(ImplicitNoneGen(module))
    in_idx = line_number(module.root, "IMPLICIT NONE")
    assert in_idx == 2
Пример #7
0
    def gen(self):
        '''
        Generate PSy code for the GOcean api.

        :rtype: ast

        '''
        from psyclone.f2pygen import ModuleGen, UseGen

        # create an empty PSy layer module
        psy_module = ModuleGen(self.name)
        # include the kind_params module
        psy_module.add(UseGen(psy_module, name="kind_params_mod"))
        # include the topology_mod module for loop bounds
        psy_module.add(UseGen(psy_module, name="topology_mod"))
        # include the field_mod module in case we have any r-space variables
        psy_module.add(
            UseGen(psy_module, name="field_mod", only=["scalar_field_type"]))
        # add in the subroutines for each invocation
        self.invokes.gen_code(psy_module)
        return psy_module.root
Пример #8
0
def test_imp_none_in_module_with_use_and_decs_and_comments():
    ''' test that implicit none is added after any use statements
    and before any declarations in a module in the presence of
    comments when auto (the default) is used for insertion'''
    module = ModuleGen(name="testmodule", implicitnone=False)
    module.add(DeclGen(module, datatype="integer", entity_decls=["var1"]))
    module.add(TypeDeclGen(module, datatype="my_type", entity_decls=["type1"]))
    module.add(UseGen(module, "fred"))
    for idx in [0, 1, 2, 3]:
        module.add(CommentGen(module, " hello " + str(idx)),
                   position=["before_index", 2 * idx])
    module.add(ImplicitNoneGen(module))
    in_idx = line_number(module.root, "IMPLICIT NONE")
    assert in_idx == 3
Пример #9
0
def test_if_add_use():
    ''' Check that IfThenGen.add() correctly handles the case
    when it is passed a UseGen object '''
    module = ModuleGen(name="testmodule")
    clause = "a < b"
    if_statement = IfThenGen(module, clause)
    if_statement.add(CommentGen(if_statement, "GOODBYE"))
    if_statement.add(UseGen(if_statement, name="dibna"))
    module.add(if_statement)
    print str(module.root)
    use_line = line_number(module.root, "USE dibna")
    if_line = line_number(module.root, "IF (" + clause + ") THEN")
    # The use statement must come before the if..then block
    assert use_line < if_line
Пример #10
0
 def gen_code(self, parent):
     ''' Generates GOcean specific psy code for a call to the dynamo
         kernel instance. '''
     from psyclone.f2pygen import CallGen, UseGen
     arguments = ["i", "j"]
     for arg in self._arguments.args:
         if arg.space.lower() == "r":
             arguments.append(arg.name + "%data")
         else:
             arguments.append(arg.name)
     parent.add(CallGen(parent, self._name, arguments))
     if not self.module_inline:
         parent.add(UseGen(parent, name=self._module_name, only=True,
                           funcnames=[self._name]))
Пример #11
0
    def gen_code(self, parent):
        # pylint: disable=arguments-differ
        '''Creates the profile start and end calls, surrounding the children
        of this node.

        :param parent: the parent of this node.
        :type parent: :py:class:`psyclone.psyGen.Node`

        '''
        if self._module_name is None or self._region_name is None:
            # Find the first kernel and use its name. In an untransformed
            # Schedule there should be only one kernel, but if Profile is
            # invoked after e.g. a loop merge more kernels might be there.
            region_name = "unknown-kernel"
            module_name = "unknown-module"
            for kernel in self.walk(Kern):
                region_name = kernel.name
                module_name = kernel.module_name
                break
            if self._region_name is None:
                self._region_name = Profiler.create_unique_region(region_name)
            if self._module_name is None:
                self._module_name = module_name

        # Note that adding a use statement makes sure it is only
        # added once, so we don't need to test this here!
        use = UseGen(parent,
                     self.fortran_module,
                     only=True,
                     funcnames=["ProfileData, ProfileStart, ProfileEnd"])
        parent.add(use)
        prof_var_decl = TypeDeclGen(parent,
                                    datatype="ProfileData",
                                    entity_decls=[self._var_name],
                                    save=True)
        parent.add(prof_var_decl)

        prof_start = CallGen(parent, "ProfileStart", [
            "\"{0}\"".format(self._module_name), "\"{0}\"".format(
                self._region_name), self._var_name
        ])
        parent.add(prof_start)

        for child in self.children:
            child.gen_code(parent)

        prof_end = CallGen(parent, "ProfileEnd", [self._var_name])
        parent.add(prof_end)
Пример #12
0
    def gen(self):
        '''
        Generate PSy code for the Dynamo0.1 api.

        :rtype: ast

        '''
        from psyclone.f2pygen import ModuleGen, UseGen

        # create an empty PSy layer module
        psy_module = ModuleGen(self.name)
        # include the lfric module
        lfric_use = UseGen(psy_module, name="lfric")
        psy_module.add(lfric_use)
        # add all invoke specific information
        self.invokes.gen_code(psy_module)
        return psy_module.root
Пример #13
0
    def gen_code(self, parent):
        ''' Generates GOcean v1.0 specific psy code for a call to the dynamo
            kernel instance. '''
        from psyclone.f2pygen import CallGen, UseGen

        # Before we do anything else, go through the arguments and
        # determine the best one from which to obtain the grid properties.
        grid_arg = self._find_grid_access()

        # A GOcean 1.0 kernel always requires the [i,j] indices of the
        # grid-point that is to be updated
        arguments = ["i", "j"]
        for arg in self._arguments.args:

            if arg.type == "scalar":
                # Scalar arguments require no de-referencing
                arguments.append(arg.name)
            elif arg.type == "field":
                # Field objects are Fortran derived-types
                arguments.append(arg.name + "%data")
            elif arg.type == "grid_property":
                # Argument is a property of the grid which we can access via
                # the grid member of any field object.
                # We use the most suitable field as chosen above.
                if grid_arg is None:
                    raise GenerationError(
                        "Error: kernel {0} requires grid property {1} but "
                        "does not have any arguments that are fields".format(
                            self._name, arg.name))
                else:
                    arguments.append(grid_arg.name + "%grid%" + arg.name)
            else:
                raise GenerationError("Kernel {0}, argument {1} has "
                                      "unrecognised type: {2}".format(
                                          self._name, arg.name, arg.type))

        parent.add(CallGen(parent, self._name, arguments))
        if not self.module_inline:
            parent.add(
                UseGen(parent,
                       name=self._module_name,
                       only=True,
                       funcnames=[self._name]))
Пример #14
0
    def create_driver(self, input_list, output_list):
        # pylint: disable=too-many-locals, too-many-statements
        '''This function creates a driver that can read the
        output created by the extraction code. This is a stand-alone
        program that will read the input data, calls the kernels/
        instrumented region, and then compares the results with the
        stored results in the file.

        TODO: #644: we need type information here.

        :param input_list: list of variables that are input parameters.
        :type input_list: list of str
        :param output_list: list of variables that are output parameters.
        :type output_list: list or str
        '''

        from psyclone.f2pygen import AllocateGen, AssignGen, CallGen,\
            CommentGen, DeclGen, ModuleGen, SubroutineGen, UseGen, \
            TypeDeclGen
        from psyclone.gocean1p0 import GOSymbolTable
        from psyclone.psyir.symbols import Symbol

        all_vars = list(set(input_list).union(set(output_list)))
        all_vars.sort()

        module_name, region_name = self.region_identifier
        module = ModuleGen(name=module_name)
        prog = SubroutineGen(parent=module, name=module_name+"_code",
                             implicitnone=True)
        module.add(prog)
        use = UseGen(prog, self.add_psydata_class_prefix("psy_data_mod"),
                     only=True,
                     funcnames=[self.add_psydata_class_prefix("PSyDataType")])
        prog.add(use)

        # Use a symbol table to make sure all variable names are unique
        sym_table = GOSymbolTable()
        sym = Symbol("PSyDataType")
        sym_table.add(sym)

        psy_data = sym_table.new_symbol_name(self.add_psydata_class_prefix
                                             ("psy_data"))
        sym_table.add(Symbol(psy_data))
        var_decl = TypeDeclGen(prog, datatype=self.add_psydata_class_prefix
                               ("PSyDataType"),
                               entity_decls=[psy_data])
        prog.add(var_decl)

        call = CallGen(prog,
                       "{0}%OpenRead(\"{1}\", \"{2}\")"
                       .format(psy_data, module_name, region_name))
        prog.add(call)

        post_suffix = self._post_name

        # Variables might need to be renamed in order to guarantee unique
        # variable names in the driver: An example of this would be if the
        # user code contains a variable 'dx', and the kernel takes a
        # property 'dx' as well. In the original code that is no problem,
        # since the property is used via field%grid%dx. But the stand-alone
        # driver renames field%grid%dx to dx, which can cause a name clash.
        # Similar problems can exist with any user defined type, since all
        # user defined types are rewritten to just use the field name.
        # We use a mapping to support renaming of variables: it takes as
        # key the variable as used in the original program (e.g. 'dx' from
        # an expression like field%grid%dx), and maps it to a unique local
        # name (e.g. dx_0).

        rename_variable = {}
        for var_name in all_vars:
            # TODO #644: we need to identify arrays!!
            # Support GOcean properties, which are accessed via a
            # derived type (e.g. 'fld%grid%dx'). In this stand-alone
            # driver we don't have the derived type, instead we create
            # variable based on the field in the derived type ('dx'
            # in the example above), and pass this variable to the
            # instrumented code.
            last_percent = var_name.rfind("%")
            if last_percent > -1:
                # Strip off the derived type, and only leave the last
                # field, which is used as the local variable name.
                local_name = var_name[last_percent+1:]
            else:
                # No derived type, so we can just use the
                # variable name directly in the driver
                local_name = var_name
            unique_local_name = sym_table.new_symbol_name(local_name)
            rename_variable[local_name] = unique_local_name
            sym_table.add(Symbol(unique_local_name))
            local_name = unique_local_name

            # TODO: #644 - we need to identify arrays!!
            # Any variable used needs to be defined. We also need
            # to handle the kind property better and not rely on
            # a hard-coded value.
            decl = DeclGen(prog, "real", [local_name], kind="8",
                           dimension=":,:", allocatable=True)
            prog.add(decl)
            is_input = var_name in input_list
            is_output = var_name in output_list

            if is_input and not is_output:
                # We only need the pre-variable, and we can read
                # it from the file (this call also allocates space for it).
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}\", {2})"
                               .format(psy_data, var_name, local_name))
                prog.add(call)
            elif is_input:
                # Now must be input and output:
                # First read the pre-variable (which also allocates it):
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}\", {2})"
                               .format(psy_data, var_name, local_name))
                prog.add(call)
                # Then declare the post variable, and and read its values
                # (ReadVariable will also allocate it)
                sym = Symbol(local_name+post_suffix)
                sym_table.add(sym)
                decl = DeclGen(prog, "real", [local_name+post_suffix],
                               dimension=":,:", kind="8", allocatable=True)
                prog.add(decl)
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}{3}\", {2}{3})"
                               .format(psy_data, var_name, local_name,
                                       post_suffix))
                prog.add(call)
            else:
                # Now the variable is output only. We need to read the
                # post variable in, and create and allocate a pre variable
                # with the same size as the post
                sym = Symbol(local_name+post_suffix)
                sym_table.add(sym)
                decl = DeclGen(prog, "real", [local_name+post_suffix],
                               dimension=":,:", kind="8", allocatable=True)
                prog.add(decl)
                call = CallGen(prog,
                               "{0}%ReadVariable(\"{1}{3}\", {2}{3})"
                               .format(psy_data, var_name, local_name,
                                       post_suffix))
                prog.add(call)
                decl = DeclGen(prog, "real", [local_name], kind="8",
                               dimension=":,:", allocatable=True)
                prog.add(decl)
                alloc = AllocateGen(prog, [var_name],
                                    mold="{0}".format(local_name +
                                                      post_suffix))
                prog.add(alloc)
                # Initialise the variable with 0, since it might contain
                # values that are not set at all (halo regions, or a
                # kernel might not set all values). This way the array
                # comparison with the post value works as expected
                # TODO #644 - create the right "0.0" type here (e.g.
                # 0.0d0, ...)
                assign = AssignGen(prog, local_name, "0.0d0")
                prog.add(assign)

        # Now add the region that was extracted here:
        prog.add(CommentGen(prog, ""))
        prog.add(CommentGen(prog, " RegionStart"))

        # For the driver we have to re-create the code of the
        # instrumented region, but in this stand-alone driver the
        # arguments are not dl_esm_inf fields anymore, but simple arrays.
        # Similarly, for properties we cannot use e.g. 'fld%grid%dx'
        # anymore, we have to use e.g. a local variable 'dx' that has
        # been created. Since we are using the existing way of creating
        # the code for the instrumented region, we need to modify how
        # these variables are created. We do this by temporarily
        # modifying the properties in the config file.
        api_config = Config.get().api_conf("gocean1.0")
        all_props = api_config.grid_properties
        # Keep a copy of the original values, so we can restore
        # them later
        orig_props = dict(all_props)

        # 1) A grid property is defined like "{0}%grid%dx". This is
        #    changed to be just 'dx', i.e. the final component of
        #    the current value (but we also take renaming into account,
        #    so 'dx' might become 'dx_0').
        #    If a property is not used, it doesn't matter if we modify
        #    its definition, so we just change all properties.
        for name, prop in all_props.items():
            last_percent = prop.fortran.rfind("%")
            if last_percent > -1:
                # Get the last field name, which will be the
                # local variable name
                local_name = prop.fortran[last_percent+1:]
                unique_name = rename_variable.get(local_name, local_name)
                all_props[name] = GOceanConfig.make_property(
                    unique_name, prop.type, prop.intrinsic_type)

        # 2) The property 'grid_data' is a reference to the data on the
        #    grid (i.e. the actual field) , and it is defined as "{0}%data".
        #    This just becomes {0} ('a_fld%data' in the original program
        #    becomes just 'a_fld', and 'a_fld' is declared to be a plain
        #    Fortran 2d-array)
        all_props["go_grid_data"] = GOceanConfig.make_property(
            "{0}", "array", "real")

        # Each kernel caches the argument code, so we also
        # need to clear this cached data to make sure the new
        # value for "go_grid_data" is actually used.
        from psyclone.psyGen import CodedKern
        for kernel in self.psy_data_body.walk(CodedKern):
            kernel.clear_cached_data()

        # Recreate the instrumented region. Due to the changes in the
        # config files, fields and properties will now become local
        # plain arrays and variables:
        for child in self.psy_data_body:
            child.gen_code(prog)

        # Now reset all properties back to the original values:
        for name in all_props.keys():
            all_props[name] = orig_props[name]

        prog.add(CommentGen(prog, " RegionEnd"))
        prog.add(CommentGen(prog, ""))

        for var_name in output_list:
            prog.add(CommentGen(prog, " Check {0}".format(var_name)))

        code = str(module.root)

        with open("driver-{0}-{1}.f90".
                  format(module_name, region_name), "w") as out:
            out.write(code)
Пример #15
0
    def gen_code(self, parent, options=None):
        # pylint: disable=arguments-differ, too-many-branches
        '''Creates the PSyData code before and after the children
        of this node.

        :param parent: the parent of this node in the f2pygen AST.
        :type parent: :py:class:`psyclone.f2pygen.BaseGen`
        :param options: a dictionary with options for transformations.
        :type options: dictionary of string:values or None
        :param options["pre_var_list"]: a list of variables to be extracted \
            before the first child.
        :type options["pre_var_list"]: list of str
        :param options["post_var_list"]: a list of variables to be extracted \
            after the last child.
        :type options["post_var_list"]: list of str
        :param str options["pre_var_postfix"]: an optional postfix that will \
            be added to each variable name in the pre_var_list.
        :param str options["post_var_postfix"]: an optional postfix that will \
            be added to each variable name in the post_var_list.

        '''
        # Avoid circular dependency
        # pylint: disable=import-outside-toplevel
        from psyclone.psyGen import Kern, InvokeSchedule
        # TODO: #415 Support different classes of PSyData calls.
        invoke = self.ancestor(InvokeSchedule).invoke
        module_name = self._module_name
        if module_name is None:
            # The user has not supplied a module (location) name so
            # return the psy-layer module name as this will be unique
            # for each PSyclone algorithm file.
            module_name = invoke.invokes.psy.name

        region_name = self._region_name
        if region_name is None:
            # The user has not supplied a region name (to identify
            # this particular invoke region). Use the invoke name as a
            # starting point.
            region_name = invoke.name
            kerns = self.walk(Kern)
            if len(kerns) == 1:
                # This PSyData region only has one kernel within it,
                # so append the kernel name.
                region_name += ":{0}".format(kerns[0].name)
            # Add a region index to ensure uniqueness when there are
            # multiple regions in an invoke.
            psy_data_nodes = self.root.walk(PSyDataNode)
            idx = psy_data_nodes.index(self)
            region_name += ":r{0}".format(idx)

        if not options:
            options = {}

        pre_variable_list = options.get("pre_var_list", [])
        post_variable_list = options.get("post_var_list", [])
        pre_suffix = options.get("pre_var_postfix", "")
        post_suffix = options.get("post_var_postfix", "")

        # Note that adding a use statement makes sure it is only
        # added once, so we don't need to test this here!
        use = UseGen(parent, self.fortran_module, only=True,
                     funcnames=[sym.name for sym in self.imported_symbols])
        parent.add(use)
        var_decl = TypeDeclGen(parent,
                               datatype=self.type_name,
                               entity_decls=[self._var_name],
                               save=True, target=True)
        parent.add(var_decl)

        self._add_call("PreStart", parent,
                       ["\"{0}\"".format(module_name),
                        "\"{0}\"".format(region_name),
                        len(pre_variable_list),
                        len(post_variable_list)])
        self.set_region_identifier(module_name, region_name)
        has_var = pre_variable_list or post_variable_list

        # Each variable name can be given a suffix. The reason for
        # this feature is that a library might have to distinguish if
        # a variable is both in the pre- and post-variable list.
        # Consider a NetCDF file that is supposed to store a
        # variable that is read (i.e. it is in the pre-variable
        # list) and written (it is also in the post-variable
        # list). Since a NetCDF file uses the variable name as a key,
        # there must be a way to distinguish these two variables.
        # The application could for example give all variables in
        # the post-variable list a suffix like "_post" to create
        # a different key in the NetCDF file, allowing it to store
        # values of a variable "A" as "A" in the pre-variable list,
        # and store the modified value of "A" later as "A_post".
        if has_var:
            for var_name in pre_variable_list:
                self._add_call("PreDeclareVariable", parent,
                               ["\"{0}{1}\"".format(var_name, pre_suffix),
                                var_name])
            for var_name in post_variable_list:
                self._add_call("PreDeclareVariable", parent,
                               ["\"{0}{1}\"".format(var_name, post_suffix),
                                var_name])

            self._add_call("PreEndDeclaration", parent)

            for var_name in pre_variable_list:
                self._add_call("ProvideVariable", parent,
                               ["\"{0}{1}\"".format(var_name, pre_suffix),
                                var_name])

            self._add_call("PreEnd", parent)

        for child in self.psy_data_body:
            child.gen_code(parent)

        if has_var:
            # Only add PostStart() if there is at least one variable.
            self._add_call("PostStart", parent)
            for var_name in post_variable_list:
                self._add_call("ProvideVariable", parent,
                               ["\"{0}{1}\"".format(var_name, post_suffix),
                                var_name])

        self._add_call("PostEnd", parent)
Пример #16
0
    def gen_code(self, parent):
        ''' Generates dynamo version 0.1 specific psy code for a call to
            the dynamo kernel instance. '''
        from psyclone.f2pygen import CallGen, DeclGen, AssignGen, UseGen

        # TODO: we simply choose the first field as the lookup for the moment
        field_name = self.arguments.args[0].name

        # add a dofmap lookup using first field.
        # TODO: This needs to be generalised to work for multiple dofmaps
        parent.add(
            CallGen(parent, field_name + "%vspace%get_cell_dofmap",
                    ["cell", "map"]))
        parent.add(DeclGen(parent, datatype="integer", entity_decls=["cell"]))
        parent.add(
            DeclGen(parent,
                    datatype="integer",
                    pointer=True,
                    entity_decls=["map(:)"]))

        # create the argument list on the fly so we can also create
        # appropriate variables and lookups
        arglist = []
        arglist.append("nlayers")
        arglist.append("ndf")
        arglist.append("map")

        found_gauss_quad = False
        gauss_quad_arg = None
        for arg in self._arguments.args:
            if arg.requires_basis:
                basis_name = arg.function_space + "_basis_" + arg.name
                arglist.append(basis_name)
                new_parent, position = parent.start_parent_loop()
                new_parent.add(CallGen(new_parent,
                                       field_name + "%vspace%get_basis",
                                       [basis_name]),
                               position=["before", position])
                parent.add(
                    DeclGen(parent,
                            datatype="real",
                            kind="dp",
                            pointer=True,
                            entity_decls=[basis_name + "(:,:,:,:,:)"]))
            if arg.requires_diff_basis:
                raise GenerationError("differential basis has not yet "
                                      "been coded")
            if arg.requires_gauss_quad:
                if found_gauss_quad:
                    raise GenerationError("found more than one gaussian "
                                          "quadrature in this kernel")
                found_gauss_quad = True
                gauss_quad_arg = arg
            dataref = "%data"
            arglist.append(arg.name + dataref)

        if found_gauss_quad:
            gq_name = "gaussian_quadrature"
            arglist.append(gauss_quad_arg.name + "%" + gq_name)

        # generate the kernel call and associated use statement
        parent.add(CallGen(parent, self._name, arglist))
        if not self.module_inline:
            parent.add(
                UseGen(parent,
                       name=self._module_name,
                       only=True,
                       funcnames=[self._name]))

        # declare and initialise the number of layers and the number
        # of degrees of freedom. Needs to be generalised.
        parent.add(
            DeclGen(parent,
                    datatype="integer",
                    entity_decls=["nlayers", "ndf"]))
        new_parent, position = parent.start_parent_loop()
        new_parent.add(AssignGen(new_parent,
                                 lhs="nlayers",
                                 rhs=field_name + "%get_nlayers()"),
                       position=["before", position])
        new_parent.add(AssignGen(new_parent,
                                 lhs="ndf",
                                 rhs=field_name + "%vspace%get_ndf()"),
                       position=["before", position])