Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
234a26a
#868 add text describing nlevels and ndata
arporter Oct 21, 2025
060a1ea
#868 update the rules for kernel arguments
arporter Oct 21, 2025
716c908
#868 change nlevels to nlayers
arporter Oct 22, 2025
5a04ba3
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Nov 5, 2025
e878688
#868 extend docs to allow for naming of nlayers values
arporter Nov 5, 2025
5cdb284
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Apr 2, 2026
3d4e9b9
#868 update nlayers docs
arporter Apr 2, 2026
a0f3f15
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Apr 8, 2026
5db865a
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Apr 8, 2026
a0132df
Merge branch '868_nlayers_ndata_mdata' of github.com:stfc/PSyclone in…
arporter Apr 8, 2026
1bfa1af
#868 update the nlayers text to make it clear that named values are j…
arporter Apr 8, 2026
a6833a1
#868 add initial code to get nlevels metadata for a field arg
arporter Apr 8, 2026
0f54ac6
Merge branch '868_nlayers_ndata_mdata' of github.com:stfc/PSyclone in…
arporter Apr 8, 2026
b5751bb
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Apr 8, 2026
6e5b79a
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Apr 10, 2026
892245f
#868 update nlayers documentation to remove special gh_runtime tag
arporter Apr 10, 2026
1c18889
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Jun 5, 2026
07e8a29
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Jun 5, 2026
748d38d
#868 update docs for nlayers and ndata to use string values
arporter Jun 5, 2026
529158b
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Jun 8, 2026
66617c4
#868 begin adding support for ndata metadata
arporter Jun 8, 2026
64c2d26
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Jun 8, 2026
7a8c9e9
#868 WIP on initial implementation [skip ci]
arporter Jun 9, 2026
707db35
#868 tidy implementation
arporter Jun 9, 2026
268985b
#868 WIP updating psyir-metadata handling classes [skip ci]
arporter Jun 9, 2026
4b8ea08
#868 WIP extending metadata handling [skip ci]
arporter Jun 16, 2026
81d39f0
#868 more work extending metadata support
arporter Jun 16, 2026
9082088
Merge branch 'master' into 868_nlayers_ndata_mdata
arporter Jun 17, 2026
22c474d
#868 allow for tuple of fparser classes
arporter Jun 17, 2026
2f4820c
#868 rm unused utility and improve tests
arporter Jun 17, 2026
3f420a3
#868 simplify intergrid
arporter Jun 18, 2026
887a9d5
#868 fix cov of common_arg_metadata
arporter Jun 18, 2026
58d9787
#868 more coverage
arporter Jun 18, 2026
d56eebb
#868 add type hints
arporter Jun 18, 2026
36c031a
#868 extend support for inter-grid and vector inter-grid
arporter Jun 18, 2026
cfdf291
#868 improve doc string and typing
arporter Jun 22, 2026
c09444d
#868 update setup-python action to v5
arporter Jun 22, 2026
512ecdc
#868 update lfric workflow to use updated lfric_core with nlevels/ndata
arporter Jun 24, 2026
03203e7
#868 add use of ndata to testkern_mod and fix compilation tests
arporter Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lfric_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ on:
push

env:
LFRIC_APPS_HASH: 299d4f786dd2d86a59a184c3e53384005a69eded
LFRIC_APPS_HASH: f7714f2726e24574e5b4c42359f6e4728f268314
PYTHON_VERSION: "3.14"
GNU_TOOLCHAIN: gnu14_openmpi
NVHPC_TOOLCHAIN: nvhpc26_3_openmpi
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ jobs:
fail-fast: true
commit-filter: '[skip ci]'
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
Expand Down
108 changes: 87 additions & 21 deletions doc/user_guide/lfric.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,24 +162,34 @@ least rank (number of dimensions) one. Scalar arrays are identified with
Field
+++++

LFRic API fields, identified with ``GH_FIELD`` metadata, represent
FEM discretisations of various dynamical core prognostic and diagnostic
LFRic API fields, identified with ``GH_FIELD`` metadata, represent FEM
discretisations of various dynamical core prognostic and diagnostic
variables. In FEM, variables are discretised by placing them into a
function space (see :ref:`lfric-function-space`) from which they
inherit a polynomial expansion via the basis functions of that space.
Field values at points within a cell are evaluated as the sum of a set
of basis functions multiplied by coefficients which are the data points.
Points of evaluation are determined by a quadrature object
of basis functions multiplied by coefficients which are the data
points. Points of evaluation are determined by a quadrature object
(:ref:`lfric-quadrature`) and are independent of the function space
the field is on. Placement of field data points, also called degrees of
freedom (hereafter "DoFs"), is determined by the function space the field
is on.
the field is on. Placement of field data points, also called degrees
of freedom (hereafter "DoFs"), is determined by the function space the
field is on. An LFRic multi-data field can have more than one value
associated with each data point.

