diff --git a/doc/tutorials_and_examples/examples_intro.rst b/doc/tutorials_and_examples/examples_intro.rst index e6ccf4e9de..ec9a35011b 100644 --- a/doc/tutorials_and_examples/examples_intro.rst +++ b/doc/tutorials_and_examples/examples_intro.rst @@ -105,14 +105,14 @@ so it can be recorded in this table. .. tabularcolumns:: |l|L| -======================= ======================================================= +======================== ======================================================= Compiler Version -======================= ======================================================= +======================== ======================================================= Gnu Fortran 13.3, 14.3, 15.2, 16.1 Intel Fortran (ifort) 21 Intel LLVM Fortran (ifx) 2025.3.1, 2026.1.0 NVIDIA Fortran 23.5 -======================= ======================================================= +======================== ======================================================= .. _examples_dependencies: diff --git a/doc/user_guide/lfric.rst b/doc/user_guide/lfric.rst index 75f019a079..7c3affed06 100644 --- a/doc/user_guide/lfric.rst +++ b/doc/user_guide/lfric.rst @@ -1744,6 +1744,8 @@ meshes cannot be on the same function space while those on the same mesh must also be on the same function space. +.. _number-of-layers-metadata: + Number of Layers Metadata """"""""""""""""""""""""" @@ -1774,6 +1776,7 @@ If two or more field/operator arguments are on the same function space and have the same number of layers (whether a literal or a name) then only one dofmap is passed to the kernel for those arguments. +.. _ndata-metadata: Multi-Data Metadata """"""""""""""""""" @@ -2090,11 +2093,23 @@ conventions, are: 1) If an LMA operator is passed then include the ``cells`` argument. ``cells`` is an ``integer`` of kind ``i_def`` and has intent ``in``. -2) Include ``nlayers``, the number of layers in a column. ``nlayers`` - is an ``integer`` of kind ``i_def`` and has intent ``in``. PSyclone - will obtain the value of ``nlayers`` to use for a particular kernel - from the first field or operator in the argument list. -3) For each scalar/field/vector_field/operator/ScalarArray in the order specified by + +2) Include each unique value of ``nlayers``, the number of layers in a + column, for all of the field/operator arguments. ``nlayers`` + is an ``integer`` of kind ``i_def`` and has intent ``in``. A typical + kernel will have just one value of ``nlayers`` which PSyclone will + obtain from the first field/operator argument in the list. For those + kernels which have field arguments with ``nlayers`` specified by a + label in the metadata (see :ref:`number-of-layers-metadata`), include + a value of ``nlayers`` for every unique label, in the same + order as they occur in the ``meta_args`` list. + +3) If any field/operator arguments have custom values of ``ndata`` (see + :ref:`ndata-metadata`) then, for each unique label, include an ``ndata`` + argument in the same order as they appear in the ``meta_args`` list. Each + ``ndata`` is an ``integer`` of kind ``i_def`` and has intent ``in``. + +4) For each scalar/field/vector_field/operator/ScalarArray in the order specified by the meta_args metadata: 1) If the current entry is a scalar quantity then include the Fortran @@ -2131,16 +2146,7 @@ conventions, are: 4) If the field entry stencil access is of type ``XORY1D`` then add an additional ``integer`` direction argument of kind ``i_def`` and with intent ``in``. - 5) If the field is multi-data and the value of ``NDATA`` is unknown - (i.e. it is specified in the metadata using a label) - then add an additional ``integer``, scalar argument of kind ``i_def`` - and intent ``in``. If the ``NDATA`` value is common to more than one - kernel argument, it is only added for the first such argument. - 6) If the field has an unknown (i.e. specified in the metadata using a - label), custom number of vertical levels then pass this as an - additional ``integer``, scalar argument of kind ``i_def`` and - intent ``in``. If the number of vertical levels is common to more than - one kernel argument, it is only added for the first such argument. + 3) If the current entry is a field vector then for each dimension of the vector, include a field array. The field array name is specified as @@ -2166,7 +2172,7 @@ conventions, are: the data type and kind specified in the metadata. The ScalarArray must be denoted with intent ``in`` to match its read-only nature. -4) DoF maps for function spaces are handled in the order they appear in the +5) DoF maps for function spaces are handled in the order they appear in the metadata arguments (the ``to`` function space of an operator is considered to be before the ``from`` function space of the same operator as it appears first in lexicographic order). Note that if two fields on a given function @@ -2247,7 +2253,7 @@ conventions, are: | | | W2Vtrace, W3, Wtheta, Wchi | +---------------+-----------+------------------------------------+ -5) If either the ``normals_to_horizontal_faces`` or +6) If either the ``normals_to_horizontal_faces`` or ``outward_normals_to_horizontal_faces`` properties of the reference element are required then pass the number of horizontal faces of the reference element (``nfaces_re_h``). Similarly, if either the @@ -2269,7 +2275,7 @@ conventions, are: a rank-2 ``integer`` array of kind ``i_def`` with dimensions ``(3, nfaces_re)``. -6) If the ``adjacent_face`` mesh property is required then: +7) If the ``adjacent_face`` mesh property is required then: 1) If the number of horizontal cell faces obtained from the reference element (``nfaces_re_h``) is not already being passed to the kernel (due @@ -2278,7 +2284,7 @@ conventions, are: 2) Pass a rank-1, ``integer`` array of kind ``i_def`` and extent ``nfaces_re_h``. -7) If Quadrature is required (``gh_shape = gh_quadrature_*``) then, for +8) If Quadrature is required (``gh_shape = gh_quadrature_*``) then, for each shape in the order specified in the ``gh_shape`` metadata: 1) Include ``integer``, scalar arguments of kind ``i_def`` with intent diff --git a/examples/common.mk b/examples/common.mk index cebe43c84a..1eac0d197e 100644 --- a/examples/common.mk +++ b/examples/common.mk @@ -94,6 +94,12 @@ ${NOTEBOOK_FILES}: notebook: ${NOTEBOOK_FILES} +%.o: %.F90 + $(F90) $(F90FLAGS) -c $< + +%.o: %.f90 + $(F90) $(F90FLAGS) -c $< + # By default we clean-up emacs backup files and generated Jupyter notebooks clean: ${RM} ./*~ ./*.nbconvert.ipynb ${GENERATED_FILES} diff --git a/examples/lfric/README.md b/examples/lfric/README.md index 913d30370f..081ecde8ec 100644 --- a/examples/lfric/README.md +++ b/examples/lfric/README.md @@ -390,6 +390,13 @@ the algorithm subroutine generated by this example: 5. Run ``../bin/skeleton ./configuration.nml`` in the ``miniapps/skeleton/example`` directory. +## Example 21: Kernels with arguments that specify ndata and/or nlayers + +Shows the use of kernels that have field arguments that specify non-default +values of ndata and/or nlayers. Although the code compiles, it cannot be +executed because, for simplicity, it lacks the necessary set-up of the LFRic +field objects. + ## Code Location of LFRic algorithm and kernel code that is used by two or diff --git a/examples/lfric/code/testkern_nlayers_ndata2_mod.F90 b/examples/lfric/code/testkern_nlayers_ndata2_mod.F90 new file mode 100644 index 0000000000..ae9d857ba2 --- /dev/null +++ b/examples/lfric/code/testkern_nlayers_ndata2_mod.F90 @@ -0,0 +1,69 @@ +! ----------------------------------------------------------------------------- +! SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +! Facilities Council +! SPDX-License-Identifier: BSD-3-Clause +! See the full LICENSE file in the project root for details. +! ----------------------------------------------------------------------------- + +!> An example LFRic kernel which has arguments with non-default values +!! of NLAYERS and NDATA. +module testkern_nlayers_ndata2_mod + + use argument_mod + use fs_continuity_mod + use kernel_mod + use constants_mod + + implicit none + + type, extends(kernel_type) :: testkern_nlayers_ndata2_type + type(arg_type), dimension(5) :: meta_args = (/ & + arg_type(gh_field, gh_real, gh_inc, w1), & + ! Non-default number of layers. + arg_type(gh_field, gh_real, gh_read, w2, nlayers="shallow"), & + ! Non-default number of data values per dof. + arg_type(gh_field, gh_real, gh_read, w2, ndata="precip"), & + ! Although we have seen the values of ndata and nlayers before + ! (individually), the dof map will be different. + arg_type(gh_field, gh_real, gh_read, w2, ndata="precip", & + nlayers="shallow"), & + arg_type(gh_field, gh_real, gh_read, w2, ndata="3", nlayers="1") & + /) + integer :: operates_on = cell_column + contains + procedure, nopass :: code => testkern_nlayers_ndata2_code + end type testkern_nlayers_ndata2_type + +contains + + subroutine testkern_nlayers_ndata2_code( & + nlayers, nlayers_shallow, ndata_precip, & + fld1, fld2, fld3, fld4, fld5, & + ndf_w1, undf_w1, map_w1, & + ndf_fld2, undf_fld2, map_w2_fld2, & + ndf_fld3, undf_fld3, map_w2_fld3, & + ndf_fld4, undf_fld4, map_w2_fld4, & + ndf_fld5, undf_fld5, map_w2_fld5) + implicit none + + integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_shallow + integer(kind=i_def), intent(in) :: ndata_precip + integer(kind=i_def), intent(in) :: ndf_w1, ndf_fld2, ndf_fld3, ndf_fld4, & + ndf_fld5 + integer(kind=i_def), intent(in) :: undf_w1, undf_fld2, undf_fld3, & + undf_fld4, undf_fld5 + integer(kind=i_def), intent(in), dimension(ndf_w1) :: map_w1 + integer(kind=i_def), intent(in), dimension(ndf_fld2) :: map_w2_fld2 + integer(kind=i_def), intent(in), dimension(ndf_fld3) :: map_w2_fld3 + integer(kind=i_def), intent(in), dimension(ndf_fld4) :: map_w2_fld4 + integer(kind=i_def), intent(in), dimension(ndf_fld5) :: map_w2_fld5 + real(kind=r_def), intent(inout), dimension(undf_w1) :: fld1 + real(kind=r_def), intent(in), dimension(undf_fld2) :: fld2 + real(kind=r_def), intent(in), dimension(undf_fld3) :: fld3 + real(kind=r_def), intent(in), dimension(undf_fld4) :: fld4 + real(kind=r_def), intent(in), dimension(undf_fld5) :: fld5 + + end subroutine testkern_nlayers_ndata2_code + +end module testkern_nlayers_ndata2_mod diff --git a/examples/lfric/code/testkern_nlayers_ndata_mod.F90 b/examples/lfric/code/testkern_nlayers_ndata_mod.F90 new file mode 100644 index 0000000000..c9238594c0 --- /dev/null +++ b/examples/lfric/code/testkern_nlayers_ndata_mod.F90 @@ -0,0 +1,75 @@ +! ----------------------------------------------------------------------------- +! SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +! Facilities Council +! SPDX-License-Identifier: BSD-3-Clause +! See the full LICENSE file in the project root for details. +! ----------------------------------------------------------------------------- + +!> An example LFRic kernel which has arguments with non-default values +!! of NLAYERS and NDATA. +module testkern_nlayers_ndata_mod + + use argument_mod + use fs_continuity_mod + use kernel_mod + use constants_mod + + implicit none + + type, extends(kernel_type) :: testkern_nlayers_ndata_type + type(arg_type), dimension(7) :: meta_args = & + (/ arg_type(gh_scalar, gh_real, gh_read), & + arg_type(gh_field, gh_real, gh_inc, w1), & + arg_type(gh_field, gh_real, gh_read, w2), & + ! Non-default number of layers. + arg_type(gh_field, gh_real, gh_read, w2, nlayers="shallow"), & + ! Non-default number of layers but same as previous arg. so + ! has same dof map. + arg_type(gh_field, gh_real, gh_read, w2, nlayers="shallow"), & + ! Non-default number of data values per dof. + arg_type(gh_field, gh_real, gh_read, w2, ndata="precip"), & + ! Although we have seen the values of ndata and nlayers before + ! (individually), the dof map will be different. + arg_type(gh_field, gh_real, gh_read, w2, ndata="precip", & + nlayers="shallow") & + /) + integer :: operates_on = cell_column + contains + procedure, nopass :: code => testkern_nlayers_ndata_code + end type testkern_nlayers_ndata_type + +contains + + subroutine testkern_nlayers_ndata_code( & + nlayers, nlayers_shallow, ndata_precip, & + ascalar, fld1, fld2, fld3, fld4, fld5, fld6, & + ndf_w1, undf_w1, map_w1, & + ndf_w2, undf_w2, map_w2, & + ndf_fld3, undf_fld3, map_w2_fld3, & + ndf_fld5, undf_fld5, map_w2_fld5, & + ndf_fld6, undf_fld6, map_w2_fld6) + implicit none + + integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_shallow + integer(kind=i_def), intent(in) :: ndata_precip + integer(kind=i_def), intent(in) :: ndf_w1, ndf_w2, ndf_fld3, ndf_fld5, & + ndf_fld6 + integer(kind=i_def), intent(in) :: undf_w1, undf_w2, undf_fld3, & + undf_fld5, undf_fld6 + integer(kind=i_def), intent(in), dimension(ndf_w1) :: map_w1 + integer(kind=i_def), intent(in), dimension(ndf_w2) :: map_w2 + integer(kind=i_def), intent(in), dimension(ndf_fld3) :: map_w2_fld3 + integer(kind=i_def), intent(in), dimension(ndf_fld5) :: map_w2_fld5 + integer(kind=i_def), intent(in), dimension(ndf_fld6) :: map_w2_fld6 + real(kind=r_def), intent(in) :: ascalar + real(kind=r_def), intent(inout), dimension(undf_w1) :: fld1 + real(kind=r_def), intent(in), dimension(undf_w2) :: fld2 + real(kind=r_def), intent(in), dimension(undf_fld3) :: fld3 + real(kind=r_def), intent(in), dimension(undf_fld3) :: fld4 + real(kind=r_def), intent(in), dimension(undf_fld5) :: fld5 + real(kind=r_def), intent(in), dimension(undf_fld6) :: fld6 + + end subroutine testkern_nlayers_ndata_code + +end module testkern_nlayers_ndata_mod diff --git a/examples/lfric/eg21/Makefile b/examples/lfric/eg21/Makefile new file mode 100644 index 0000000000..499373300d --- /dev/null +++ b/examples/lfric/eg21/Makefile @@ -0,0 +1,42 @@ +# ----------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +# Facilities Council +# SPDX-License-Identifier: BSD-3-Clause +# See the full LICENSE file in the project root for details. +# ----------------------------------------------------------------------------- + +include ../lfric_common.mk + +ALG_NAME = nlayers_ndata_eg_alg + +OBJ = testkern_nlayers_ndata_mod.o testkern_nlayers_ndata2_mod.o ${ALG_NAME}_psy.o ${ALG_NAME}.o +GENERATED_FILES = ${ALG_NAME}.f90 ${ALG_NAME}_psy.f90 \ + testkern_nlayers_ndata_mod.F90 testkern_nlayers_ndata2_mod.F90 *.o \ + *.mod will_not_run.exe + +# Although we link an executable, the example is too simplistic to +# actually execute. +run: compile + @echo "lfric/eg21 is not executable." + +# Dependencies +%.F90: + ln -s ../code/$@ . + +# Keep the generated psy and alg files +.precious: ${ALG_NAME}_psy.f90 ${ALG_NAME}.f90 + +transform: ${ALG_NAME}_psy.f90 + +# Process the algorithm layer with PSyclone to generate a compilable +# algorithm file and a suitable PSy-layer subroutine. +${ALG_NAME}_psy.f90 ${ALG_NAME}.f90: ${ALG_NAME}.x90 + ${PSYCLONE} -api lfric -d ../code -opsy ${ALG_NAME}_psy.f90 -oalg ${ALG_NAME}.f90 $< + +# Compile and link purely to test correctness - the resulting executable +# won't actually work. +compile: transform ${LFRIC_LIB} ${OBJ} + ${F90} ${F90FLAGS} -o will_not_run.exe ${OBJ} ${LDFLAGS} + +allclean: clean + make -C $(LFRIC_PATH) allclean diff --git a/examples/lfric/eg21/nlayers_ndata_eg_alg.x90 b/examples/lfric/eg21/nlayers_ndata_eg_alg.x90 new file mode 100644 index 0000000000..f980c4f3a2 --- /dev/null +++ b/examples/lfric/eg21/nlayers_ndata_eg_alg.x90 @@ -0,0 +1,25 @@ +! ----------------------------------------------------------------------------- +! SPDX-FileCopyrightText: Copyright (c) 2026 Science and Technology +! Facilities Council +! SPDX-License-Identifier: BSD-3-Clause +! See the full LICENSE file in the project root for details. +! ----------------------------------------------------------------------------- + +!> A (non-executable) example LFRic algorithm layer that invokes two different +!! kernels that both have field arguments with custom values of NLAYERS and NDATA. +!! The example is not executable because it omits all the initialisation +!! required to construct fully valid field objects. +program my_alg + use constants_mod, only: r_def + use field_mod, only: field_type + use testkern_nlayers_ndata2_mod, only: testkern_nlayers_ndata2_type + use testkern_nlayers_ndata_mod, only: testkern_nlayers_ndata_type + implicit none + real(kind=r_def) :: scalar + type(field_type) :: fld1, fld2, fld3, fld4, fld5, fld6, fld_2d + + call invoke( & + testkern_nlayers_ndata_type(scalar, fld1, fld2, fld3, fld4, fld5, fld6), & + testkern_nlayers_ndata2_type(fld1, fld2, fld3, fld4, fld_2d) ) + +end program diff --git a/external/lfric_infrastructure/src/kernel_metadata/argument_mod.f90 b/external/lfric_infrastructure/src/kernel_metadata/argument_mod.f90 index 74c8f799d2..795dca5895 100644 --- a/external/lfric_infrastructure/src/kernel_metadata/argument_mod.f90 +++ b/external/lfric_infrastructure/src/kernel_metadata/argument_mod.f90 @@ -231,10 +231,10 @@ module argument_mod !> Optional metadata (fields only) specifying how many data values !! there are at each DoF. Defaults to 1. character(len=METADATA_CHAR_LENGTH) :: ndata = "1" - !> Optional metadata specifying how many vertical levels + !> Optional metadata specifying how many vertical layers !! the field has. Default is to use the number associated with the !! first field/operator kernel argument. - character(len=METADATA_CHAR_LENGTH) :: nlevels = "" + character(len=METADATA_CHAR_LENGTH) :: nlayers = "" end type arg_type !> Optional metadata for the basis/differential basis functions required with diff --git a/lib/process.py b/lib/process.py index a06313b6ae..9780f6e948 100755 --- a/lib/process.py +++ b/lib/process.py @@ -68,64 +68,65 @@ help="Declare generic interfaces for " "ProvideVariable functions.", default=False) -args = parser.parse_args() - -# --------------------------------------------------------- -# This is a mapping from the command line option name -# to the tuple that is required for the jinja templates: -TYPE_DATA = {"real": ("Real", "real(kind=real32)"), - "double": ("Double", "real(kind=real64)"), - "int": ("Int", "integer(kind=int32)"), - "char": ("Char", "character(*)"), - "logical": ("Logical", "Logical(kind=4)"), - "long": ("Long", "integer(kind=int64)")} - -# --------------------------------------------------------- -# Check type information: -types = [type.lower() for type in args.types.split(",")] - -# If types is empty (e.g. in profiling no types are actually -# required), the parsers assigns ['']. Convert to an empty list: -if types == ['']: - types = [] - -for my_type in types: - if my_type not in TYPE_DATA: - print(f"Type '{my_type}' is not supported.", file=sys.stderr) - valid_str = ",".join(list(TYPE_DATA.keys())) - print(f"Use one or more of {valid_str}", file=sys.stderr) - sys.exit(-1) -all_types = [TYPE_DATA[my_type] for my_type in types] - -# --------------------------------------------------------- -# check dimension -dims = args.dims.split(",") - -# Convert to empty list if an empty dims argument was given: -if dims == ['']: - dims = [] - -for dim in dims: - try: - int_dim = int(dim) - except ValueError: - print(f"Dimension value '{dim}' is not valid.", - file=sys.stderr) - sys.exit(-1) - if int_dim < 1 or int_dim > 7: - print(f"Dimension value '{dim}' is not between 1 and 7.", - file=sys.stderr) - sys.exit(-1) - -dims = [int(dim) for dim in dims] -# --------------------------------------------------------- -with open(args.template_name, "r", encoding='utf-8') as file: - template_string = "".join(file.readlines()) - -env = Environment(trim_blocks=True, lstrip_blocks=True) -template = env.from_string(template_string) - -print(template.render(ALL_TYPES=all_types, ALL_DIMS=dims, - PREFIX=args.prefix, - GENERIC_DECLARE=args.generic_declare, - GENERIC_PROVIDE=args.generic_provide)) +if __name__ == '__main__': + args = parser.parse_args() + + # --------------------------------------------------------- + # This is a mapping from the command line option name + # to the tuple that is required for the jinja templates: + TYPE_DATA = {"real": ("Real", "real(kind=real32)"), + "double": ("Double", "real(kind=real64)"), + "int": ("Int", "integer(kind=int32)"), + "char": ("Char", "character(*)"), + "logical": ("Logical", "Logical(kind=4)"), + "long": ("Long", "integer(kind=int64)")} + + # --------------------------------------------------------- + # Check type information: + types = [type.lower() for type in args.types.split(",")] + + # If types is empty (e.g. in profiling no types are actually + # required), the parsers assigns ['']. Convert to an empty list: + if types == ['']: + types = [] + + for my_type in types: + if my_type not in TYPE_DATA: + print(f"Type '{my_type}' is not supported.", file=sys.stderr) + valid_str = ",".join(list(TYPE_DATA.keys())) + print(f"Use one or more of {valid_str}", file=sys.stderr) + sys.exit(-1) + all_types = [TYPE_DATA[my_type] for my_type in types] + + # --------------------------------------------------------- + # check dimension + dims = args.dims.split(",") + + # Convert to empty list if an empty dims argument was given: + if dims == ['']: + dims = [] + + for dim in dims: + try: + int_dim = int(dim) + except ValueError: + print(f"Dimension value '{dim}' is not valid.", + file=sys.stderr) + sys.exit(-1) + if int_dim < 1 or int_dim > 7: + print(f"Dimension value '{dim}' is not between 1 and 7.", + file=sys.stderr) + sys.exit(-1) + + dims = [int(dim) for dim in dims] + # --------------------------------------------------------- + with open(args.template_name, "r", encoding='utf-8') as file: + template_string = "".join(file.readlines()) + + env = Environment(trim_blocks=True, lstrip_blocks=True) + template = env.from_string(template_string) + + print(template.render(ALL_TYPES=all_types, ALL_DIMS=dims, + PREFIX=args.prefix, + GENERIC_DECLARE=args.generic_declare, + GENERIC_PROVIDE=args.generic_provide)) diff --git a/src/psyclone/domain/lfric/arg_ordering.py b/src/psyclone/domain/lfric/arg_ordering.py index d31e77a862..40a66e45bd 100644 --- a/src/psyclone/domain/lfric/arg_ordering.py +++ b/src/psyclone/domain/lfric/arg_ordering.py @@ -10,6 +10,7 @@ ''' import abc +from typing import Optional from psyclone import psyGen from psyclone.core import AccessType, Signature, VariablesAccessMap @@ -19,7 +20,7 @@ from psyclone.domain.lfric.metadata_to_arguments_rules import ( MetadataToArgumentsRules) from psyclone.errors import GenerationError, InternalError -from psyclone.psyir.nodes import ArrayReference, Reference +from psyclone.psyir.nodes import ArrayReference, Node, Reference from psyclone.psyir.symbols import DataSymbol, ArrayType, SymbolTable @@ -77,17 +78,21 @@ def _symtab(self) -> SymbolTable: return current_invoke.schedule.symbol_table return SymbolTable() - def psyir_append(self, node): + def psyir_append(self, node: Node) -> None: '''Appends a PSyIR node to the PSyIR argument list. :param node: the node to append. - :type node: :py:class:`psyclone.psyir.nodes.Node` ''' self._psyir_arglist.append(node) - def append(self, var_name, var_accesses=None, var_access_name=None, - mode=AccessType.READ, metadata_posn=None): + def append(self, + var_name: str, + var_accesses: Optional[VariablesAccessMap] = None, + var_access_name: Optional[str] = None, + mode: AccessType = AccessType.READ, + metadata_posn: Optional[int] = None + ) -> None: # pylint: disable=too-many-arguments '''Appends the specified variable name to the list of all arguments and stores the mapping between the position of this actual argument and @@ -97,18 +102,14 @@ def append(self, var_name, var_accesses=None, var_access_name=None, it is assumed that access mode is READ (which can be set with ``mode``). - :param str var_name: the name of the variable. - :param var_accesses: optional class to store variable access \ + :param var_name: the name of the variable. + :param var_accesses: optional class to store variable access information. - :type var_accesses: \ - :py:class:`psyclone.core.VariablesAccessMap` - :param str var_access_name: optional name of the variable for \ - which access information is stored (used e.g. when the \ - actual argument is field_proxy, but the access is to be \ - recorded for field). + :param var_access_name: optional name of the variable for which + access information is stored (used e.g. when the actual argument + is field_proxy, but the access is to be recorded for field). :param mode: optional access mode (defaults to READ). - :type mode: :py:class:`psyclone.core.access_type.AccessType` - :param int metadata_posn: the location of the corresponding entry in \ + :param metadata_posn: the location of the corresponding entry in the list of arguments in the kernel metadata (if any). ''' @@ -324,7 +325,9 @@ def metadata_index_from_actual_index(self, idx): ''' return self._arg_index_to_metadata_index[idx] - def generate(self, var_accesses: VariablesAccessMap = None): + def generate(self, + var_accesses: Optional[VariablesAccessMap] = None + ) -> None: # pylint: disable=too-many-statements, too-many-branches ''' Specifies which arguments appear in an argument list, their type @@ -349,10 +352,14 @@ def generate(self, var_accesses: VariablesAccessMap = None): 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 + + # Pass the number of layers and number of data pts/dof associated with + # the various field/operator arguments 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) + self.field_ndata(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 @@ -536,37 +543,43 @@ def cell_map(self, var_accesses=None): ''' - def mesh_height(self, var_accesses=None): + def mesh_height(self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: '''Add mesh height (nlayers) to the argument list and if supplied stores this access in var_accesses. - :param var_accesses: optional VariablesAccessMap instance to store \ + :param var_accesses: optional VariablesAccessMap instance to store the information about variable accesses. - :type var_accesses: \ - :py:class:`psyclone.core.VariablesAccessMap` + ''' + + def field_ndata(self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: + '''Add any distinct values of ndata (number of data values per dof) + required by field arguments to the argument list. Also add these + accesses to `var_accesses` if supplied. + :param var_accesses: optional VariablesAccessMap instance to store + the information about variable accesses. ''' - def _mesh_ncell2d(self, var_accesses=None): + def _mesh_ncell2d(self, + var_accesses: Optional[VariablesAccessMap] = None + ) -> None: '''Add the number of columns in the mesh (including halos) to the argument list and stores this access in var_accesses (if supplied). - :param var_accesses: optional VariablesAccessMap instance to store \ + :param var_accesses: optional VariablesAccessMap instance to store the information about variable accesses. - :type var_accesses: \ - :py:class:`psyclone.core.VariablesAccessMap` - ''' - def _mesh_ncell2d_no_halos(self, var_accesses=None): + def _mesh_ncell2d_no_halos( + self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: '''Add the number of columns in the mesh (excluding halos) to the argument list and stores this access in var_accesses (if supplied). - :param var_accesses: optional VariablesAccessMap instance to store \ + :param var_accesses: optional VariablesAccessMap instance to store the information about variable accesses. - :type var_accesses: \ - :py:class:`psyclone.core.VariablesAccessMap` - ''' @abc.abstractmethod diff --git a/src/psyclone/domain/lfric/function_space.py b/src/psyclone/domain/lfric/function_space.py index 161c718545..abe800bebb 100644 --- a/src/psyclone/domain/lfric/function_space.py +++ b/src/psyclone/domain/lfric/function_space.py @@ -21,11 +21,17 @@ class FunctionSpace(): ''' Manages the name of a function space. If it is an any_space or any_discontinuous_space then its name is mangled such that it is unique - within the scope of an Invoke. + within the scope of an Invoke. If the function space is associated with + a non-default number of layers and/or data values per dof then the + labels specifying those are also incorporated in the name. :param name: original name of function space. :param kernel_args: object encapsulating all arguments to the kernel, one or more of which are on this function space. + :param nlayers: the label specifying the number of layers associated + with this function space. + :param ndata: the label (or integer literal) specifying the number of + data values per dof. :raises InternalError: if an unrecognised function space is encountered. @@ -34,9 +40,15 @@ class FunctionSpace(): ## and field names. MAX_NAME_LEN = 21 - def __init__(self, name: str, kernel_args: "LFRicKernelArguments"): + def __init__(self, + name: str, + kernel_args: "LFRicKernelArguments", + nlayers: Optional[str] = None, + ndata: Optional[str] = "1"): self._orig_name = name self._kernel_args = kernel_args + self._nlayers = nlayers + self._ndata = ndata const = LFRicConstants() # Check whether the function space name is a valid name @@ -76,20 +88,35 @@ def mangled_name(self) -> str: const = LFRicConstants() if (self._orig_name not in const.VALID_ANY_SPACE_NAMES + const.VALID_ANY_DISCONTINUOUS_SPACE_NAMES): - return self._orig_name - - # List kernel arguments - args = self._kernel_args.args - # Mangle the function space name for any_*_space - lorig_name = self._orig_name.lower() - for arg in args: - for fspace in arg.function_spaces: - if (fspace and fspace.orig_name.lower() == lorig_name): - return self._shorten_name(f"{self.short_name}_{arg.name}") - # Raise an error if there are no kernel arguments on this - # function space - raise FieldNotFoundError(f"No kernel argument found for function " - f"space '{self._orig_name}'") + if (not self._nlayers) and self._ndata == "1": + return self._orig_name + base_name = self._orig_name + else: + base_name = "" + # List kernel arguments + args = self._kernel_args.args + # Mangle the function space name for any_*_space + lorig_name = self._orig_name.lower() + for arg in args: + for fspace in arg.function_spaces: + if (fspace and fspace.orig_name.lower() == lorig_name): + base_name = f"{self.short_name}_{arg.name}" + break + if base_name: + break + else: + # Raise an error if there are no kernel arguments on this + # function space + raise FieldNotFoundError( + f"No kernel argument found for function " + f"space '{self._orig_name}'") + + parts = [base_name] + if self._nlayers: + parts.append(self._nlayers) + if self._ndata != "1": + parts.append(self._ndata) + return self._shorten_name("_".join(parts)) @staticmethod def _shorten_name(name: str) -> str: diff --git a/src/psyclone/domain/lfric/kern_call_arg_list.py b/src/psyclone/domain/lfric/kern_call_arg_list.py index f577033f42..f7dbfa9e59 100644 --- a/src/psyclone/domain/lfric/kern_call_arg_list.py +++ b/src/psyclone/domain/lfric/kern_call_arg_list.py @@ -202,9 +202,12 @@ def cell_map(self, var_accesses: Optional[VariablesAccessMap] = None): sym = self.append_integer_reference(base_name) self.append(sym.name, var_accesses) - def mesh_height(self, var_accesses: Optional[VariablesAccessMap] = None): - '''Add mesh height (nlayers) to the argument list and if supplied - stores this access in var_accesses. + def mesh_height(self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: + '''Add mesh height (nlayers) of the first field/operator argument to + the argument list. If other field arguments have different numbers of + layers then these are also added to the list. These accesses are + stored in var_accesses if it is supplied. :param var_accesses: optional VariablesAccessMap instance to store the information about variable accesses. @@ -212,10 +215,52 @@ def mesh_height(self, var_accesses: Optional[VariablesAccessMap] = None): ''' if self._kern.iterates_over == "dof": return + nlayers_names = set() + # By default we always pass the number of layers associated with the + # first field or operator argument. name = f"nlayers_{self._kern.arguments.first_field_or_operator.name}" + nlayers_names.add(name) nlayers_symbol = self.append_integer_reference(name, tag=name) self.append(nlayers_symbol.name, var_accesses) self._nlayers_positions.append(self.num_args) + # We also have to pass any other values of nlayers for those args + # that have a different number and which is unknown at compile time. + for arg in self._kern.arguments.args: + if arg.nlayers and not arg.nlayers.isnumeric(): + sym = self._symtab.lookup_with_tag( + f"nlayers_{arg.nlayers}") + if sym.name in nlayers_names: + # Make sure we don't duplicate arguments. + continue + nlayers_names.add(sym.name) + self.append(sym.name, var_accesses) + self.psyir_append(Reference(sym)) + self._nlayers_positions.append(self.num_args) + + def field_ndata(self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: + '''Add any distinct values of ndata (number of data values per dof) + required by field arguments to the argument list. Also add these + accesses to `var_accesses` if supplied. + + :param var_accesses: optional VariablesAccessMap instance to store + the information about variable accesses. + ''' + if self._kern.iterates_over == "dof": + return + ndata_names = set() + for arg in self._kern.arguments.args: + if arg.ndata and not arg.ndata.isnumeric(): + sym = self._symtab.lookup_with_tag( + f"ndata_{arg.ndata}") + if sym.name in ndata_names: + # Make sure we don't duplicate arguments. + continue + ndata_names.add(sym.name) + self.append(sym.name, var_accesses) + self.psyir_append(Reference(sym)) + # TODO #3498 keep track of positions of ndata + # arguments c.f. nlayers def scalar(self, scalar_arg, var_accesses: Optional[VariablesAccessMap] = None): diff --git a/src/psyclone/domain/lfric/kern_stub_arg_list.py b/src/psyclone/domain/lfric/kern_stub_arg_list.py index e30e8ad9fc..a4c23ee329 100644 --- a/src/psyclone/domain/lfric/kern_stub_arg_list.py +++ b/src/psyclone/domain/lfric/kern_stub_arg_list.py @@ -30,35 +30,60 @@ class KernStubArgList(ArgOrdering): :type kern: :py:class:`psyclone.domain.lfric.LFRicKern` :raises NotImplementedError: if the kernel is inter-grid. - :raises NotImplementedError: if the kernel requires properties of the \ + :raises NotImplementedError: if the kernel requires properties of the reference element. ''' def __init__(self, kern): ArgOrdering.__init__(self, kern) - def cell_position(self, var_accesses=None): + def cell_position(self, + var_accesses: Optional[VariablesAccessMap] = None + ) -> None: '''Adds a cell argument to the argument list and if supplied stores this access in var_accesses. - :param var_accesses: optional VariablesAccessMap instance to store \ + :param var_accesses: optional VariablesAccessMap instance to store the information about variable accesses. - :type var_accesses: \ - :py:class:`psyclone.core.VariablesAccessMap` ''' self.append("cell", var_accesses) - def mesh_height(self, var_accesses=None): - '''Add mesh height (nlayers) to the argument list and if supplied - stores this access in var_accesses. + def mesh_height(self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: + '''Add the distinct arguments for mesh height (nlayers) to the list + and, if supplied, stores these accesses in var_accesses. - :param var_accesses: optional VariablesAccessMap instance to store \ + :param var_accesses: optional VariablesAccessMap instance to store the information about variable accesses. - :type var_accesses: \ - :py:class:`psyclone.core.VariablesAccessMap` ''' - self.append("nlayers", var_accesses) + first_arg: LFRicKernelArgument = ( + self._kern.arguments.first_field_or_operator) + self.append(f"nlayers_{first_arg.name}", var_accesses) + nlayers_names = set() + for arg in self._kern.arguments.args: + if arg.nlayers and not arg.nlayers.isnumeric(): + if arg.nlayers in nlayers_names: + continue + nlayers_names.add(arg.nlayers) + self.append(f"nlayers_{arg.nlayers}", var_accesses) + + def field_ndata(self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: + '''Add to the argument list any distinct and unknown values of ndata + (number of data values per dof) required by field arguments. Also add + these accesses to `var_accesses` if supplied. + + :param var_accesses: optional VariablesAccessMap instance to store + the information about variable accesses. + ''' + ndata_names = set() + for arg in self._kern.arguments.args: + if arg.ndata and not arg.ndata.isnumeric(): + if arg.ndata in ndata_names: + continue + ndata_names.add(arg.ndata) + self.append(f"ndata_{arg.ndata}", var_accesses) def _mesh_ncell2d(self, var_accesses=None): '''Add the number of columns in the mesh to the argument list and if diff --git a/src/psyclone/domain/lfric/kernel/field_arg_metadata.py b/src/psyclone/domain/lfric/kernel/field_arg_metadata.py index a485b37a02..865f682e1a 100644 --- a/src/psyclone/domain/lfric/kernel/field_arg_metadata.py +++ b/src/psyclone/domain/lfric/kernel/field_arg_metadata.py @@ -33,7 +33,7 @@ class FieldArgMetadata(ScalarArgMetadata): form = "gh_field" # The relative positions of LFRic metadata. Metadata for a field # argument is provided in the following format 'arg_type(form, - # datatype, access, function_space, nlevels=..., ndata=...)'. Therefore, + # datatype, access, function_space, nlayers=..., ndata=...)'. Therefore, # for example, the index of the form argument (form_arg_index) is 0. form_arg_index = 0 datatype_arg_index = 1 @@ -50,12 +50,12 @@ class FieldArgMetadata(ScalarArgMetadata): def __init__(self, datatype: str, access: str, function_space: str, stencil: Optional[str] = None, - nlevels: Optional[str] = None, + nlayers: Optional[str] = None, ndata: Optional[str] = "1"): super().__init__(datatype, access) self.function_space = function_space self.stencil = stencil - self.nlevels = nlevels + self.nlayers = nlayers self.ndata = ndata @classmethod @@ -73,7 +73,7 @@ def _get_metadata( for this argument. :returns: a tuple containing the datatype, access, function - space, stencil, nlevels and ndata metadata. + space, stencil, nlayers and ndata metadata. ''' datatype, access = super()._get_metadata(fparser2_tree) @@ -81,10 +81,10 @@ def _get_metadata( fparser2_tree, cls.function_space_arg_index) stencil = cls.get_stencil(fparser2_tree) super()._validate_named_args(fparser2_tree, - ["nlevels", "ndata"]) - nlevels = cls.get_named_arg(fparser2_tree, "nlevels") + ["nlayers", "ndata"]) + nlayers = cls.get_named_arg(fparser2_tree, "nlayers") ndata = cls.get_named_arg(fparser2_tree, "ndata") - return (datatype, access, function_space, stencil, nlevels, ndata) + return (datatype, access, function_space, stencil, nlayers, ndata) @classmethod def get_stencil( @@ -129,8 +129,8 @@ def fortran_string(self) -> str: f"{self.function_space}") if self.stencil: result += f", stencil({self.stencil})" - if self.nlevels: - result += f", nlevels='{self.nlevels}'" + if self.nlayers: + result += f", nlayers='{self.nlayers}'" if self.ndata and self.ndata != "1": result += f", ndata='{self.ndata}'" result += ")" diff --git a/src/psyclone/domain/lfric/kernel/field_vector_arg_metadata.py b/src/psyclone/domain/lfric/kernel/field_vector_arg_metadata.py index 19c549564f..cb1fedf9e4 100644 --- a/src/psyclone/domain/lfric/kernel/field_vector_arg_metadata.py +++ b/src/psyclone/domain/lfric/kernel/field_vector_arg_metadata.py @@ -42,9 +42,9 @@ class FieldVectorArgMetadata(FieldArgMetadata): vector = True def __init__(self, datatype, access, function_space, vector_length, - stencil=None, nlevels=None, ndata=1): + stencil=None, nlayers=None, ndata=1): super().__init__(datatype, access, function_space, stencil=stencil, - nlevels=nlevels, ndata=ndata) + nlayers=nlayers, ndata=ndata) self.vector_length = vector_length @classmethod @@ -63,14 +63,14 @@ def _get_metadata( for this argument. :returns: a tuple containing the datatype, access, function - space, vector-length, stencil, nlevels and ndata metadata. + space, vector-length, stencil, nlayers and ndata metadata. ''' (datatype, access, function_space, stencil, - nlevels, ndata) = super()._get_metadata(fparser2_tree) + nlayers, ndata) = super()._get_metadata(fparser2_tree) vector_length = cls.get_vector_length(fparser2_tree) return (datatype, access, function_space, vector_length, stencil, - nlevels, ndata) + nlayers, ndata) def fortran_string(self): ''' diff --git a/src/psyclone/domain/lfric/kernel/inter_grid_arg_metadata.py b/src/psyclone/domain/lfric/kernel/inter_grid_arg_metadata.py index 2cdd1e9051..50e3bb2d00 100644 --- a/src/psyclone/domain/lfric/kernel/inter_grid_arg_metadata.py +++ b/src/psyclone/domain/lfric/kernel/inter_grid_arg_metadata.py @@ -47,10 +47,10 @@ def __init__(self, function_space: str, mesh_arg: str, stencil: Optional[str] = None, - nlevels: Optional[str] = None, + nlayers: Optional[str] = None, ndata: Optional[str] = "1"): super().__init__(datatype, access, function_space, stencil=stencil, - nlevels=nlevels, ndata=ndata) + nlayers=nlayers, ndata=ndata) self.mesh_arg = mesh_arg @classmethod @@ -67,22 +67,22 @@ def _get_metadata( for this argument. :returns: a tuple containing the datatype, access, function - space, mesh, stencil, nlevels and ndata metadata. + space, mesh, stencil, nlayers and ndata metadata. ''' datatype, access = cls._get_datatype_access_metadata(fparser2_tree) function_space = cls.get_arg(fparser2_tree, cls.function_space_arg_index) - cls._validate_named_args(fparser2_tree, ["mesh_arg", "nlevels", + cls._validate_named_args(fparser2_tree, ["mesh_arg", "nlayers", "ndata"]) stencil = cls.get_stencil(fparser2_tree) mesh_arg = cls.get_named_arg(fparser2_tree, "mesh_arg") - nlevels = cls.get_named_arg(fparser2_tree, "nlevels") + nlayers = cls.get_named_arg(fparser2_tree, "nlayers") ndata = cls.get_named_arg(fparser2_tree, "ndata") - return (datatype, access, function_space, mesh_arg, stencil, nlevels, + return (datatype, access, function_space, mesh_arg, stencil, nlayers, ndata) def fortran_string(self) -> str: diff --git a/src/psyclone/domain/lfric/kernel/inter_grid_vector_arg_metadata.py b/src/psyclone/domain/lfric/kernel/inter_grid_vector_arg_metadata.py index f68248b66b..d05db2b72c 100644 --- a/src/psyclone/domain/lfric/kernel/inter_grid_vector_arg_metadata.py +++ b/src/psyclone/domain/lfric/kernel/inter_grid_vector_arg_metadata.py @@ -48,10 +48,10 @@ class InterGridVectorArgMetadata(InterGridArgMetadata): vector = True def __init__(self, datatype, access, function_space, mesh_arg, - vector_length, stencil=None, nlevels=None, ndata="1"): + vector_length, stencil=None, nlayers=None, ndata="1"): super().__init__( datatype, access, function_space, mesh_arg, stencil=stencil, - nlevels=nlevels, ndata=ndata) + nlayers=nlayers, ndata=ndata) self.vector_length = vector_length @classmethod @@ -68,14 +68,14 @@ def _get_metadata(cls, for this argument. :returns: a tuple containing the datatype, access, function - space, mesh, vector-length, stencil, nlevels and ndata metadata. + space, mesh, vector-length, stencil, nlayers and ndata metadata. ''' - datatype, access, function_space, mesh_arg, stencil, nlevels, ndata = \ + datatype, access, function_space, mesh_arg, stencil, nlayers, ndata = \ super()._get_metadata(fparser2_tree) vector_length = cls.get_vector_length(fparser2_tree) return (datatype, access, function_space, mesh_arg, vector_length, - stencil, nlevels, ndata) + stencil, nlayers, ndata) def fortran_string(self): ''' diff --git a/src/psyclone/domain/lfric/kernel_interface.py b/src/psyclone/domain/lfric/kernel_interface.py index ef7715f368..6f06e83dd8 100644 --- a/src/psyclone/domain/lfric/kernel_interface.py +++ b/src/psyclone/domain/lfric/kernel_interface.py @@ -9,8 +9,9 @@ kernel based on the kernel metadata. ''' -from typing import TYPE_CHECKING -from psyclone.core import AccessType +from typing import Optional, TYPE_CHECKING + +from psyclone.core import AccessType, VariablesAccessMap from psyclone.domain.lfric.arg_ordering import ArgOrdering from psyclone.domain.lfric.lfric_constants import LFRicConstants from psyclone.domain.lfric.lfric_types import LFRicTypes @@ -80,14 +81,12 @@ def __init__(self, kern: "LFRicKern"): # as arguments. self._forced_symtab = SymbolTable() - def generate(self, var_accesses=None): + def generate(self, var_accesses: Optional[VariablesAccessMap] = None): '''Call the generate base class then add the argument list as it can't be appended as we go along. - :param var_accesses: an unused optional argument that stores \ + :param var_accesses: an unused optional argument that stores information about variable accesses. - :type var_accesses: :\ - py:class:`psyclone.core.VariablesAccessMap` ''' super().generate(var_accesses=var_accesses) @@ -113,14 +112,14 @@ def generate(self, var_accesses=None): mode=mapping[symbol.interface.access]) self._arglist = self._arglist[:len_arglist] - def cell_position(self, var_accesses=None): + def cell_position(self, + var_accesses: Optional[VariablesAccessMap] = None + ) -> None: '''Create an LFRic cell-position object and add it to the symbol table and argument list. - :param var_accesses: an unused optional argument that stores \ + :param var_accesses: an unused optional argument that stores information about variable accesses. - :type var_accesses: :\ - py:class:`psyclone.core.VariablesAccessMap` ''' symbol = self._symtab.find_or_create_tag( @@ -128,21 +127,51 @@ def cell_position(self, var_accesses=None): interface=self._read_access) self._arglist.append(symbol) - def mesh_height(self, var_accesses=None): - '''Create an LFRic mesh height object and add it to the symbol table - and argument list. + def mesh_height(self, + var_accesses: Optional[VariablesAccessMap] = None + ) -> None: + '''Create LFRic mesh height (nlayers) objects and add to the + symbol table and argument list. - :param var_accesses: an unused optional argument that stores \ + :param var_accesses: an unused optional argument that stores information about variable accesses. - :type var_accesses: :\ - py:class:`psyclone.core.VariablesAccessMap` ''' + # By default we always pass the nlayers value associated with the first + # field or operator argument. symbol = self._symtab.find_or_create_tag( - "nlayers", symbol_type=LFRicTypes("MeshHeightDataSymbol"), + f"nlayers_{self._kern.arguments.first_field_or_operator.name}", + symbol_type=LFRicTypes("MeshHeightDataSymbol"), interface=self._read_access) self._arglist.append(symbol) + # Check for arguments that specify a different value for nlayers + for arg in self._kern.arguments.args: + if arg.nlayers and not arg.nlayers.isnumeric(): + sym = self._symtab.find_or_create_tag( + f"nlayers_{arg.nlayers}", + symbol_type=LFRicTypes("MeshHeightDataSymbol"), + interface=self._read_access) + if sym not in self._arglist: + self._arglist.append(sym) + + def field_ndata(self, + var_accesses: Optional[VariablesAccessMap] = None) -> None: + '''Add any distinct values of ndata (number of data values per dof) + required by field arguments to the argument list. Also add these + accesses to `var_accesses` if supplied. + + :param var_accesses: an unused optional argument for interface + consistency. + ''' + for arg in self._kern.arguments.args: + if arg.ndata and not arg.ndata.isnumeric(): + sym = self._symtab.find_or_create_tag( + f"ndata_{arg.ndata}", + symbol_type=LFRicTypes("NumberOfValuesPerDofDataSymbol")) + if sym not in self._arglist: + self._arglist.append(sym) + def _mesh_ncell2d(self, var_accesses=None): '''Not implemented. diff --git a/src/psyclone/domain/lfric/lfric_arg_descriptor.py b/src/psyclone/domain/lfric/lfric_arg_descriptor.py index 5e9dd51c0a..31fa7d842a 100644 --- a/src/psyclone/domain/lfric/lfric_arg_descriptor.py +++ b/src/psyclone/domain/lfric/lfric_arg_descriptor.py @@ -79,9 +79,9 @@ def __init__(self, arg_type, operates_on, metadata_index): self._function_space2 = None self._stencil = None self._mesh = None - # No. of vertical levels associated with the argument. Defaults to + # No. of vertical layers associated with the argument. Defaults to # using that of the first field/operator argument to a kernel. - self._nlevels = None + self._nlayers = None # No. of data values per dof - defaults to 1. self._ndata = "1" self._nargs = 0 @@ -386,9 +386,9 @@ def _init_field(self, arg_type, operates_on): elif "mesh" in str(arg_type.args[prop_ind]): self._mesh = get_mesh(arg_type.args[prop_ind], const.VALID_MESH_TYPES) - elif "nlevels" in str(arg_type.args[prop_ind]): - self._nlevels = get_char_value(arg_type.args[prop_ind], - "nlevels") + elif "nlayers" in str(arg_type.args[prop_ind]): + self._nlayers = get_char_value(arg_type.args[prop_ind], + "nlayers") elif "ndata" in str(arg_type.args[prop_ind]): self._ndata = get_char_value(arg_type.args[prop_ind], "ndata") @@ -398,7 +398,7 @@ def _init_field(self, arg_type, operates_on): raise ParseError( f"In the LFRic API argument {prop_ind+1} of a " f"'meta_arg' field entry must be either a valid " - f"stencil specification, a number of levels, a number " + f"stencil specification, a number of layers, a number " f"of data values per dof or a mesh identifier (for " f"inter-grid kernels). However, entry '{arg_type}' " f"raised the following error: {err}.") from err @@ -776,14 +776,14 @@ def function_spaces(self): f"'{self._argument_type}'.") @property - def nlevels(self) -> Optional[str]: + def nlayers(self) -> Optional[str]: ''' :returns: a label (or integer, encoded as a string) identifying the - number of vertical levels associated with this argument or None - if the default (the number of levels associated with the first + number of vertical layers associated with this argument or None + if the default (the number of layers associated with the first kernel argument) is to be used. ''' - return self._nlevels + return self._nlayers @property def ndata(self) -> str: diff --git a/src/psyclone/domain/lfric/lfric_cell_iterators.py b/src/psyclone/domain/lfric/lfric_cell_iterators.py index c566afa585..35a5f8199a 100644 --- a/src/psyclone/domain/lfric/lfric_cell_iterators.py +++ b/src/psyclone/domain/lfric/lfric_cell_iterators.py @@ -8,12 +8,17 @@ ''' This module implements the LFRicCellIterators collection which handles the requirements of kernels that operator on cells.''' +from typing import TYPE_CHECKING, Union + from psyclone.domain.lfric.lfric_collection import LFRicCollection +from psyclone.domain.lfric.lfric_invoke import LFRicInvoke from psyclone.domain.lfric.lfric_kern import LFRicKern from psyclone.domain.lfric.lfric_types import LFRicTypes from psyclone.errors import GenerationError from psyclone.psyir.nodes import Assignment, Reference -from psyclone.psyir.symbols import ArgumentInterface +from psyclone.psyir.symbols import ArgumentInterface, DataSymbol +if TYPE_CHECKING: + from psyclone.domain.lfric import LFRicKernelArgument class LFRicCellIterators(LFRicCollection): @@ -22,84 +27,148 @@ class LFRicCellIterators(LFRicCollection): :param kern_or_invoke: the Kernel or Invoke for which to manage cell iterators. - :type kern_or_invoke: :py:class:`psyclone.domain.lfric.LFRicKern` | - :py:class:`psyclone.lfric.LFRicInvoke` :raises GenerationError: if an Invoke has no field or operator arguments. ''' - def __init__(self, kern_or_invoke): + def __init__(self, + kern_or_invoke: Union[LFRicKern, LFRicInvoke]): super().__init__(kern_or_invoke) - # Dictionary to hold the names of the various nlayers variables and - # (for invokes) the kernel argument to which each corresponds. - self._nlayers_names = {} - - if self._invoke: - # Each kernel that operates on either the domain or cell-columns - # needs an 'nlayers' obtained from the first field/operator - # argument. - for kern in self._invoke.schedule.walk(LFRicKern): - if kern.iterates_over != "dof": - arg = kern.arguments.first_field_or_operator - sym = self.symtab.find_or_create_tag( - f"nlayers_{arg.name}", - symbol_type=LFRicTypes("MeshHeightDataSymbol")) - self._nlayers_names[sym.name] = arg + # Dictionary to hold the names of the various nlayers/ndata variables + # and (for invokes) the kernel argument to which each corresponds. + self._nlayers_names: dict[str, LFRicKernelArgument] = {} + self._ndata_names: dict[str, LFRicKernelArgument] = {} + + if not self._invoke: + return + + # Each kernel that operates on either the domain or cell-columns needs + # an 'nlayers' obtained from the first field/operator argument. + for kern in self._invoke.schedule.walk(LFRicKern): + if kern.iterates_over != "dof": + self._declare_kern_args(kern) + + for var in self._invoke.psy_unique_vars: + if not var.is_scalar: + break + else: + raise GenerationError( + "Cannot create an Invoke with no field/operator arguments.") + self._first_var = var + + def _declare_kern_args(self, + kern: LFRicKern, + for_stub: bool = False) -> None: + ''' + Creates the Symbols for the nlayers and ndata arguments to the + supplied Kernel. If `for_stub` is True, then we are generating a + Kernel stub (subroutine) and the new symbols are declared as read-only + arguments. - first_var = None - for var in self._invoke.psy_unique_vars: - if not var.is_scalar: - first_var = var - break - if not first_var: - raise GenerationError( - "Cannot create an Invoke with no field/operator " - "arguments.") - self._first_var = first_var - - def stub_declarations(self): + :param kern: the LFRic kernel for which to generate nlayers and ndata + arguments. + :param for_stub: whether or not we are creating a Kernel stub. + + ''' + def _update_arg_properties(symbol: DataSymbol) -> None: + ''' + Update the supplied symbol to be a dummy argument if `for_stub` + is True. + ''' + if for_stub: + symbol.interface = ArgumentInterface( + ArgumentInterface.Access.READ) + self.symtab.append_argument(symbol) + + if kern.cma_operation not in ["apply", "matrix-matrix"]: + first_arg: LFRicKernelArgument = ( + kern.arguments.first_field_or_operator) + sym = self.symtab.find_or_create_tag( + f"nlayers_{first_arg.name}", + symbol_type=LFRicTypes("MeshHeightDataSymbol")) + _update_arg_properties(sym) + self._nlayers_names[sym.name] = first_arg + + # We must also check for any subsequent arguments that have + # a number of layers or ndata specified by a label in the metadata. + for arg in kern.arguments.args: + if arg.nlayers and not arg.nlayers.isnumeric(): + sym = self.symtab.find_or_create_tag( + f"nlayers_{arg.nlayers}", + symbol_type=LFRicTypes("MeshHeightDataSymbol")) + if sym.name not in self._nlayers_names: + self._nlayers_names[sym.name] = arg + _update_arg_properties(sym) + for arg in kern.arguments.args: + if arg.ndata and not arg.ndata.isnumeric(): + sym = self.symtab.find_or_create_tag( + f"ndata_{arg.ndata}", + symbol_type=LFRicTypes("NumberOfValuesPerDofDataSymbol")) + if sym.name not in self._ndata_names: + self._ndata_names[sym.name] = arg + _update_arg_properties(sym) + + def stub_declarations(self) -> None: ''' Declare entities required for a kernel stub that operates on cell-columns. ''' super().stub_declarations() - if self._kernel.cma_operation not in ["apply", "matrix-matrix"]: - nlayers = self.symtab.find_or_create_tag( - "nlayers", - symbol_type=LFRicTypes("MeshHeightDataSymbol") - ) - nlayers.interface = ArgumentInterface( - ArgumentInterface.Access.READ) - self.symtab.append_argument(nlayers) - - def initialise(self, cursor): + + self._declare_kern_args(self._kernel, for_stub=True) + + def initialise(self, cursor: int) -> int: ''' - Look-up the number of vertical layers in the mesh in the PSy layer. + Look-up the number(s) of vertical layers and number(s) of data values + per dof in the PSy layer. - :param int cursor: position where to add the next initialisation + :param cursor: position where to add the next initialisation statements. :returns: Updated cursor value. - :rtype: int ''' - if not self._nlayers_names or not self._invoke: - return cursor + cursor = self._initialise_var_list(cursor, self._nlayers_names, + "get_nlayers", + "number of layers") + cursor = self._initialise_var_list(cursor, self._ndata_names, + "get_ndata", + "number of data values per dof") + return cursor + + def _initialise_var_list(self, + cursor: int, + name_map: dict[str, "LFRicKernelArgument"], + fn_name: str, + comment: str + ) -> int: + ''' + A utility to generate the initialisation statements for the + kernel arguments named in the supplied map. + + :param cursor: the current position at which to add content in + the PSy-layer schedule. + :param name_map: dict holding descriptions of each kernel argument, + indexed by name. + :param fn_name: the name of the type-bound procedure that must be + called to get the initial vaue of each argument. + :param comment: text to use in the associated comment in the + generated PSy-layer code. + ''' # Sort for test reproducibility - sorted_names = list(self._nlayers_names.keys()) - sorted_names.sort() + sorted_names = sorted(list(name_map.keys())) init_cursor = cursor for name in sorted_names: symbol = self.symtab.lookup(name) - var = self._nlayers_names[name] + var = name_map[name] stmt = Assignment.create( - lhs=Reference(symbol), - rhs=var.generate_method_call("get_nlayers")) + lhs=Reference(symbol), + rhs=var.generate_method_call(fn_name)) if cursor == init_cursor: - stmt.preceding_comment = "Initialise number of layers" + stmt.preceding_comment = f"Initialise {comment}" self._invoke.schedule.addchild(stmt, cursor) cursor += 1 return cursor diff --git a/src/psyclone/domain/lfric/lfric_dofmaps.py b/src/psyclone/domain/lfric/lfric_dofmaps.py index 5ceef4bbf9..16024007ce 100644 --- a/src/psyclone/domain/lfric/lfric_dofmaps.py +++ b/src/psyclone/domain/lfric/lfric_dofmaps.py @@ -257,13 +257,10 @@ def stub_declarations(self): symbol.interface = ArgumentInterface(ArgumentInterface.Access.READ) self.symtab.append_argument(symbol) - nlayers = self.symtab.find_or_create_tag( - "nlayers", - symbol_type=LFRicTypes("MeshHeightDataSymbol") - ) - nlayers.interface = ArgumentInterface( - ArgumentInterface.Access.READ) - self.symtab.append_argument(nlayers) + # We need nlayers to dimension the array. + first_arg = self._kernel.arguments.first_field_or_operator + nlayers = self.symtab.lookup_with_tag( + f"nlayers_{first_arg.name}") dmap_symbol = self.symtab.find_or_create( dmap, tag=dmap, symbol_type=DataSymbol, diff --git a/src/psyclone/domain/lfric/lfric_kern_metadata.py b/src/psyclone/domain/lfric/lfric_kern_metadata.py index e173d52d61..fa8b4224e5 100644 --- a/src/psyclone/domain/lfric/lfric_kern_metadata.py +++ b/src/psyclone/domain/lfric/lfric_kern_metadata.py @@ -419,18 +419,18 @@ def _identify_cma_op(self, cwise_ops: list[str]) -> str: f"argument must only have field arguments with 'gh_real' " f"data type but kernel '{self.name}' has a field argument " f"with '{arg.data_type}' data type.") - # No arguments with non-default values for nlevels or ndata are + # No arguments with non-default values for nlayers or ndata are # permitted. if arg.ndata != "1": raise ParseError( f"Kernel '{self.name}' takes a CMA operator but has an " f"argument with a non-default value ('{arg.ndata}') of " f"NDATA. This is forbidden.") - if arg.nlevels: + if arg.nlayers: raise ParseError( f"Kernel '{self.name}' takes a CMA operator but has an " - f"argument with a non-default value ('{arg.nlevels}') of " - f"NLEVELS. This is forbidden.") + f"argument with a non-default value ('{arg.nlayers}') of " + f"NLAYERS. This is forbidden.") # Count the number of CMA operators that are written to write_count = 0 diff --git a/src/psyclone/domain/lfric/lfric_types.py b/src/psyclone/domain/lfric/lfric_types.py index 6133a3718f..4dffe7a3b8 100644 --- a/src/psyclone/domain/lfric/lfric_types.py +++ b/src/psyclone/domain/lfric/lfric_types.py @@ -277,7 +277,7 @@ def __init__(self, value): # ------------------------------------------------------------------------ @staticmethod - def _create_specific_scalars(): + def _create_specific_scalars() -> None: '''This function creates all required specific scalar, which are derived from the corresponding generic classes (e.g. LFRicIntegerScalarData) @@ -294,6 +294,7 @@ def _create_specific_scalars(): specific_scalar_datatypes = [ Scalar("CellPosition", "LFRicIntegerScalarData", []), Scalar("MeshHeight", "LFRicIntegerScalarData", []), + Scalar("NumberOfValuesPerDof", "LFRicIntegerScalarData", []), Scalar("NumberOfCells", "LFRicIntegerScalarData", []), Scalar("NumberOfDofs", "LFRicIntegerScalarData", ["fs"]), Scalar("NumberOfUniqueDofs", "LFRicIntegerScalarData", ["fs"]), diff --git a/src/psyclone/lfric.py b/src/psyclone/lfric.py index 7307a34b09..d71d198c15 100644 --- a/src/psyclone/lfric.py +++ b/src/psyclone/lfric.py @@ -17,7 +17,7 @@ from enum import Enum from collections import OrderedDict, namedtuple from dataclasses import dataclass -from typing import Any, List, Optional +from typing import Any, List, Optional, Union from psyclone import psyGen from psyclone.configuration import Config @@ -5257,8 +5257,6 @@ class LFRicKernelArguments(Arguments): kernel metadata and the algorithm layer. Defaults to True. :raises GenerationError: if the kernel metadata specifies stencil extent. - :raises NotImplementedError: if the kernel metadata specifies arguments - with either NDATA or NLEVELS. ''' def __init__(self, @@ -5274,7 +5272,7 @@ def __init__(self, # create our arguments and add in stencil information where # appropriate. - self._args = [] + self._args: list[LFRicKernelArgument] = [] idx = 0 for arg in call.ktype.arg_descriptors: lfric_argument = LFRicKernelArgument(self, arg, call.args[idx], @@ -5359,13 +5357,6 @@ def __init__(self, # List of corresponding unique function-space objects self._unique_fss = [] for arg in self._args: - if arg.nlevels or arg.ndata != "1": - name = (f"'{self._parent_call.name}'" if self._parent_call - else "stub") - raise NotImplementedError( - f"Cannot generate arguments for kernel {name}: code " - f"generation for kernels specifying NLEVELS or NDATA in " - f"their metadata is not yet supported - TODO #868") for fspace in arg.function_spaces: # We check that function_space is not None because scalar # args don't have one and fields only have one (only @@ -5624,10 +5615,10 @@ def __init__(self, self._mesh = arg_meta_data.mesh.lower() else: self._mesh = None - # The number of vertical levels (for a field/operator) - self._nlevels = arg_meta_data.nlevels + # The number of vertical layers (for a field/operator) + self._nlayers: Optional[str] = arg_meta_data.nlayers # The number of data values associated with each DoF of a field - self._ndata = arg_meta_data.ndata + self._ndata: Optional[str] = arg_meta_data.ndata # The list of function-space objects for this argument. Each # object can be queried for its original name and for the @@ -5640,13 +5631,16 @@ def __init__(self, if self.is_operator: fs1 = FunctionSpace(arg_meta_data.function_space_to, - self._kernel_args) + self._kernel_args, self._nlayers, + self._ndata) fs2 = FunctionSpace(arg_meta_data.function_space_from, - self._kernel_args) + self._kernel_args, self._nlayers, + self._ndata) else: if arg_meta_data.function_space: fs1 = FunctionSpace(arg_meta_data.function_space, - self._kernel_args) + self._kernel_args, self._nlayers, + self._ndata) self._function_spaces = [fs1, fs2] # Set the argument's intrinsic type from its descriptor's @@ -6154,15 +6148,15 @@ def name_indexed(self) -> str: return self._name @property - def nlevels(self) -> Optional[str]: + def nlayers(self) -> Optional[str]: ''' - :returns: the number of vertical levels of this (field/operator) + :returns: the number of vertical layers of this (field/operator) argument, as specified in the Kernel metadata. Default is None in which case the value is the same as that of the first field/operator argument. ''' - return self._nlevels + return self._nlayers @property def ndata(self) -> str: @@ -6275,24 +6269,21 @@ def proxy_declaration_name(self): return self.proxy_name @property - def proxy_data_type(self): + def proxy_data_type(self) -> Union[str, None]: ''' - :returns: the type of this argument's proxy (if it exists) as \ + :returns: the type of this argument's proxy (if it exists) as defined in LFRic infrastructure. - :rtype: str or NoneType - ''' return self._proxy_data_type @property - def function_space(self): + def function_space(self) -> FunctionSpace: ''' Returns the expected finite element function space for a kernel argument as specified by the kernel argument metadata: a single function space for a field and function_space_from for an operator. :returns: function space for this argument. - :rtype: :py:class:`psyclone.domain.lfric.FunctionSpace` ''' if self._argument_type == "gh_operator": # We return the 'from' space for an operator argument @@ -6300,23 +6291,21 @@ def function_space(self): return self._function_spaces[0] @property - def function_space_to(self): + def function_space_to(self) -> FunctionSpace: ''' :returns: the 'to' function space of an operator. - :rtype: str ''' return self._function_spaces[0] @property - def function_space_from(self): + def function_space_from(self) -> FunctionSpace: ''' - :returns: the 'from' function space of an operator. - :rtype: str + :returns: the 'from' function space of an operator. ''' return self._function_spaces[1] @property - def function_spaces(self): + def function_spaces(self) -> list[FunctionSpace]: ''' Returns the expected finite element function space for a kernel argument as specified by the kernel argument metadata: a single @@ -6324,7 +6313,6 @@ def function_spaces(self): function_space_to and function_space_from for an operator. :returns: function space(s) for this argument. - :rtype: list of :py:class:`psyclone.domain.lfric.FunctionSpace` ''' return self._function_spaces diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 75ed4d6ede..c3413d1c15 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -746,31 +746,32 @@ class Kern(Statement): PSy layer. :param parent: parent of this node in the PSyIR. - :type parent: sub-class of :py:class:`psyclone.psyir.nodes.Node` - :param call: information on the call itself, as obtained by parsing \ + :param call: information on the call itself, as obtained by parsing the Algorithm layer code. - :type call: :py:class:`psyclone.parse.algorithm.KernelCall` - :param str name: the name of the routine being called. - :param ArgumentsClass: class to create the object that holds all \ - information on the kernel arguments, as extracted from kernel \ + :param name: the name of the routine being called. + :param ArgumentsClass: class to create the object that holds all + information on the kernel arguments, as extracted from kernel meta-data (and accessible here via call.ktype). - :type ArgumentsClass: type of :py:class:`psyclone.psyGen.Arguments` - :param bool check: whether to check for consistency between the \ + :param check: whether to check for consistency between the kernel metadata and the algorithm layer. Defaults to True. - :raises GenerationError: if any of the arguments to the call are \ + :raises GenerationError: if any of the arguments to the call are duplicated. - ''' # Textual representation of the valid children for this node. _children_valid_format = "" - def __init__(self, parent, call, name, ArgumentsClass, check=True): + def __init__(self, + parent: Node, + call: KernelCall, + name: str, + ArgumentsClass: type, + check: bool = True): # pylint: disable=too-many-arguments super().__init__(parent=parent) self._name = name self._iterates_over = call.ktype.iterates_over - self._arguments = ArgumentsClass(call, self, check=check) + self._arguments: Arguments = ArgumentsClass(call, self, check=check) # check algorithm arguments are unique for a kernel or # built-in call diff --git a/src/psyclone/tests/dependency_test.py b/src/psyclone/tests/dependency_test.py index e8a34b99de..c8dc3dd173 100644 --- a/src/psyclone/tests/dependency_test.py +++ b/src/psyclone/tests/dependency_test.py @@ -418,7 +418,7 @@ def test_lfric_stub_args(): assert "ndf_w1: READ," in var_info assert "ndf_w2: READ," in var_info assert "ndf_w3: READ," in var_info - assert "nlayers: READ," in var_info + assert "nlayers_field_1: READ," in var_info assert "undf_w1: READ," in var_info assert "undf_w2: READ," in var_info assert "undf_w3: READ" in var_info diff --git a/src/psyclone/tests/domain/lfric/kernel/common_arg_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/common_arg_metadata_test.py index 1948357db4..2eef437e69 100644 --- a/src/psyclone/tests/domain/lfric/kernel/common_arg_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/common_arg_metadata_test.py @@ -113,10 +113,10 @@ def test_get_named_arg(): assert test_cls.get_named_arg(fparser_tree, "red") is None # Named argument with a string value fparser_tree2 = CommonArgMetadata.create_fparser2( - "arg_type(GH_FIELD, GH_REAL, GH_READ, nlevels='crazy')", + "arg_type(GH_FIELD, GH_REAL, GH_READ, nlayers='crazy')", Fortran2003.Component_Spec) assert test_cls.get_named_arg(fparser_tree2, "red") is None - assert test_cls.get_named_arg(fparser_tree2, "nlevels") == "crazy" + assert test_cls.get_named_arg(fparser_tree2, "nlayers") == "crazy" # Named argument with a parameter value fparser_tree3 = test_cls.create_fparser2( "arg_type(GH_FIELD, GH_REAL, GH_READ, mesh=GH_FINE)", diff --git a/src/psyclone/tests/domain/lfric/kernel/field_arg_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/field_arg_metadata_test.py index 697d77284e..79ee66d744 100644 --- a/src/psyclone/tests/domain/lfric/kernel/field_arg_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/field_arg_metadata_test.py @@ -31,7 +31,7 @@ def test_create(datatype, access, function_space): assert field_arg._function_space == "w0" assert field_arg._stencil is None assert field_arg.ndata == "1" - assert field_arg.nlevels is None + assert field_arg.nlayers is None def test_create_stencil(): @@ -46,16 +46,16 @@ def test_create_stencil(): assert field_arg._access == "gh_read" assert field_arg._function_space == "w0" assert field_arg._stencil == "cross" - assert field_arg.nlevels is None + assert field_arg.nlayers is None assert field_arg.ndata == "1" -def test_create_nlevels_ndata(): +def test_create_nlayers_ndata(): '''Test that an instance of FieldArgMetadata can be created successfully - with optional ndata and nlevels metadata. + with optional ndata and nlayers metadata. ''' - fld_arg = FieldArgMetadata("gh_real", "gh_write", "w0", nlevels="1", + fld_arg = FieldArgMetadata("gh_real", "gh_write", "w0", nlayers="1", stencil="cross") assert isinstance(fld_arg, FieldArgMetadata) assert fld_arg.form == "gh_field" @@ -63,12 +63,12 @@ def test_create_nlevels_ndata(): assert fld_arg._access == "gh_write" assert fld_arg._function_space == "w0" assert fld_arg._stencil == "cross" - assert fld_arg.nlevels == "1" + assert fld_arg.nlayers == "1" assert fld_arg.ndata == "1" - fld_arg2 = FieldArgMetadata("gh_real", "gh_write", "w0", nlevels="ustar", + fld_arg2 = FieldArgMetadata("gh_real", "gh_write", "w0", nlayers="ustar", stencil="cross", ndata="big_phys") assert fld_arg2._stencil == "cross" - assert fld_arg2.nlevels == "ustar" + assert fld_arg2.nlayers == "ustar" assert fld_arg2.ndata == "big_phys" @@ -95,16 +95,16 @@ def test_init_invalid_stencil(): @pytest.mark.parametrize( - "metadata,expected_stencil,expected_nlevels,expected_ndata", + "metadata,expected_stencil,expected_nlayers,expected_ndata", [("arg_type(GH_FIELD, GH_REAL, GH_READ, W0)", None, None, None), ("arg_type(GH_FIELD, GH_REAL, GH_READ, W0, stencil(region))", "region", None, None), - ('arg_type(GH_FIELD, GH_REAL, GH_READ, W0, nlevels="big")', + ('arg_type(GH_FIELD, GH_REAL, GH_READ, W0, nlayers="big")', None, "big", None), ('arg_type(GH_FIELD, GH_REAL, GH_READ, W0, stencil(region), ' - 'nlevels="big", ndata="4")', + 'nlayers="big", ndata="4")', "region", "big", "4")]) -def test_get_metadata(metadata, expected_stencil, expected_nlevels, +def test_get_metadata(metadata, expected_stencil, expected_nlayers, expected_ndata): '''Test that the _get_metadata class method works as expected, with and without optional stencil metadata. @@ -113,12 +113,12 @@ def test_get_metadata(metadata, expected_stencil, expected_nlevels, encoding = Fortran2003.Structure_Constructor fparser2_tree = FieldArgMetadata.create_fparser2(metadata, encoding) (datatype, access, function_space, stencil, - nlevels, ndata) = FieldArgMetadata._get_metadata(fparser2_tree) + nlayers, ndata) = FieldArgMetadata._get_metadata(fparser2_tree) assert datatype == "GH_REAL" assert access == "GH_READ" assert function_space == "W0" assert stencil == expected_stencil - assert nlevels == expected_nlevels + assert nlayers == expected_nlayers assert ndata == expected_ndata @@ -159,7 +159,7 @@ def test_get_stencil(): "arg_type(GH_FIELD, GH_REAL, GH_READ, W0)", "arg_type(GH_FIELD, GH_REAL, GH_READ, W0, STENCIL(REGION))", "arg_type(GH_FIELD, GH_REAL, GH_READ, W0, NDATA='THREE')", - "arg_type(GH_FIELD, GH_REAL, GH_READ, W0, NLEVELS='4')"]) + "arg_type(GH_FIELD, GH_REAL, GH_READ, W0, NLAYERS='4')"]) def test_fortran_string(fortran_string): '''Test that the fortran_string method works as expected. Test with and without a stencil. diff --git a/src/psyclone/tests/domain/lfric/kernel/field_vector_arg_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/field_vector_arg_metadata_test.py index bf023c08bd..b0ae98da21 100644 --- a/src/psyclone/tests/domain/lfric/kernel/field_vector_arg_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/field_vector_arg_metadata_test.py @@ -81,13 +81,13 @@ def test_get_metadata(): "arg_type(GH_FIELD*3, GH_REAL, GH_READ, W0)", Fortran2003.Structure_Constructor) (datatype, access, function_space, vector_length, stencil, - nlevels, ndata) = FieldVectorArgMetadata._get_metadata(fparser2_tree) + nlayers, ndata) = FieldVectorArgMetadata._get_metadata(fparser2_tree) assert datatype == "GH_REAL" assert access == "GH_READ" assert function_space == "W0" assert vector_length == "3" assert stencil is None - assert nlevels is None + assert nlayers is None assert ndata is None diff --git a/src/psyclone/tests/domain/lfric/kernel/inter_grid_arg_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/inter_grid_arg_metadata_test.py index 02beffedcf..63301519ca 100644 --- a/src/psyclone/tests/domain/lfric/kernel/inter_grid_arg_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/inter_grid_arg_metadata_test.py @@ -58,14 +58,14 @@ def test_get_metadata(): metadata = "arg_type(GH_FIELD, GH_REAL, GH_READ, W0, mesh_arg=GH_COARSE)" fparser2_tree = InterGridArgMetadata.create_fparser2( metadata, encoding=Fortran2003.Structure_Constructor) - datatype, access, function_space, mesh_arg, stencil, nlevels, ndata = \ + datatype, access, function_space, mesh_arg, stencil, nlayers, ndata = \ InterGridArgMetadata._get_metadata(fparser2_tree) assert datatype == "GH_REAL" assert access == "GH_READ" assert function_space == "W0" assert mesh_arg == "GH_COARSE" assert stencil is None - assert nlevels is None + assert nlayers is None assert ndata is None @@ -78,14 +78,14 @@ def test_get_metadata_stencil(): "mesh_arg=GH_COARSE)") fparser2_tree = InterGridArgMetadata.create_fparser2( metadata, encoding=Fortran2003.Structure_Constructor) - datatype, access, function_space, mesh_arg, stencil, nlevels, ndata = \ + datatype, access, function_space, mesh_arg, stencil, nlayers, ndata = \ InterGridArgMetadata._get_metadata(fparser2_tree) assert datatype == "GH_REAL" assert access == "GH_READ" assert function_space == "W0" assert mesh_arg == "GH_COARSE" assert stencil == "region" - assert nlevels is None + assert nlayers is None assert ndata is None @@ -113,7 +113,7 @@ def test_get_mesh_arg(): @pytest.mark.parametrize("fortran_string", [ "arg_type(GH_FIELD, GH_REAL, GH_READ, W0, mesh_arg=GH_FINE)", "arg_type(GH_FIELD, GH_REAL, GH_READ, W0, STENCIL(X1D), " - "nlevels='3', mesh_arg=GH_FINE)"]) + "nlayers='3', mesh_arg=GH_FINE)"]) def test_fortran_string(fortran_string): '''Test that the fortran_string method works as expected. Test with and without a stencil. diff --git a/src/psyclone/tests/domain/lfric/kernel/inter_grid_vector_arg_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/inter_grid_vector_arg_metadata_test.py index a478808a41..6e0fe8f74b 100644 --- a/src/psyclone/tests/domain/lfric/kernel/inter_grid_vector_arg_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/inter_grid_vector_arg_metadata_test.py @@ -62,14 +62,14 @@ def test_get_metadata(): fparser2_tree = InterGridVectorArgMetadata.create_fparser2( metadata, encoding=Fortran2003.Structure_Constructor) (datatype, access, function_space, mesh_arg, vector_length, stencil, - nlevels, ndata) = InterGridVectorArgMetadata._get_metadata(fparser2_tree) + nlayers, ndata) = InterGridVectorArgMetadata._get_metadata(fparser2_tree) assert datatype == "GH_REAL" assert access == "GH_READ" assert function_space == "W0" assert mesh_arg == "GH_COARSE" assert vector_length == "3" assert stencil is None - assert nlevels is None + assert nlayers is None assert ndata is None @@ -83,14 +83,14 @@ def test_get_metadata_stencil(): fparser2_tree = InterGridVectorArgMetadata.create_fparser2( metadata, encoding=Fortran2003.Structure_Constructor) (datatype, access, function_space, mesh_arg, vector_length, stencil, - nlevels, ndata) = InterGridVectorArgMetadata._get_metadata(fparser2_tree) + nlayers, ndata) = InterGridVectorArgMetadata._get_metadata(fparser2_tree) assert datatype == "GH_REAL" assert access == "GH_READ" assert function_space == "W0" assert mesh_arg == "GH_COARSE" assert vector_length == "3" assert stencil == "xory1d" - assert nlevels is None + assert nlayers is None assert ndata is None diff --git a/src/psyclone/tests/domain/lfric/kernel_interface_test.py b/src/psyclone/tests/domain/lfric/kernel_interface_test.py index 7c6b806c6f..026b17d154 100644 --- a/src/psyclone/tests/domain/lfric/kernel_interface_test.py +++ b/src/psyclone/tests/domain/lfric/kernel_interface_test.py @@ -15,13 +15,15 @@ import pytest from psyclone.core import AccessType, Signature, VariablesAccessMap -from psyclone.domain.lfric import FunctionSpace, KernelInterface, LFRicTypes +from psyclone.domain.lfric import ( + FunctionSpace, KernelInterface, LFRicKern, LFRicTypes) from psyclone.errors import InternalError from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory from psyclone.psyir.symbols import SymbolTable, ArgumentInterface from psyclone.psyir.nodes import Reference, Literal from psyclone.psyir.frontend.fparser2 import INTENT_MAPPING +from psyclone.tests.utilities import get_invoke # pylint complains about all isinstance tests involving LFRicTypes, # since it doesn't know what the actual return type is. So disable @@ -65,7 +67,8 @@ def test_generate(var_accesses): kernel_interface = KernelInterface(kernel0) kernel_interface.generate(var_accesses=var_accesses) # Check symbols - nlayers_symbol = kernel_interface._symtab.lookup("nlayers") + nlayers_symbol = kernel_interface._symtab.lookup( + f"nlayers_{kernel0.arguments.first_field_or_operator}") assert isinstance(nlayers_symbol, LFRicTypes("MeshHeightDataSymbol")) undf_w0_symbol = kernel_interface._symtab.lookup("undf_w0") assert isinstance(undf_w0_symbol, @@ -109,7 +112,8 @@ def test_generate(var_accesses): assert len(var_accesses.all_signatures) == 6 # Test all read-only variables - for var in ["nlayers", "undf_w0", "f2", "ndf_w0", "dofmap_w0"]: + for var in [f"nlayers_{arg_list[2].name}", "undf_w0", "f2", "ndf_w0", + "dofmap_w0"]: accesses = var_accesses[Signature(var)] assert len(accesses) == 1 assert accesses[0].access_type == AccessType.READ @@ -141,9 +145,11 @@ def test_mesh_height(): expected type of Symbol to the symbol table and the _arglist list. ''' - kernel_interface = KernelInterface(None) + _, invoke = get_invoke("1_single_invoke.f90", api="lfric", idx=0) + kern = invoke.schedule.walk(LFRicKern)[0] + kernel_interface = KernelInterface(kern) kernel_interface.mesh_height() - symbol = kernel_interface._symtab.lookup("nlayers") + symbol = kernel_interface._symtab.lookup("nlayers_f1") assert isinstance(symbol, LFRicTypes("MeshHeightDataSymbol")) assert isinstance(symbol.interface, ArgumentInterface) assert (symbol.interface.access == @@ -151,6 +157,47 @@ def test_mesh_height(): assert kernel_interface._arglist[-1] is symbol +def test_mesh_height_named_values(): + """Test mesh_height handles named nlayers values by creating the + corresponding nlayers_ symbols and adding them to the argument list + only once even if repeated in the metadata.""" + _, invoke = get_invoke("1.5.6_single_invoke_nlayers_ndata.f90", + api="lfric", idx=0) + kern = invoke.schedule.walk(LFRicKern)[0] + kernel_interface = KernelInterface(kern) + kernel_interface.mesh_height() + + # The first field is f1 so expect nlayers_f1 + nlayers_f1 = kernel_interface._symtab.lookup("nlayers_f1") + assert isinstance(nlayers_f1, LFRicTypes("MeshHeightDataSymbol")) + assert nlayers_f1 in kernel_interface._arglist + + # Named nlayers value 'shallow' should create nlayers_shallow + nlayers_shallow = kernel_interface._symtab.lookup("nlayers_shallow") + assert isinstance(nlayers_shallow, LFRicTypes("MeshHeightDataSymbol")) + assert nlayers_shallow in kernel_interface._arglist + # Even though two args used the same named nlayers, the symbol should + # only appear once in the argument list + assert kernel_interface._arglist.count(nlayers_shallow) == 1 + + +def test_ndata_named_values(): + """Test that field_ndata adds appropriate arguments for distinct values + of ndata.""" + _, invoke = get_invoke("1.5.6_single_invoke_nlayers_ndata.f90", + api="lfric", idx=0) + kern = invoke.schedule.walk(LFRicKern)[0] + kernel_interface = KernelInterface(kern) + kernel_interface.field_ndata() + + # Named ndata value 'precip' should create ndata_precip + ndata_precip = kernel_interface._symtab.lookup("ndata_precip") + assert isinstance(ndata_precip, + LFRicTypes("NumberOfValuesPerDofDataSymbol")) + # Ensure it appears only once even if used multiple times + assert kernel_interface._arglist.count(ndata_precip) == 1 + + @pytest.mark.xfail(reason="Issue #928: this callback is not yet implemented") def test_mesh_ncell2d(): '''Test that the KernelInterface.mesh_ncell2d() method adds the diff --git a/src/psyclone/tests/domain/lfric/lfric_field_codegen_test.py b/src/psyclone/tests/domain/lfric/lfric_field_codegen_test.py index 50d6e3ea57..a397d6a4f2 100644 --- a/src/psyclone/tests/domain/lfric/lfric_field_codegen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_field_codegen_test.py @@ -11,12 +11,11 @@ ''' import os -import pytest from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory from psyclone.tests.lfric_build import LFRicBuild -from psyclone.tests.utilities import get_invoke, get_psylayer_schedule +from psyclone.tests.utilities import get_invoke # Constants @@ -1167,14 +1166,78 @@ def test_int_real_field_fs(dist_mem, tmpdir): assert LFRicBuild(tmpdir).code_compiles(psy) -def test_field_nlevels(): +def test_field_nlayers(tmp_path): '''Test for a kernel that has arguments with non-default values of - NLEVELS and NDATA. + NLAYERS and NDATA. ''' - with pytest.raises(NotImplementedError) as err: - _ = get_psylayer_schedule("1.5.6_single_invoke_nlevels_ndata.f90", - TEST_API) - # TODO #868 - code generation yet to be implemented. - assert ("Cannot generate arguments for kernel " - "'testkern_nlevels_ndata_code'" in str(err.value)) + psy, _ = get_invoke("1.5.6_single_invoke_nlayers_ndata.f90", + dist_mem=False, api=TEST_API, idx=0) + output = str(psy.gen) + + # Check the lookups for the number of layers. There should be the default + # value from the first field/op argument and then a second, custom value + # for arguments three and six. + expected = '''\ + ! Initialise number of layers + nlayers_f1 = f1_proxy%vspace%get_nlayers() + nlayers_shallow = f3_proxy%vspace%get_nlayers() +''' + assert expected in output + + # Lookup for the number of data points per dof. + expected = '''\ + ! Initialise number of data values per dof + ndata_precip = f5_proxy%vspace%get_ndata() +''' + assert expected in output + + # Check the dofmap lookups. + expected = '''\ + ! Look-up dofmaps for each function space + map_w1 => f1_proxy%vspace%get_whole_dofmap() + map_w2 => f2_proxy%vspace%get_whole_dofmap() + map_w2_shallow => f2_proxy%vspace%get_whole_dofmap() + map_w2_precip => f2_proxy%vspace%get_whole_dofmap() + map_w2_shallow_precip => f2_proxy%vspace%get_whole_dofmap() + + ! Initialise number of DoFs for w1 + ndf_w1 = f1_proxy%vspace%get_ndf() + undf_w1 = f1_proxy%vspace%get_undf() + + ! Initialise number of DoFs for w2 + ndf_w2 = f2_proxy%vspace%get_ndf() + undf_w2 = f2_proxy%vspace%get_undf() + + ! Initialise number of DoFs for w2_shallow + ndf_w2_shallow = f3_proxy%vspace%get_ndf() + undf_w2_shallow = f3_proxy%vspace%get_undf() + + ! Initialise number of DoFs for w2_precip + ndf_w2_precip = f5_proxy%vspace%get_ndf() + undf_w2_precip = f5_proxy%vspace%get_undf() + + ! Initialise number of DoFs for w2_shallow_precip + ndf_w2_shallow_precip = f6_proxy%vspace%get_ndf() + undf_w2_shallow_precip = f6_proxy%vspace%get_undf() +''' + assert expected in output + + # Check the kernel call. + assert ("call testkern_nlayers_ndata_code(" + "nlayers_f1, nlayers_shallow, ndata_precip, a, " + "f1_data, f2_data, f3_data, f4_data, f5_data, f6_data, " + # Arg one is on W1 + "ndf_w1, undf_w1, map_w1(:,cell), " + # Arg two is on W2 + "ndf_w2, undf_w2, map_w2(:,cell), " + # Arg three (and four) are on w2 but with nlayers='shallow' + "ndf_w2_shallow, undf_w2_shallow, map_w2_shallow(:,cell), " + # Arg five is on w2 but has ndata='precip' + "ndf_w2_precip, undf_w2_precip, map_w2_precip(:,cell), " + # Arg six is on w2 but has nlayers='shallow' *and* ndata='precip' + "ndf_w2_shallow_precip, undf_w2_shallow_precip, " + "map_w2_shallow_precip(:,cell)" + ")" in output) + + assert LFRicBuild(tmp_path).code_compiles(psy) diff --git a/src/psyclone/tests/domain/lfric/lfric_field_mdata_test.py b/src/psyclone/tests/domain/lfric/lfric_field_mdata_test.py index 1ef531c27f..c109e05f24 100644 --- a/src/psyclone/tests/domain/lfric/lfric_field_mdata_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_field_mdata_test.py @@ -359,26 +359,26 @@ def test_arg_descriptor_field(): assert field_descriptor.mesh is None assert field_descriptor.stencil is None assert field_descriptor.vector_size == 1 - assert field_descriptor.nlevels is None + assert field_descriptor.nlayers is None assert field_descriptor.ndata == "1" -def test_fld_nlevels(): +def test_fld_nlayers(): ''' - Test a field argument with the optional 'nlevels' metatadata. + Test a field argument with the optional 'nlayers' metatadata. ''' code = FIELD_CODE.replace( "arg_type(gh_scalar, gh_integer, gh_read)", - "arg_type(gh_field, gh_real, gh_read, w3, nlevels='double')", 1) + "arg_type(gh_field, gh_real, gh_read, w3, nlayers='double')", 1) ast = fpapi.parse(code, ignore_comments=False) name = "testkern_field_type" mdata = LFRicKernMetadata(ast, name=name) - # By default, nlevels is left as None. + # By default, nlayers is left as None. field_descriptor = mdata.arg_descriptors[5] - assert field_descriptor.nlevels is None - # The seventh argument has nlevels specified as "double" + assert field_descriptor.nlayers is None + # The seventh argument has nlayers specified as "double" field_descriptor = mdata.arg_descriptors[6] - assert field_descriptor.nlevels == "double" + assert field_descriptor.nlayers == "double" def test_fld_ndata(): diff --git a/src/psyclone/tests/domain/lfric/lfric_field_stubgen_test.py b/src/psyclone/tests/domain/lfric/lfric_field_stubgen_test.py index b6104e74de..faf5430451 100644 --- a/src/psyclone/tests/domain/lfric/lfric_field_stubgen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_field_stubgen_test.py @@ -124,14 +124,15 @@ def test_int_field_gen_stub(fortran_writer): public contains - subroutine testkern_int_field_code(nlayers, field_1_wtheta, field_2_w3_v1, \ -field_2_w3_v2, field_2_w3_v3, field_3_w2trace, field_3_stencil_size, \ + subroutine testkern_int_field_code(nlayers_field_1, field_1_wtheta, \ +field_2_w3_v1, field_2_w3_v2, field_2_w3_v3, field_3_w2trace, \ +field_3_stencil_size, \ field_3_stencil_dofmap, ndf_wtheta, undf_wtheta, map_wtheta, \ basis_wtheta_qr_xyoz, ndf_w3, undf_w3, map_w3, basis_w3_qr_xyoz, \ diff_basis_w3_qr_xyoz, ndf_w2trace, undf_w2trace, map_w2trace, \ np_xy_qr_xyoz, np_z_qr_xyoz, weights_xy_qr_xyoz, weights_z_qr_xyoz) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w2trace integer(kind=i_def), dimension(ndf_w2trace), intent(in) :: map_w2trace integer(kind=i_def), intent(in) :: ndf_w3 @@ -186,14 +187,15 @@ def test_int_field_all_stencils_gen_stub(fortran_writer): public contains - subroutine testkern_stencil_multi_int_field_code(nlayers, field_1_w2broken, \ + subroutine testkern_stencil_multi_int_field_code(nlayers_field_1, \ +field_1_w2broken, \ field_2_w1, field_2_stencil_size, field_2_stencil_dofmap, field_3_w0, \ field_3_stencil_size, field_3_direction, field_3_stencil_dofmap, field_4_w2v, \ field_4_stencil_size, field_4_stencil_dofmap, ndf_w2broken, undf_w2broken, \ map_w2broken, ndf_w1, undf_w1, map_w1, ndf_w0, undf_w0, map_w0, ndf_w2v, \ undf_w2v, map_w2v) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w0 integer(kind=i_def), dimension(ndf_w0), intent(in) :: map_w0 integer(kind=i_def), intent(in) :: ndf_w1 @@ -253,14 +255,14 @@ def test_real_int_field_gen_stub(fortran_writer): public contains - subroutine testkern_field_code(nlayers, rscalar_1, field_2_w1, field_3_w2, \ -field_4_wtheta, field_5_w3, iscalar_6, ndf_w1, undf_w1, map_w1, \ + subroutine testkern_field_code(nlayers_field_2, rscalar_1, field_2_w1, \ +field_3_w2, field_4_wtheta, field_5_w3, iscalar_6, ndf_w1, undf_w1, map_w1, \ basis_w1_qr_xyoz, diff_basis_w1_qr_xyoz, ndf_w2, undf_w2, map_w2, ndf_wtheta, \ undf_wtheta, map_wtheta, ndf_w3, undf_w3, map_w3, basis_w3_qr_xyoz, \ diff_basis_w3_qr_xyoz, np_xy_qr_xyoz, np_z_qr_xyoz, weights_xy_qr_xyoz, \ weights_z_qr_xyoz) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_2 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1 integer(kind=i_def), intent(in) :: ndf_w2 diff --git a/src/psyclone/tests/domain/lfric/lfric_mesh_props_stubgen_test.py b/src/psyclone/tests/domain/lfric/lfric_mesh_props_stubgen_test.py index 581f8fde3e..5bade47860 100644 --- a/src/psyclone/tests/domain/lfric/lfric_mesh_props_stubgen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_mesh_props_stubgen_test.py @@ -66,10 +66,10 @@ def test_mesh_prop_stub_gen(fortran_writer): public contains - subroutine testkern_mesh_prop_code(nlayers, rscalar_1, field_2_w1, ndf_w1, \ -undf_w1, map_w1, nfaces_re_h, adjacent_face) + subroutine testkern_mesh_prop_code(nlayers_field_2, rscalar_1, field_2_w1, \ +ndf_w1, undf_w1, map_w1, nfaces_re_h, adjacent_face) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_2 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1 integer(kind=i_def), intent(in) :: undf_w1 @@ -101,13 +101,13 @@ def test_mesh_props_quad_stub_gen(fortran_writer): public contains - subroutine testkern_mesh_prop_quad_code(nlayers, field_1_w1, \ + subroutine testkern_mesh_prop_quad_code(nlayers_field_1, field_1_w1, \ field_2_wtheta, ndf_w1, undf_w1, map_w1, basis_w1_qr_xyoz, ndf_wtheta, \ undf_wtheta, map_wtheta, basis_wtheta_qr_xyoz, nfaces_re_h, nfaces_re, \ normals_to_horiz_faces, out_normals_to_faces, adjacent_face, np_xy_qr_xyoz, \ np_z_qr_xyoz, weights_xy_qr_xyoz, weights_z_qr_xyoz) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1 integer(kind=i_def), intent(in) :: ndf_wtheta diff --git a/src/psyclone/tests/domain/lfric/lfric_ref_elem_stubgen_test.py b/src/psyclone/tests/domain/lfric/lfric_ref_elem_stubgen_test.py index c166001fd2..e6978ce1b5 100644 --- a/src/psyclone/tests/domain/lfric/lfric_ref_elem_stubgen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_ref_elem_stubgen_test.py @@ -62,12 +62,12 @@ def test_refelem_stub_gen(fortran_writer): public contains - subroutine testkern_ref_elem_code(nlayers, rscalar_1, field_2_w1, \ + subroutine testkern_ref_elem_code(nlayers_field_2, rscalar_1, field_2_w1, \ field_3_w2, field_4_w2, field_5_w3, ndf_w1, undf_w1, map_w1, ndf_w2, \ undf_w2, map_w2, ndf_w3, undf_w3, map_w3, nfaces_re_h, nfaces_re_v, \ normals_to_horiz_faces, normals_to_vert_faces) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_2 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1 integer(kind=i_def), intent(in) :: ndf_w2 @@ -107,14 +107,14 @@ def test_refelem_quad_stub_gen(fortran_writer): gen = fortran_writer(kernel.gen_stub) output1 = ( - " subroutine testkern_refelem_quad_code(nlayers, field_1_w1, " + " subroutine testkern_refelem_quad_code(nlayers_field_1, field_1_w1, " "field_2_wtheta, ndf_w1, undf_w1, map_w1, basis_w1_qr_xyoz, " "ndf_wtheta, undf_wtheta, map_wtheta, basis_wtheta_qr_xyoz, " "nfaces_re, normals_to_faces, out_normals_to_faces, np_xy_qr_xyoz, " "np_z_qr_xyoz, weights_xy_qr_xyoz, weights_z_qr_xyoz)") assert output1 in gen assert """\ - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1 integer(kind=i_def), intent(in) :: ndf_wtheta diff --git a/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py b/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py index 27aaac7643..72f8ec965c 100644 --- a/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py @@ -88,11 +88,11 @@ def test_stub_generate_with_scalars(): public contains - subroutine testkern_three_scalars_code(nlayers, rscalar_1, field_2_w1, \ -field_3_w2, field_4_w2, field_5_w3, lscalar_6, iscalar_7, ndf_w1, undf_w1, \ -map_w1, ndf_w2, undf_w2, map_w2, ndf_w3, undf_w3, map_w3) + subroutine testkern_three_scalars_code(nlayers_field_2, rscalar_1, \ +field_2_w1, field_3_w2, field_4_w2, field_5_w3, lscalar_6, iscalar_7, \ +ndf_w1, undf_w1, map_w1, ndf_w2, undf_w2, map_w2, ndf_w3, undf_w3, map_w3) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_2 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1 integer(kind=i_def), intent(in) :: ndf_w2 @@ -144,12 +144,12 @@ def test_stub_generate_with_scalar_array(): public contains - subroutine testkern_scalar_array_code(nlayers, field_1_w1, \ + subroutine testkern_scalar_array_code(nlayers_field_1, field_1_w1, \ dims_rscalar_array_2, rscalar_array_2, dims_lscalar_array_3, \ lscalar_array_3, dims_iscalar_array_4, iscalar_array_4, \ iscalar_5, ndf_w1, undf_w1, map_w1) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1 integer(kind=i_def), intent(in) :: undf_w1 diff --git a/src/psyclone/tests/domain/lfric/lfric_stencil_stubgen_test.py b/src/psyclone/tests/domain/lfric/lfric_stencil_stubgen_test.py index 8953f8b13f..c0e9eaa46a 100644 --- a/src/psyclone/tests/domain/lfric/lfric_stencil_stubgen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_stencil_stubgen_test.py @@ -32,7 +32,7 @@ def test_stub_stencil_extent(fortran_writer): kernel.load_meta(metadata) generated_code = fortran_writer(kernel.gen_stub) result1 = ( - "subroutine testkern_stencil_code(nlayers, field_1_w1, " + "subroutine testkern_stencil_code(nlayers_field_1, field_1_w1, " "field_2_w2, field_2_stencil_size, field_2_stencil_dofmap, " "field_3_w2, field_4_w3, ndf_w1, undf_w1, map_w1, ndf_w2, " "undf_w2, map_w2, ndf_w3, undf_w3, map_w3)") @@ -59,8 +59,9 @@ def test_stub_cross2d_stencil(fortran_writer): kernel.load_meta(metadata) generated_code = fortran_writer(kernel.gen_stub) result1 = ( - " subroutine testkern_stencil_cross2d_code(nlayers, field_1_w1, " - "field_2_w2, field_2_stencil_size, field_2_max_branch_length, " + " subroutine testkern_stencil_cross2d_code(nlayers_field_1, " + "field_1_w1, field_2_w2, field_2_stencil_size, " + "field_2_max_branch_length, " "field_2_stencil_dofmap, field_3_w2, field_4_w3, ndf_w1, undf_w1, " "map_w1, ndf_w2, undf_w2, map_w2, ndf_w3, undf_w3, map_w3)" ) @@ -87,8 +88,8 @@ def test_stub_stencil_direction(fortran_writer): kernel.load_meta(metadata) code = fortran_writer(kernel.gen_stub) assert ( - " subroutine testkern_stencil_xory1d_code(nlayers, field_1_w1, " - "field_2_w2, field_2_stencil_size, field_2_direction, " + " subroutine testkern_stencil_xory1d_code(nlayers_field_1, " + "field_1_w1, field_2_w2, field_2_stencil_size, field_2_direction, " "field_2_stencil_dofmap, field_3_w2, field_4_w3, ndf_w1, undf_w1, " "map_w1, ndf_w2, undf_w2, map_w2, ndf_w3, undf_w3, map_w3)" in code) assert "integer(kind=i_def), intent(in) :: field_2_stencil_size\n" in code @@ -110,9 +111,9 @@ def test_stub_stencil_vector(fortran_writer): kernel.load_meta(metadata) code = fortran_writer(kernel.gen_stub) assert ( - " subroutine testkern_stencil_vector_code(nlayers, field_1_w0_v1, " - "field_1_w0_v2, field_1_w0_v3, field_2_w3_v1, field_2_w3_v2, " - "field_2_w3_v3, field_2_w3_v4, field_2_stencil_size, " + " subroutine testkern_stencil_vector_code(nlayers_field_1, " + "field_1_w0_v1, field_1_w0_v2, field_1_w0_v3, field_2_w3_v1, " + "field_2_w3_v2, field_2_w3_v3, field_2_w3_v4, field_2_stencil_size, " "field_2_stencil_dofmap, ndf_w0, undf_w0, map_w0, ndf_w3, undf_w3, " "map_w3)" in code) assert "integer(kind=i_def), intent(in) :: field_2_stencil_size\n" in code @@ -133,9 +134,10 @@ def test_stub_stencil_multi(fortran_writer): kernel.load_meta(metadata) code = fortran_writer(kernel.gen_stub) assert ( - " subroutine testkern_stencil_multi_code(nlayers, field_1_w1, " - "field_2_w2, field_2_stencil_size, field_2_stencil_dofmap, field_3_w2," - " field_3_stencil_size, field_3_direction, field_3_stencil_dofmap, " + " subroutine testkern_stencil_multi_code(nlayers_field_1, field_1_w1," + " field_2_w2, field_2_stencil_size, field_2_stencil_dofmap, " + "field_3_w2, field_3_stencil_size, field_3_direction, " + "field_3_stencil_dofmap, " "field_4_w3, field_4_stencil_size, field_4_stencil_dofmap, ndf_w1, " "undf_w1, map_w1, ndf_w2, undf_w2, map_w2, ndf_w3, undf_w3, map_w3)" in code) diff --git a/src/psyclone/tests/lfric_basis_test.py b/src/psyclone/tests/lfric_basis_test.py index 080e466ce8..41e1cfe944 100644 --- a/src/psyclone/tests/lfric_basis_test.py +++ b/src/psyclone/tests/lfric_basis_test.py @@ -1464,10 +1464,11 @@ def test_basis_evaluator(fortran_writer): code = fortran_writer(kernel.gen_stub) assert ( - "subroutine dummy_code(cell, nlayers, field_1_w0, op_2_ncell_3d, " - "op_2, field_3_w2, op_4_ncell_3d, op_4, field_5_wtheta, " - "op_6_ncell_3d, op_6, field_7_w2v, op_8_ncell_3d, op_8, field_9_wchi, " - "op_10_ncell_3d, op_10, field_11_w2vtrace, op_12_ncell_3d, op_12, " + "subroutine dummy_code(cell, nlayers_field_1, field_1_w0, " + "op_2_ncell_3d, op_2, field_3_w2, op_4_ncell_3d, op_4, " + "field_5_wtheta, op_6_ncell_3d, op_6, field_7_w2v, op_8_ncell_3d, " + "op_8, field_9_wchi, op_10_ncell_3d, op_10, field_11_w2vtrace, " + "op_12_ncell_3d, op_12, " "ndf_w0, undf_w0, map_w0, basis_w0_on_w0, ndf_w1, basis_w1_on_w0, " "ndf_w2, undf_w2, map_w2, basis_w2_on_w0, ndf_w3, basis_w3_on_w0, " "ndf_wtheta, undf_wtheta, map_wtheta, basis_wtheta_on_w0, ndf_w2h, " @@ -1476,7 +1477,7 @@ def test_basis_evaluator(fortran_writer): "basis_wchi_on_w0, ndf_w2trace, basis_w2trace_on_w0, ndf_w2vtrace, " "undf_w2vtrace, map_w2vtrace, basis_w2vtrace_on_w0, ndf_w2htrace, " "basis_w2htrace_on_w0)" in code) - assert "integer(kind=i_def), intent(in) :: nlayers" in code + assert "integer(kind=i_def), intent(in) :: nlayers_field_1" in code assert "integer(kind=i_def), intent(in) :: ndf_w0" in code assert ("integer(kind=i_def), dimension(ndf_w0), intent(in) " ":: map_w0" in code) @@ -1677,7 +1678,8 @@ def test_diff_basis(fortran_writer): " implicit none\n" " public\n\n" " contains\n" - " subroutine dummy_code(cell, nlayers, field_1_w0, op_2_ncell_3d, " + " subroutine dummy_code(cell, nlayers_field_1, " + "field_1_w0, op_2_ncell_3d, " "op_2, field_3_w2, op_4_ncell_3d, op_4, field_5_wtheta, " "op_6_ncell_3d, op_6, field_7_w2v, op_8_ncell_3d, op_8, field_9_wchi, " "op_10_ncell_3d, op_10, field_11_w2htrace, op_12_ncell_3d, op_12, " @@ -1694,7 +1696,7 @@ def test_diff_basis(fortran_writer): "weights_xy_qr_xyoz, weights_z_qr_xyoz)\n" " use constants_mod\n") in code - assert "integer(kind=i_def), intent(in) :: nlayers" in code + assert "integer(kind=i_def), intent(in) :: nlayers_field_1" in code assert "integer(kind=i_def), intent(in) :: ndf_w0" in code assert ("integer(kind=i_def), dimension(ndf_w0), intent(in) :: map_w0" in code) @@ -1854,7 +1856,8 @@ def test_diff_basis_eval(fortran_writer): " public\n" "\n" " contains\n" - " subroutine dummy_code(cell, nlayers, field_1_w0, op_2_ncell_3d, " + " subroutine dummy_code(cell, nlayers_field_1, " + "field_1_w0, op_2_ncell_3d, " "op_2, field_3_w2, op_4_ncell_3d, op_4, field_5_wtheta, " "op_6_ncell_3d, op_6, field_7_w2v, op_8_ncell_3d, op_8, field_9_wchi, " "op_10_ncell_3d, op_10, field_11_w2vtrace, op_12_ncell_3d, op_12, " @@ -1871,7 +1874,7 @@ def test_diff_basis_eval(fortran_writer): assert output_args in generated_code assert """\ use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w0 integer(kind=i_def), dimension(ndf_w0), intent(in) :: map_w0 integer(kind=i_def), intent(in) :: ndf_w2 @@ -1966,7 +1969,8 @@ def test_2eval_stubgen(fortran_writer): generated_code = fortran_writer(kernel.gen_stub) assert ( - "subroutine dummy_code(cell, nlayers, field_1_w0, op_2_ncell_3d, " + "subroutine dummy_code(cell, nlayers_field_1, " + "field_1_w0, op_2_ncell_3d, " "op_2, field_3_w2, op_4_ncell_3d, op_4, field_5_wtheta, " "op_6_ncell_3d, op_6, field_7_w2v, op_8_ncell_3d, op_8, " "field_9_wchi, op_10_ncell_3d, op_10, field_11_w2vtrace, " @@ -1989,7 +1993,7 @@ def test_2eval_stubgen(fortran_writer): generated_code) assert """\ use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w0 integer(kind=i_def), dimension(ndf_w0), intent(in) :: map_w0 integer(kind=i_def), intent(in) :: ndf_w2 diff --git a/src/psyclone/tests/lfric_cma_test.py b/src/psyclone/tests/lfric_cma_test.py index a2c6a71d45..a273e43068 100644 --- a/src/psyclone/tests/lfric_cma_test.py +++ b/src/psyclone/tests/lfric_cma_test.py @@ -315,11 +315,11 @@ def test_cma_mdata_asm_fld_stencil_error(): in str(excinfo.value)) -@pytest.mark.parametrize("mdata_name", ["NLEVELS", "NDATA"]) -def test_cma_mdata_asm_fld_nlevels_ndata_error(mdata_name): +@pytest.mark.parametrize("mdata_name", ["NLAYERS", "NDATA"]) +def test_cma_mdata_asm_fld_nlayers_ndata_error(mdata_name): ''' Check that we raise the expected error if a CMA kernel has an argument - with a non-default value of either NLEVELS or NDATA. + with a non-default value of either NLAYERS or NDATA. ''' code = CMA_ASSEMBLE.replace( "arg_type(gh_field, gh_real, gh_read, any_space_1)", @@ -1312,18 +1312,18 @@ def test_cma_asm_stub_gen(): public contains - subroutine columnwise_op_asm_kernel_code(cell, nlayers, ncell_2d, \ + subroutine columnwise_op_asm_kernel_code(cell, nlayers_op_1, ncell_2d, \ op_1_ncell_3d, op_1, cma_op_2, cma_op_2_nrow, cma_op_2_ncol, \ cma_op_2_bandwidth, cma_op_2_alpha, cma_op_2_beta, cma_op_2_gamma_m, \ cma_op_2_gamma_p, ndf_ads1_op_1, cbanded_map_ads1_op_1, ndf_ads2_op_1, \ cbanded_map_ads2_op_1) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_op_1 integer(kind=i_def), intent(in) :: ndf_ads1_op_1 - integer(kind=i_def), dimension(ndf_ads1_op_1,nlayers), intent(in) :: \ + integer(kind=i_def), dimension(ndf_ads1_op_1,nlayers_op_1), intent(in) :: \ cbanded_map_ads1_op_1 integer(kind=i_def), intent(in) :: ndf_ads2_op_1 - integer(kind=i_def), dimension(ndf_ads2_op_1,nlayers), intent(in) :: \ + integer(kind=i_def), dimension(ndf_ads2_op_1,nlayers_op_1), intent(in) :: \ cbanded_map_ads2_op_1 integer(kind=i_def), intent(in) :: cell integer(kind=i_def), intent(in) :: ncell_2d @@ -1364,21 +1364,21 @@ def test_cma_asm_with_field_stub_gen(): public contains - subroutine columnwise_op_asm_field_kernel_code(cell, nlayers, ncell_2d, \ -field_1_as1_field_1, op_2_ncell_3d, op_2, cma_op_3, cma_op_3_nrow, \ + subroutine columnwise_op_asm_field_kernel_code(cell, nlayers_field_1, \ +ncell_2d, field_1_as1_field_1, op_2_ncell_3d, op_2, cma_op_3, cma_op_3_nrow, \ cma_op_3_ncol, cma_op_3_bandwidth, cma_op_3_alpha, cma_op_3_beta, \ cma_op_3_gamma_m, cma_op_3_gamma_p, ndf_as1_field_1, undf_as1_field_1, \ map_as1_field_1, cbanded_map_as1_field_1, ndf_as2_op_2, cbanded_map_as2_op_2) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_as1_field_1 integer(kind=i_def), dimension(ndf_as1_field_1), intent(in) :: \ map_as1_field_1 - integer(kind=i_def), dimension(ndf_as1_field_1,nlayers), intent(in) :: \ -cbanded_map_as1_field_1 + integer(kind=i_def), dimension(ndf_as1_field_1,nlayers_field_1), \ +intent(in) :: cbanded_map_as1_field_1 integer(kind=i_def), intent(in) :: ndf_as2_op_2 - integer(kind=i_def), dimension(ndf_as2_op_2,nlayers), intent(in) :: \ -cbanded_map_as2_op_2 + integer(kind=i_def), dimension(ndf_as2_op_2,nlayers_field_1), \ +intent(in) :: cbanded_map_as2_op_2 integer(kind=i_def), intent(in) :: undf_as1_field_1 integer(kind=i_def), intent(in) :: cell integer(kind=i_def), intent(in) :: ncell_2d @@ -1421,18 +1421,18 @@ def test_cma_asm_same_fs_stub_gen(): public contains - subroutine columnwise_op_asm_same_fs_kernel_code(cell, nlayers, ncell_2d, \ -op_1_ncell_3d, op_1, field_2_as1_op_1, cma_op_3, cma_op_3_nrow, \ + subroutine columnwise_op_asm_same_fs_kernel_code(cell, nlayers_op_1, \ +ncell_2d, op_1_ncell_3d, op_1, field_2_as1_op_1, cma_op_3, cma_op_3_nrow, \ cma_op_3_bandwidth, cma_op_3_alpha, cma_op_3_beta, cma_op_3_gamma_m, \ cma_op_3_gamma_p, ndf_as1_op_1, undf_as1_op_1, map_as1_op_1, \ ndf_as2_op_1, cbanded_map_as2_op_1) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_op_1 integer(kind=i_def), intent(in) :: ndf_as1_op_1 integer(kind=i_def), dimension(ndf_as1_op_1), intent(in) :: \ map_as1_op_1 integer(kind=i_def), intent(in) :: ndf_as2_op_1 - integer(kind=i_def), dimension(ndf_as2_op_1,nlayers), intent(in) :: \ + integer(kind=i_def), dimension(ndf_as2_op_1,nlayers_op_1), intent(in) :: \ cbanded_map_as2_op_1 integer(kind=i_def), intent(in) :: undf_as1_op_1 integer(kind=i_def), intent(in) :: cell diff --git a/src/psyclone/tests/lfric_lma_test.py b/src/psyclone/tests/lfric_lma_test.py index 6e32fc7ddb..d1cbe0c3ed 100644 --- a/src/psyclone/tests/lfric_lma_test.py +++ b/src/psyclone/tests/lfric_lma_test.py @@ -884,7 +884,8 @@ def test_operators(fortran_writer): public contains - subroutine dummy_code(cell, nlayers, op_1_ncell_3d, op_1, op_2_ncell_3d, \ + subroutine dummy_code(cell, nlayers_op_1, op_1_ncell_3d, op_1, \ +op_2_ncell_3d, \ op_2, op_3_ncell_3d, op_3, op_4_ncell_3d, op_4, op_5_ncell_3d, op_5, \ op_6_ncell_3d, op_6, op_7_ncell_3d, op_7, op_8_ncell_3d, op_8, op_9_ncell_3d, \ op_9, op_10_ncell_3d, op_10, op_11_ncell_3d, op_11, op_12_ncell_3d, op_12, \ @@ -892,7 +893,7 @@ def test_operators(fortran_writer): ndf_w2broken, ndf_w2trace, ndf_w2htrace, ndf_w2vtrace, ndf_w3, ndf_wtheta, \ ndf_as1_op_12, ndf_ads1_op_13) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_op_1 integer(kind=i_def), intent(in) :: ndf_w0 integer(kind=i_def), intent(in) :: ndf_w1 integer(kind=i_def), intent(in) :: ndf_w2 @@ -999,7 +1000,8 @@ def test_stub_operator_different_spaces(fortran_writer): kernel = LFRicKern() kernel.load_meta(metadata) result = fortran_writer(kernel.gen_stub) - assert "(cell, nlayers, op_1_ncell_3d, op_1, ndf_w0, ndf_w1)" in result + assert ("(cell, nlayers_op_1, op_1_ncell_3d, op_1, ndf_w0, ndf_w1)" + in result) assert "dimension(op_1_ncell_3d,ndf_w0,ndf_w1)" in result # Check for discontinuous to- and from- spaces code = OPERATOR_DIFFERENT_SPACES.replace( @@ -1010,7 +1012,7 @@ def test_stub_operator_different_spaces(fortran_writer): kernel = LFRicKern() kernel.load_meta(metadata) result = fortran_writer(kernel.gen_stub) - assert ("(cell, nlayers, op_1_ncell_3d, op_1, ndf_w3, ndf_ads2_op_1)" + assert ("(cell, nlayers_op_1, op_1_ncell_3d, op_1, ndf_w3, ndf_ads2_op_1)" in result) assert "dimension(op_1_ncell_3d,ndf_w3,ndf_ads2_op_1)" in result field_descriptor = metadata.arg_descriptors[0] diff --git a/src/psyclone/tests/lfric_quadrature_test.py b/src/psyclone/tests/lfric_quadrature_test.py index 8215b25057..255dae34ac 100644 --- a/src/psyclone/tests/lfric_quadrature_test.py +++ b/src/psyclone/tests/lfric_quadrature_test.py @@ -794,8 +794,8 @@ def test_qr_basis_stub(fortran_writer): public contains - subroutine dummy_code(cell, nlayers, field_1_w0, op_2_ncell_3d, op_2, \ -field_3_w2, op_4_ncell_3d, op_4, field_5_wtheta, op_6_ncell_3d, op_6, \ + subroutine dummy_code(cell, nlayers_field_1, field_1_w0, op_2_ncell_3d, \ +op_2, field_3_w2, op_4_ncell_3d, op_4, field_5_wtheta, op_6_ncell_3d, op_6, \ field_7_w2v, op_8_ncell_3d, op_8, field_9_wchi, op_10_ncell_3d, op_10, \ field_11_w2htrace, op_12_ncell_3d, op_12, ndf_w0, undf_w0, map_w0, \ basis_w0_qr_xyoz, ndf_w1, basis_w1_qr_xyoz, ndf_w2, undf_w2, map_w2, \ @@ -807,7 +807,7 @@ def test_qr_basis_stub(fortran_writer): basis_w2htrace_qr_xyoz, ndf_w2vtrace, basis_w2vtrace_qr_xyoz, np_xy_qr_xyoz, \ np_z_qr_xyoz, weights_xy_qr_xyoz, weights_z_qr_xyoz) use constants_mod - integer(kind=i_def), intent(in) :: nlayers + integer(kind=i_def), intent(in) :: nlayers_field_1 integer(kind=i_def), intent(in) :: ndf_w0 integer(kind=i_def), dimension(ndf_w0), intent(in) :: map_w0 integer(kind=i_def), intent(in) :: ndf_w2 diff --git a/src/psyclone/tests/lfric_stubgen_test.py b/src/psyclone/tests/lfric_stubgen_test.py index 834970dadd..1c517be2f9 100644 --- a/src/psyclone/tests/lfric_stubgen_test.py +++ b/src/psyclone/tests/lfric_stubgen_test.py @@ -76,10 +76,10 @@ def test_stub_generate_with_anyw2(): " public\n" "\n" " contains\n" - " subroutine simple_code(nlayers, field_1_w1, ndf_w1, undf_w1," + " subroutine simple_code(nlayers_field_1, field_1_w1, ndf_w1, undf_w1," " map_w1)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_field_1\n" " integer(kind=i_def), intent(in) :: ndf_w1\n" " integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1\n" " integer(kind=i_def), intent(in) :: undf_w1\n" @@ -149,10 +149,10 @@ def test_intent(fortran_writer): " public\n" "\n" " contains\n" - " subroutine dummy_code(nlayers, field_1_w3, field_2_w1, " + " subroutine dummy_code(nlayers_field_1, field_1_w3, field_2_w1, " "field_3_w1, ndf_w3, undf_w3, map_w3, ndf_w1, undf_w1, map_w1)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_field_1\n" " integer(kind=i_def), intent(in) :: ndf_w1\n" " integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1\n" " integer(kind=i_def), intent(in) :: ndf_w3\n" @@ -217,7 +217,7 @@ def test_spaces(fortran_writer): " public\n" "\n" " contains\n" - " subroutine dummy_code(nlayers, field_1_w0, field_2_w1, " + " subroutine dummy_code(nlayers_field_1, field_1_w0, field_2_w1, " "field_3_w2, field_4_w2broken, field_5_w2trace, field_6_w3, " "field_7_wtheta, field_8_w2h, field_9_w2v, field_10_w2htrace, " "field_11_w2vtrace, field_12_wchi, " @@ -229,7 +229,7 @@ def test_spaces(fortran_writer): "map_w2htrace, ndf_w2vtrace, undf_w2vtrace, map_w2vtrace, " "ndf_wchi, undf_wchi, map_wchi)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_field_1\n" " integer(kind=i_def), intent(in) :: ndf_w0\n" " integer(kind=i_def), dimension(ndf_w0), intent(in) :: map_w0\n" " integer(kind=i_def), intent(in) :: ndf_w1\n" @@ -343,13 +343,13 @@ def test_any_spaces(fortran_writer): " public\n" "\n" " contains\n" - " subroutine dummy_code(nlayers, field_1_ads1_field_1, " + " subroutine dummy_code(nlayers_field_1, field_1_ads1_field_1, " "field_2_as7_field_2, field_3_ads4_field_3, " "ndf_ads1_field_1, undf_ads1_field_1, map_ads1_field_1, " "ndf_as7_field_2, undf_as7_field_2, map_as7_field_2, " "ndf_ads4_field_3, undf_ads4_field_3, map_ads4_field_3)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_field_1\n" " integer(kind=i_def), intent(in) :: ndf_ads1_field_1\n" " integer(kind=i_def), dimension(" "ndf_ads1_field_1), intent(in) :: map_ads1_field_1\n" @@ -406,10 +406,10 @@ def test_vectors(fortran_writer): " public\n" "\n" " contains\n" - " subroutine dummy_code(nlayers, field_1_w0_v1, " + " subroutine dummy_code(nlayers_field_1, field_1_w0_v1, " "field_1_w0_v2, field_1_w0_v3, ndf_w0, undf_w0, map_w0)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_field_1\n" " integer(kind=i_def), intent(in) :: ndf_w0\n" " integer(kind=i_def), dimension(ndf_w0), intent(in) :: map_w0\n" " integer(kind=i_def), intent(in) :: undf_w0\n" @@ -461,11 +461,11 @@ def test_enforce_bc_kernel_stub_gen(fortran_writer): " public\n" "\n" " contains\n" - " subroutine enforce_bc_code(nlayers, field_1_as1_field_1, " + " subroutine enforce_bc_code(nlayers_field_1, field_1_as1_field_1, " "ndf_as1_field_1, undf_as1_field_1, map_as1_field_1, " "boundary_dofs_field_1)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_field_1\n" " integer(kind=i_def), intent(in) :: ndf_as1_field_1\n" " integer(kind=i_def), " "dimension(ndf_as1_field_1), intent(in) :: map_as1_field_1\n" @@ -503,11 +503,11 @@ def test_enforce_op_bc_kernel_stub_gen(fortran_writer): " public\n" "\n" " contains\n" - " subroutine enforce_operator_bc_code(cell, nlayers, " + " subroutine enforce_operator_bc_code(cell, nlayers_op_1, " "op_1_ncell_3d, op_1, ndf_as1_op_1, ndf_as2_op_1, " "boundary_dofs_op_1)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_op_1\n" " integer(kind=i_def), intent(in) :: ndf_as1_op_1\n" " integer(kind=i_def), intent(in) :: ndf_as2_op_1\n" " integer(kind=i_def), intent(in) :: cell\n" @@ -534,8 +534,8 @@ def test_multi_qr_stub_gen(fortran_writer): kernel = LFRicKern() kernel.load_meta(metadata) generated_code = fortran_writer(kernel.gen_stub) - assert ("subroutine testkern_2qr_code(nlayers, field_1_w1, field_2_w2, " - "field_3_w2, field_4_w3, ndf_w1, undf_w1, map_w1, " + assert ("subroutine testkern_2qr_code(nlayers_field_1, field_1_w1, " + "field_2_w2, field_3_w2, field_4_w3, ndf_w1, undf_w1, map_w1, " "basis_w1_qr_face, basis_w1_qr_edge, ndf_w2, undf_w2, map_w2, " "diff_basis_w2_qr_face, diff_basis_w2_qr_edge, ndf_w3, undf_w3, " "map_w3, basis_w3_qr_face, basis_w3_qr_edge, " @@ -583,7 +583,8 @@ def test_qr_plus_eval_stub_gen(fortran_writer): kernel.load_meta(metadata) code = fortran_writer(kernel.gen_stub) assert ( - "subroutine testkern_qr_eval_code(nlayers, field_1_w1, field_2_w2," + "subroutine testkern_qr_eval_code(nlayers_field_1, field_1_w1, " + "field_2_w2," " field_3_w2, field_4_w3, ndf_w1, undf_w1, map_w1, basis_w1_qr_face, " "basis_w1_on_w1, ndf_w2, undf_w2, map_w2, diff_basis_w2_qr_face, " "diff_basis_w2_on_w1, ndf_w3, undf_w3, map_w3, basis_w3_qr_face, " @@ -647,10 +648,10 @@ def test_sub_name(fortran_writer): " public\n" "\n" " contains\n" - " subroutine dummy_code(nlayers, field_1_w1, " + " subroutine dummy_code(nlayers_field_1, field_1_w1, " "ndf_w1, undf_w1, map_w1)\n" " use constants_mod\n" - " integer(kind=i_def), intent(in) :: nlayers\n" + " integer(kind=i_def), intent(in) :: nlayers_field_1\n" " integer(kind=i_def), intent(in) :: ndf_w1\n" " integer(kind=i_def), dimension(ndf_w1), intent(in) :: map_w1\n" " integer(kind=i_def), intent(in) :: undf_w1\n" @@ -662,3 +663,30 @@ def test_sub_name(fortran_writer): "\n" "end module dummy_mod\n") assert output == generated_code + + +def test_nlayers_ndata_stub(fortran_writer): + '''Test the stub generation for a kernel that has arguments with custom + values for ndata and nlayers. + ''' + ast = fpapi.parse(os.path.join(BASE_PATH, + "testkern_nlayers_ndata_mod.F90"), + ignore_comments=False) + metadata = LFRicKernMetadata(ast) + kernel = LFRicKern() + kernel.load_meta(metadata) + code = fortran_writer(kernel.gen_stub) + expected = ("subroutine testkern_nlayers_ndata_code(" + "nlayers_field_2, nlayers_shallow, ndata_precip, " + "rscalar_1, field_2_w1, field_3_w2, field_4_w2_shallow, " + "field_5_w2_shallow, field_6_w2_precip, " + "field_7_w2_shallow_precip, " + "ndf_w1, undf_w1, map_w1, ndf_w2, undf_w2, map_w2, " + "ndf_w2_shallow, undf_w2_shallow, map_w2_shallow, " + "ndf_w2_precip, undf_w2_precip, map_w2_precip, " + "ndf_w2_shallow_precip, undf_w2_shallow_precip, " + "map_w2_shallow_precip)") + assert expected in code + assert "integer(kind=i_def), intent(in) :: nlayers_field_2" in code + assert "integer(kind=i_def), intent(in) :: nlayers_shallow" in code + assert "integer(kind=i_def), intent(in) :: ndata_precip" in code diff --git a/src/psyclone/tests/lfric_test.py b/src/psyclone/tests/lfric_test.py index 23b52b3440..7d4408e076 100644 --- a/src/psyclone/tests/lfric_test.py +++ b/src/psyclone/tests/lfric_test.py @@ -2250,7 +2250,7 @@ def test_mangle_function_space(): def test_no_mangle_specified_function_space(): ''' Test that we do not name-mangle a function space that is not - any_space or any_discontinuous_space. + any_space or any_discontinuous_space unless ndata or nlayers is specified. ''' _, invoke_info = parse(os.path.join(BASE_PATH, @@ -2264,6 +2264,21 @@ def test_no_mangle_specified_function_space(): short_name = FunctionSpace(fs_name, first_kernel.arguments).short_name assert mangled_name == fs_name assert short_name == fs_name + # When nlayers is specified. + fspace = FunctionSpace(fs_name, first_kernel.arguments, + nlayers="twod") + assert fspace.mangled_name == "w2_twod" + assert fspace.short_name == "w2" + # ndata=1 is the default so is not included in the name. + fspace = FunctionSpace(fs_name, first_kernel.arguments, + nlayers="twod", ndata="1") + assert fspace.mangled_name == "w2_twod" + assert fspace.short_name == "w2" + # With both nlayers and ndata + fspace = FunctionSpace(fs_name, first_kernel.arguments, + nlayers="3", ndata="physics") + assert fspace.mangled_name == "w2_3_physics" + assert fspace.short_name == "w2" @pytest.mark.parametrize( diff --git a/src/psyclone/tests/parse/kernel_test.py b/src/psyclone/tests/parse/kernel_test.py index eaf5f2b864..af4dac3c2b 100644 --- a/src/psyclone/tests/parse/kernel_test.py +++ b/src/psyclone/tests/parse/kernel_test.py @@ -688,16 +688,16 @@ def test_get_char_value(): ''' enode = ExpressionNode(["1"]) with pytest.raises(ParseError) as err: - _ = get_char_value(enode, "nlevels") - assert "not a valid nlevels specifier (expected" in str(err.value) + _ = get_char_value(enode, "nlayers") + assert "not a valid nlayers specifier (expected" in str(err.value) # Value must be a quoted string - node = NamedArg(["nlevels", "=", "1"]) + node = NamedArg(["nlayers", "=", "1"]) with pytest.raises(ParseError) as err: - _ = get_char_value(node, "nlevels") - assert ("nlevels must be specified as a quoted string but got nlevels=1" + _ = get_char_value(node, "nlayers") + assert ("nlayers must be specified as a quoted string but got nlayers=1" in str(err.value)) - node2 = NamedArg(["nlevels", "=", "'1'"]) - value = get_char_value(node2, "nlevels") + node2 = NamedArg(["nlayers", "=", "'1'"]) + value = get_char_value(node2, "nlayers") assert value == "1" diff --git a/src/psyclone/tests/test_files/lfric/1.5.6_single_invoke_nlevels_ndata.f90 b/src/psyclone/tests/test_files/lfric/1.5.6_single_invoke_nlayers_ndata.f90 similarity index 91% rename from src/psyclone/tests/test_files/lfric/1.5.6_single_invoke_nlevels_ndata.f90 rename to src/psyclone/tests/test_files/lfric/1.5.6_single_invoke_nlayers_ndata.f90 index d156a7509b..c840911eb3 100644 --- a/src/psyclone/tests/test_files/lfric/1.5.6_single_invoke_nlevels_ndata.f90 +++ b/src/psyclone/tests/test_files/lfric/1.5.6_single_invoke_nlayers_ndata.f90 @@ -36,16 +36,16 @@ program single_invoke_fs ! Description: invoke of single kernel which has field arguments with - ! non-default values for number of vertical levels and number of data + ! non-default values for number of vertical layers and number of data ! elements per dof. use constants_mod, only: r_def use field_mod, only: field_type - use testkern_nlevels_ndata_mod, only: testkern_nlevels_ndata_type + use testkern_nlayers_ndata_mod, only: testkern_nlayers_ndata_type implicit none real(kind=r_def) :: a type(field_type) :: f1, f2, f3, f4, f5, f6 - call invoke( testkern_nlevels_ndata_type(a, f1, f2, f3, f4, f5, f6) ) + call invoke( testkern_nlayers_ndata_type(a, f1, f2, f3, f4, f5, f6) ) end program single_invoke_fs diff --git a/src/psyclone/tests/test_files/lfric/testkern_nlevels_ndata_mod.F90 b/src/psyclone/tests/test_files/lfric/testkern_nlayers_ndata_mod.F90 similarity index 69% rename from src/psyclone/tests/test_files/lfric/testkern_nlevels_ndata_mod.F90 rename to src/psyclone/tests/test_files/lfric/testkern_nlayers_ndata_mod.F90 index 242961b9a6..bf24e7b442 100644 --- a/src/psyclone/tests/test_files/lfric/testkern_nlevels_ndata_mod.F90 +++ b/src/psyclone/tests/test_files/lfric/testkern_nlayers_ndata_mod.F90 @@ -32,8 +32,8 @@ ! Author: A. R. Porter, STFC Daresbury Lab !> An example LFRic kernel which has arguments with non-default values -!! of NLEVELS and NDATA. -module testkern_nlevels_ndata_mod +!! of NLAYERS and NDATA. +module testkern_nlayers_ndata_mod use argument_mod use fs_continuity_mod @@ -42,63 +42,60 @@ module testkern_nlevels_ndata_mod implicit none - type, extends(kernel_type) :: testkern_nlevels_ndata_type + type, extends(kernel_type) :: testkern_nlayers_ndata_type type(arg_type), dimension(7) :: meta_args = & (/ arg_type(gh_scalar, gh_real, gh_read), & arg_type(gh_field, gh_real, gh_inc, w1), & arg_type(gh_field, gh_real, gh_read, w2), & - ! Non-default number of levels. - arg_type(gh_field, gh_real, gh_read, w2, nlevels="shallow"), & - ! Non-default number of levels but same as previous arg. so + ! Non-default number of layers. + arg_type(gh_field, gh_real, gh_read, w2, nlayers="shallow"), & + ! Non-default number of layers but same as previous arg. so ! has same dof map. - arg_type(gh_field, gh_real, gh_read, w2, nlevels="shallow"), & + arg_type(gh_field, gh_real, gh_read, w2, nlayers="shallow"), & ! Non-default number of data values per dof. arg_type(gh_field, gh_real, gh_read, w2, ndata="precip"), & - ! Although we have seen the values of ndata and nlevels before + ! Although we have seen the values of ndata and nlayers before ! (individually), the dof map will be different. arg_type(gh_field, gh_real, gh_read, w2, ndata="precip", & - nlevels="shallow") & + nlayers="shallow") & /) integer :: operates_on = cell_column contains - procedure, nopass :: code => testkern_nlevels_ndata_code - end type testkern_nlevels_ndata_type + procedure, nopass :: code => testkern_nlayers_ndata_code + end type testkern_nlayers_ndata_type contains - subroutine testkern_nlevels_ndata_code(& - nlayers, ascalar, fld1, fld2, & - fld3, nlayers_fld3, & - fld5, & - fld4, ndata_fld4, & - fld6, & - ndf_w1, undf_w1, map_w1, & - ndf_w2, undf_w2, map_w2, & - ndf_fld3, undf_fld3, map_w2_fld3, & - ndf_fld4, undf_fld4, map_w2_fld4, & + subroutine testkern_nlayers_ndata_code( & + nlayers, nlayers_shallow, ndata_precip, & + ascalar, fld1, fld2, fld3, fld4, fld5, fld6, & + ndf_w1, undf_w1, map_w1, & + ndf_w2, undf_w2, map_w2, & + ndf_fld3, undf_fld3, map_w2_fld3, & + ndf_fld5, undf_fld5, map_w2_fld5, & ndf_fld6, undf_fld6, map_w2_fld6) implicit none integer(kind=i_def), intent(in) :: nlayers - integer(kind=i_def), intent(in) :: nlayers_fld3 - integer(kind=i_def), intent(in) :: ndata_fld4 - integer(kind=i_def), intent(in) :: ndf_w1, ndf_w2, ndf_fld3, ndf_fld4, & + integer(kind=i_def), intent(in) :: nlayers_shallow + integer(kind=i_def), intent(in) :: ndata_precip + integer(kind=i_def), intent(in) :: ndf_w1, ndf_w2, ndf_fld3, ndf_fld5, & ndf_fld6 - integer(kind=i_def), intent(in) :: undf_w1, undf_w2, undf_w3, undf_fld3, & - undf_fld4, undf_fld6 + integer(kind=i_def), intent(in) :: undf_w1, undf_w2, undf_fld3, & + undf_fld5, undf_fld6 integer(kind=i_def), intent(in), dimension(ndf_w1) :: map_w1 integer(kind=i_def), intent(in), dimension(ndf_w2) :: map_w2 integer(kind=i_def), intent(in), dimension(ndf_fld3) :: map_w2_fld3 - integer(kind=i_def), intent(in), dimension(ndf_fld4) :: map_w2_fld4 + integer(kind=i_def), intent(in), dimension(ndf_fld5) :: map_w2_fld5 integer(kind=i_def), intent(in), dimension(ndf_fld6) :: map_w2_fld6 real(kind=r_def), intent(in) :: ascalar real(kind=r_def), intent(inout), dimension(undf_w1) :: fld1 real(kind=r_def), intent(in), dimension(undf_w2) :: fld2 real(kind=r_def), intent(in), dimension(undf_fld3) :: fld3 - real(kind=r_def), intent(in), dimension(undf_fld3) :: fld5 - real(kind=r_def), intent(in), dimension(undf_w3) :: fld4 + real(kind=r_def), intent(in), dimension(undf_fld3) :: fld4 + real(kind=r_def), intent(in), dimension(undf_fld5) :: fld5 real(kind=r_def), intent(in), dimension(undf_fld6) :: fld6 - end subroutine testkern_nlevels_ndata_code + end subroutine testkern_nlayers_ndata_code -end module testkern_nlevels_ndata_mod +end module testkern_nlayers_ndata_mod