LFRic fields passed as arguments to any :ref:`LFRic kernel
<lfric-kernel-valid-data-type>` can be of ``real`` or ``integer``
primitive type. In the LFRic infrastructure, these fields are
represented by instances of the ``field_type`` and ``integer_field_type``
classes, respectively.

Different fields may be defined on different numbers of vertical layers.
The number of layers can be as few as one (a 2D field). Additionally,
LFRic has the concet of multi-data fields where multiple data values can be
associated with each DoF. Unfortunately, both the number of layers and the
number of data values affects the numbering of the DoFs of a field. Therefore,
a distinct DoF map is required for each unique combination of function
space, number of vertical levels and number of data values.

.. _lfric-field-vector:

Field Vector
Expand Down Expand Up @@ -919,8 +929,8 @@ All three CMA-related kernel types must obey the following rules:
1) Since a CMA operator only acts within a single column of data,
stencil operations are not permitted.

2) No vector quantities (e.g. ``GH_FIELD*3`` - see below) are
permitted as arguments.
2) No vector quantities (e.g. ``GH_FIELD*3`` - see below) or
multi-data fields are permitted as arguments.

3) The kernel must operate on cell-columns.

Expand Down Expand Up @@ -1450,7 +1460,7 @@ Supported Function Spaces
As mentioned in the :ref:`lfric-field` and :ref:`lfric-field-vector`
sections, the function space of an argument specifies how it maps
onto the underlying topology and, additionally, whether the data at a
point is a vector. In LFRic API the dimension of the basis function
point is a vector. In the LFRic API the dimension of the basis function
set for the scalar function spaces is 1 and for the vector function spaces
is 3 (see the table in :ref:`lfric-stub-generation-rules` for the
dimensions of the basis and differential basis functions).
Expand Down Expand Up @@ -1636,7 +1646,7 @@ to have stencil accesses, these two options are mutually exclusive.
The metadata for each case is described in the following sections.

Stencil Metadata
________________
""""""""""""""""


Stencil metadata specifies that the corresponding field argument is accessed
Expand Down Expand Up @@ -1716,8 +1726,7 @@ be found in ``examples/lfric/eg5``.
.. _lfric-intergrid-mdata:

Inter-Grid Metadata
___________________

"""""""""""""""""""

The alternative form of the optional fifth metadata argument for a
field specifies which mesh the associated field is on. This is
Expand Down Expand Up @@ -1748,6 +1757,52 @@ 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
"""""""""""""""""""""""""

If a particular field/operator kernel argument has a number of vertical
levels that is *not* the same as the first field/operator argument then
this must be specified using the ``NLAYERS`` option to ``GH_FIELD``/
``GH_OPERATOR``. The value specified for ``NLAYERS`` may be an integer
literal encoded as a string if it is known at compile time, e.g.::

arg_type(GH_FIELD, GH_REAL, GH_READ, W3, NLAYERS="1")

Alternatively, it may be given a name, e.g.::

arg_type(GH_FIELD, GH_REAL, GH_READ, W3, NLAYERS="some name")

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 (that of the first such field listed in the metadata)
is passed to the kernel for those arguments.

(Since the value of ``NLAYERS`` is looked-up from the corresponding kernel
argument at run time, the labels given in the kernel metadata are just that
- they do not have to correspond to anything in the LFRic infrastructure.)

Multi-Data Metadata
"""""""""""""""""""

A multi-data field is the same as a standard field apart from having multiple
values associated with each DoF. This is indicated in the field metadata by
the optional ``NDATA`` argument to GH_FIELD, e.g.::

arg_type(GH_FIELD, GH_REAL, GH_READ, W2, NDATA="4")

The value contained in the string specified for ``NDATA`` may be a literal if
it is known at compile time. Alternatively, it may be a name in which
case the number of data values at each DoF is determined at runtime by
querying the field object (in the generated PSy layer). As with ``NLAYERS``,
this name is just a label and does not have to correspond to anything in the
LFRic infrastructure.

Since the data in an LFRic field object is stored as a 1D array, having more
than one data value associated with each DoF affects the dofmap. This is
handled by passing the appropriate dofmap to the kernel - see
:ref:`lfric-stub-generation-rules`.


Column-wise Operators (CMA)
^^^^^^^^^^^^^^^^^^^^^^^^^^^

Expand Down Expand Up @@ -2080,7 +2135,12 @@ 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 then the kernel must be passed the
value of ``NDATA``: add an additional ``integer``, scalar
argument of kind ``i_def`` and intent ``in``.
6) If the field has a custom number of vertical levels then pass this as
an additional ``integer``, scalar argument of kind ``i_def`` and
intent ``in``.
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
Expand All @@ -2106,21 +2166,26 @@ 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) For each function space 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)
4) 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
space have differing numbers of vertical layers and/or ``NDATA`` values,
then each requires that a dofmap be supplied (because both the number of
vertical layers *and* the number of data values per DoF alter the
*values* within the map). For each required DoF map:

1) Include the number of local degrees of freedom (i.e. number per-cell)
for the function space. This is an ``integer`` of kind ``i_def`` and
has intent ``in``. The name of this argument is
``"ndf_"<field_function_space>``.

2) If there is a field on this space

1) Include the unique number of degrees of freedom for the function
space. This is an ``integer`` of kind ``i_def`` and has intent ``in``.
The name of this argument is ``"undf_"<field_function_space>``.
2) Include the **dofmap** for this function space. This is an ``integer``
2) Include the **dofmap** itself. This is an ``integer``
array of kind ``i_def`` with intent ``in``. It has one dimension
sized by the local degrees of freedom for the function space.

Expand Down Expand Up @@ -2443,8 +2508,9 @@ dofmap for both the to- and from-function spaces of the CMA
operator. Since it does not have any LMA operator arguments it does
not require the ``ncell_3d`` and ``nlayers`` scalar arguments. (Since a
column-wise operator is, by definition, assembled for a whole column,
there is no loop over levels when applying it.)
The full set of rules is then:
there is no loop over levels when applying it.) Note that fields with
non-standard ``nlayers`` or ``ndata > 1`` cannot be supplied as
arguments to CMA kernels. The full set of rules is:

1) Include the ``cell`` argument. ``cell`` is an ``integer`` of kind
``i_def`` and has intent ``in``.
Expand Down
27 changes: 13 additions & 14 deletions external/lfric_infrastructure/src/kernel_metadata/argument_mod.f90
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@











!-----------------------------------------------------------------------------
! Copyright (c) 2017, Met Office, on behalf of HMSO and Queen's Printer
! For further details please refer to the file LICENCE.original which you
! For further details please refer to the file LICENCE which you
! should have received as part of this distribution.
!-----------------------------------------------------------------------------

Expand Down Expand Up @@ -57,10 +46,10 @@ module argument_mod
!> @defgroup argument_type Enumeration of argument type property descriptors.
!> @{
integer, public, parameter :: GH_SCALAR = 397
integer, public, parameter :: GH_SCALAR_ARRAY = 973
integer, public, parameter :: GH_FIELD = 507
integer, public, parameter :: GH_OPERATOR = 735
integer, public, parameter :: GH_COLUMNWISE_OPERATOR = 841
integer, public, parameter :: GH_SCALAR_ARRAY = 973
!> @}

!> @defgroup data_type Enumeration of argument data type property descriptors.
Expand All @@ -77,7 +66,6 @@ module argument_mod
integer, public, parameter :: GH_READWRITE = 811
integer, public, parameter :: GH_INC = 542
integer, public, parameter :: GH_READINC = 420
integer, public, parameter :: GH_REDUCTION = 991
integer, public, parameter :: GH_SUM = 563
integer, public, parameter :: GH_MIN = 718
integer, public, parameter :: GH_MAX = 391
Expand Down Expand Up @@ -202,6 +190,10 @@ module argument_mod
integer, public, parameter :: DOF = 712
!> @}

!> The maximum length of those elements of argument metadata that
!! are of character type.
integer, private, parameter :: METADATA_CHAR_LENGTH = 40

!> Metadata for the argument type description, stored in the `arg_type` type
!! as an array of `meta_args`. We need to know how many scalars, fields
!! and/or operators are passed to the kernel and in what order they are
Expand Down Expand Up @@ -236,6 +228,13 @@ module argument_mod
!> Optional metadata (fields only) for inter-grid mapping kernels.
!! One of {GH_FINE, GH_COARSE}.
integer :: mesh_arg = -1
!> 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
!! the field has. Default is to use the number associated with the
!! first field/operator kernel argument.
character(len=METADATA_CHAR_LENGTH) :: nlevels = ""
end type arg_type

!> Optional metadata for the basis/differential basis functions required with
Expand Down
66 changes: 55 additions & 11 deletions src/psyclone/domain/lfric/kernel/common_arg_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
creation, modification and Fortran output of such an argument.

'''
from typing import Optional, Union
from fparser.two import Fortran2003
from fparser.two import utils as fp_utils

from psyclone.domain.lfric.kernel.common_metadata import CommonMetadata

Expand All @@ -47,7 +49,7 @@ class CommonArgMetadata(CommonMetadata):
'''Class to capture common LFRic kernel argument metadata.'''

# The fparser2 class that captures this metadata.
fparser2_class = Fortran2003.Part_Ref
fparser2_class = Fortran2003.Structure_Constructor

@staticmethod
def check_boolean(value, name):
Expand All @@ -64,19 +66,17 @@ def check_boolean(value, name):
f"'{type(value).__name__}'.")

@staticmethod
def check_nargs(fparser2_tree, nargs):
def check_nargs(fparser2_tree: Union[Fortran2003.Part_Ref,
Fortran2003.Structure_Constructor],
nargs: Union[int, tuple[int, int]]) -> None:
'''Checks that the metadata has the number of arguments specified
by the 'nargs' argument, otherwise an exception is raised.

:param fparser2_tree: fparser2 tree capturing a metadata argument.
:type fparser2_tree: :py:class:`fparser.two.Fortran2003.Part_Ref` | \
:py:class:`fparser.two.Fortran2003.Structure_Constructor`
:param nargs: the number of expected arguments. This can \
either be a single value or a list containing a lower and an \
upper value.
:type nargs: int or Tuple[int, int]
:param nargs: the number of expected arguments. This can either be
a single value or a list containing a lower and an upper value.

:raises ValueError: if the kernel metadata does not contain \
:raises ValueError: if the kernel metadata does not contain
the expected number of arguments (nargs).

'''
Expand All @@ -103,11 +103,11 @@ def check_fparser2_arg(cls, fparser2_tree, type_name):
Structure_Constructor which captures a metadata argument.

:param fparser2_tree: fparser2 tree capturing a metadata argument.
:type fparser2_tree: :py:class:`fparser.two.Fortran2003.Part_Ref` | \
:type fparser2_tree: :py:class:`fparser.two.Fortran2003.Part_Ref` |
:py:class:`fparser.two.Fortran2003.Structure_Constructor`
:param str type_name: the name of the argument datatype.

:raises ValueError: if the kernel metadata is not in \
:raises ValueError: if the kernel metadata is not in
the form arg_type(...).

'''
Expand Down Expand Up @@ -150,5 +150,49 @@ def get_arg(fparser2_tree, index):
# Metadata at the specified index does not exist.
return None

@staticmethod
def get_named_arg(fparser2_tree: fp_utils.Base,
name: str
) -> Optional[str]:
'''
Searches the supplied metadata for 'name=value' expressions and
returns the value corresponding to the supplied name if found.
Otherwise returns None. If the value is a string then it is
lower-cased.

:param fparser2_tree: the parse tree of the metadata.
:param name: the name of the metadata element that we want.

:returns: the value of the named metadata element or None if not found.

'''
for child in fp_utils.walk(fparser2_tree, Fortran2003.Component_Spec):
if child.children[0].tostr().lower() == name:
text = child.children[1].tostr()
if isinstance(child.children[1],
Fortran2003.Char_Literal_Constant):
# TODO fparser/#295 - fparser keeps the quotation marks
# in character strings.
return text[1:-1].lower()
return text
return None

@staticmethod
def _validate_named_args(fparser2_tree: fp_utils.Base,
valid_names: list[str]) -> None:
'''
Checks that any named arguments in the supplied parse tree match
with the names in `valid_names`.

:raises ValueError: if an unsupported named argument is found in
the supplied metadata.
'''
for child in fp_utils.walk(fparser2_tree, Fortran2003.Component_Spec):
name = child.children[0].tostr().lower()
if name not in valid_names:
raise ValueError(
f"Kernel metadata contains keyword argument '{name}' "
f"which is not one of the valid options: {valid_names}.")


__all__ = ["CommonArgMetadata"]
Loading
Loading