Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
c871676
#2381 add initial GlobalReduction class
arporter Oct 3, 2025
b0c458c
#2381 linting
arporter Oct 3, 2025
0d5dadf
#2381 mv LFRicGlobalSum out of lfric.py and rename
arporter Oct 6, 2025
6856abd
#2381 fix linting
arporter Oct 6, 2025
279ec53
#2381 ensure any pre-existing Config is wiped by test fixture
arporter Oct 7, 2025
4a2f3d4
#2381 WIP fixing/moving tests [skip ci]
arporter Oct 7, 2025
9eb6784
#2381 fix all tests
arporter Oct 7, 2025
5921958
#2381 tidy and improve tests
arporter Oct 7, 2025
de51056
Merge branch 'master' into 2381_arp_global_reduction
arporter Oct 7, 2025
49b856b
#2381 get full unit coverage for global_reduction
arporter Oct 7, 2025
6aa870c
Merge branch 'master' into 2381_arp_global_reduction
arporter Oct 7, 2025
355a900
#2381 update from global sum to global reduction
arporter Oct 7, 2025
88956f8
#2381 fix linting
arporter Oct 7, 2025
488d0a5
#2381 tidy test file
arporter Oct 9, 2025
c27c9ab
Merge branch 'master' into 2381_arp_global_reduction
arporter Oct 13, 2025
7fe581b
Merge branch '2381_arp_global_reduction' of github.com:stfc/PSyclone …
arporter Oct 13, 2025
5b4d364
Merge branch 'master' into 2381_arp_global_reduction
arporter Oct 15, 2025
faa440b
Merge branch '2381_arp_global_reduction' of github.com:stfc/PSyclone …
arporter Oct 15, 2025
d26c56c
#2381 update docs
arporter Oct 15, 2025
ae2fb6d
Merge branch 'master' into 2381_arp_global_reduction
arporter Nov 4, 2025
c6d6366
Merge branch 'master' into 2381_arp_global_reduction
arporter Nov 5, 2025
226cfeb
#2381 rejig class hierarchy
arporter Nov 5, 2025
134954d
#2381 start adding an LFRicGlobalMin to test new structure
arporter Nov 5, 2025
72dd101
#2381 tidy for lint
arporter Nov 5, 2025
cf9c5c3
Merge branch 'master' into 2381_no_domain_global_reduction
arporter Nov 13, 2025
fead32a
Merge branch 'master' into 2381_no_domain_global_reduction
arporter Nov 14, 2025
5a0654a
#2381 WIP updating lower_to_language_level [skip-ci]
arporter Nov 14, 2025
eee9422
Merge branch 'master' into 2381_no_domain_global_reduction
arporter Dec 12, 2025
9fc397b
#2381 flesh out LFRicGlobalMin
arporter Dec 12, 2025
0523346
#2381 make dag_name more general
arporter Dec 12, 2025
4bceba7
#2381 mv common init functionality to parent class
arporter Dec 15, 2025
833782e
Merge branch 'master' into 2381_no_domain_global_reduction
arporter Dec 16, 2025
438e405
#2381 add MIN and MAX AccessTypes and appropriate init in Kern
arporter Dec 16, 2025
3d53e98
#2381 tidy-up the reduction classes
arporter Dec 16, 2025
b460b3c
#2381 fix failing test
arporter Dec 16, 2025
c475f0c
#2381 WIP fixing tests after extending access-modes [skip ci]
arporter Dec 16, 2025
64874cd
#2381 fix all LFRic tests
arporter Dec 17, 2025
650f584
#2381 use template for max/min reduction
arporter Dec 17, 2025
8aeffda
#2381 fix linting
arporter Dec 17, 2025
e5e9af5
#2381 fix cov of psyGen
arporter Dec 17, 2025
bd0c70a
#2381 rationalise class structure
arporter Dec 17, 2025
0eea98f
#2381 update docstrings and copyright
arporter Dec 17, 2025
19e1982
#2381 mv mpi-obj init into LFRicProxies
arporter Dec 17, 2025
e672b5a
#2381 WIP moving mpi-related setup to LFRicProxies class [skip ci]
arporter Dec 17, 2025
dfde2c6
#2381 fix lint [skip ci]
arporter Dec 17, 2025
a7cbe5e
#2381 begin adding min/max builtins [skip ci]
arporter Dec 18, 2025
3c6187e
#2381 WIP on new builtins
arporter Dec 18, 2025
3af3abb
#2381 revert reduction-related changes
arporter Dec 18, 2025
2f49897
#3263 fix remaining tests
arporter Dec 18, 2025
e3dc204
#3263 document new builtins
arporter Dec 18, 2025
60db4b9
#3263 improve testing
arporter Dec 18, 2025
abd054f
#3263 rm the min_max version and ensure code-gen is rejected when DM …
arporter Dec 18, 2025
5535e8b
Merge branch 'master' into 2381_min_max_builtins
arporter Jan 7, 2026
7a0d563
#3263 fix pesky tabs
arporter Jan 7, 2026
6e27f34
Merge branch 'master' into 2381_min_max_builtins
arporter Jan 8, 2026
75fc7e5
#2381 replace gh_sum/min/max with gh_reduction
arporter Jan 8, 2026
8622ada
#2381 fix lfric_test suite
arporter Jan 9, 2026
ae157bc
#2381 fix remaining LFRic tests
arporter Jan 9, 2026
88de371
#2381 linting
arporter Jan 9, 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 config/psyclone.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ FORTRAN_STANDARD = f2008
# ==================================
[lfric]
access_mapping = gh_read: read, gh_write: write, gh_readwrite: readwrite,
gh_inc: inc, gh_readinc: readinc, gh_sum: sum
gh_inc: inc, gh_readinc: readinc, gh_reduction: reduction

# Specify whether we compute annexed dofs when a kernel is written so
# that it iterates over dofs. This is currently only the case for
Expand Down
43 changes: 36 additions & 7 deletions doc/user_guide/lfric.rst
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,7 @@ intents there are always ``intent(in)``.
The Fortran intent of :ref:`scalars <lfric-scalar>` is still defined
by their :ref:`access metadata <lfric-kernel-valid-access>` as they are
actual data. This means ``intent(in)`` for ``GH_READ`` and ``intent(out)``
for ``GH_SUM`` (more details in :ref:`meta_args <lfric-api-meta-args>`
for ``GH_REDUCTION`` (more details in :ref:`meta_args <lfric-api-meta-args>`
section below).

The intent of other data structures is mandated by the relevant
Expand Down Expand Up @@ -1143,7 +1143,7 @@ The third component of argument metadata describes how the Kernel
makes use of the data being passed into it (the way it is accessed
within a Kernel). This information is mandatory. There are currently 6
possible values of this metadata ``GH_READ``, ``GH_WRITE``,
``GH_READWRITE``, ``GH_INC``, ``GH_READINC`` and ``GH_SUM``. However,
``GH_READWRITE``, ``GH_INC``, ``GH_READINC`` and ``GH_REDUCTION``. However,
not all combinations of metadata entries are valid and PSyclone will
raise an exception if an invalid combination is specified. Valid
combinations are specified later in this section (see
Expand Down Expand Up @@ -1174,9 +1174,9 @@ combinations are specified later in this section (see
subsequently incremented. Therefore this is equivalent to a
``GH_READ`` followed by a ``GH_INC``.

* ``GH_SUM`` is an example of a reduction and is the only reduction
currently supported in PSyclone. This metadata indicates that values
are summed over calls to Kernel code.
* ``GH_REDUCTION`` indicates a reduction. Only Built-ins may perform
reductions. The type of reduction (sum, maximum value, minimum value)
is a property of the particular Built-in.

For example::

Expand All @@ -1187,7 +1187,7 @@ For example::
arg_type(GH_FIELD, GH_INTEGER, GH_INC, ... ), &
arg_type(GH_FIELD, GH_REAL, GH_READINC, ... ), &
arg_type(GH_SCALAR_ARRAY, GH_LOGICAL, GH_READ, ... ), &
arg_type(GH_SCALAR, GH_REAL, GH_SUM) &
arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION) &
/)

.. warning:: It is important that ``GH_INC`` is not incorrectly used
Expand Down Expand Up @@ -2797,7 +2797,8 @@ are listed in the table below.
+===============+=====================+================+====================+
| GH_SCALAR | GH_INTEGER | n/a | GH_READ |
+---------------+---------------------+----------------+--------------------+
| GH_SCALAR | GH_REAL | n/a | GH_READ, GH_SUM |
| GH_SCALAR | GH_REAL | n/a | GH_READ, |
| | | | GH_REDUCTION |
+---------------+---------------------+----------------+--------------------+
| GH_FIELD | GH_REAL, GH_INTEGER | ANY_SPACE_<n> | GH_READ, GH_WRITE, |
| | | | GH_READWRITE |
Expand Down Expand Up @@ -3455,6 +3456,34 @@ the same field (``X = min(a, X)``)::

field(:) = MIN(rscalar, field(:))

Global minimum and maximum field-element values
###############################################

Built-ins which scan through all elements of a field and return its
maximum or minimum value.

.. warning::
Support for these built-ins is not yet complete and therefore they
cannot currently be used. TODO #2381.

minval_X
^^^^^^^^

**minval_X** (*rscalar*, **field**)

Returns the minimum value held in the field *field*::

rscalar = MINVAL(field(:))

maxval_X
^^^^^^^^

**maxval_X** (*rscalar*, **field**)

Returns the maximum value held in the field *field*::

rscalar = MAXVAL(field(:))

Conversion of ``real`` field elements
#####################################

Expand Down
10 changes: 7 additions & 3 deletions src/psyclone/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
from configparser import (ConfigParser, MissingSectionHeaderError,
ParsingError)
from collections import namedtuple
import logging
import os
import re
import sys
Expand Down Expand Up @@ -811,11 +812,12 @@ class BaseConfig:
'''

def __init__(self, section):
logger = logging.getLogger(__name__)
# Set a default mapping, this way the test cases all work without
# having to specify those mappings.
self._access_mapping = {"read": "read", "write": "write",
"readwrite": "readwrite", "inc": "inc",
"sum": "sum"}
"reduction": "reduction", "sum": "reduction"}
# Get the mapping if one exists and convert it into a
# dictionary. The input is in the format: key1:value1,
# key2=value2, ...
Expand All @@ -837,9 +839,11 @@ def __init__(self, section):
AccessType.from_string(access_type)
except ValueError as err:
# Raised by from_string()
raise ConfigurationError(
# raise ConfigurationError(
logger.warn(
f"Unknown access type '{access_type}' found for key "
f"'{api_access_name}'") from err
f"'{api_access_name}'")
# from err

# Now create the reverse lookup (for better error messages):
self._reverse_access_mapping = {v: k for k, v in
Expand Down
10 changes: 5 additions & 5 deletions src/psyclone/core/access_type.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@

'''This module implements the AccessType used throughout PSyclone.'''

from __future__ import annotations
from enum import Enum
from psyclone.configuration import Config

Expand All @@ -57,8 +58,8 @@ class AccessType(Enum):
#: Read before incrementing. Requires that the outermost halo be clean (see
#: the LFRic API section of the User Guide).
READINC = 5
#: Is the output of a SUM reduction.
SUM = 6
#: Is the output of a (min/max/sum) reduction.
REDUCTION = 6
#: This is used internally to indicate unknown access type of
#: a variable, e.g. when a variable is passed to a subroutine
#: and the access type of this variable in the subroutine
Expand Down Expand Up @@ -132,12 +133,11 @@ def all_read_accesses():
AccessType.READINC]

@staticmethod
def get_valid_reduction_modes():
def get_valid_reduction_modes() -> list[AccessType]:
'''
:returns: A list of valid reduction access modes.
:rtype: List of py:class:`psyclone.core.access_type.AccessType`.
'''
return [AccessType.SUM]
return [AccessType.REDUCTION]

@staticmethod
def get_valid_reduction_names():
Expand Down
118 changes: 111 additions & 7 deletions src/psyclone/domain/lfric/lfric_builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@

# pylint: disable=too-many-lines
import abc
from typing import Optional

from psyclone.configuration import Config
from psyclone.core import AccessType, Signature, VariablesAccessMap
Expand Down Expand Up @@ -168,6 +169,9 @@ class LFRicBuiltIn(BuiltIn, metaclass=abc.ABCMeta):
'''
_case_name = None
_datatype = None
#: The type of reduction performed by this kernel. By default this is
## None indicating no reduction.
_reduction_type = None

def __init__(self):
# Builtins do not accept quadrature
Expand Down Expand Up @@ -311,6 +315,10 @@ def _validate(self):
write_count = 0 # Only one argument must be written to
field_count = 0 # We must have one or more fields as arguments
spaces = set() # All field arguments must be on the same space
# Built-ins update fields DoF by DoF and therefore can have
# WRITE/READWRITE access
write_access_modes = AccessType.get_valid_reduction_modes() + [
AccessType.WRITE, AccessType.READWRITE]
# Field data types must be the same except for the conversion built-ins
data_types = set()
for arg in self.arg_descriptors:
Expand All @@ -328,10 +336,8 @@ def _validate(self):
f"must have one of {const.VALID_BUILTIN_DATA_TYPES} as "
f"a data type but kernel '{self.name}' has an argument "
f"of data type '{arg.data_type}'.")
# Built-ins update fields DoF by DoF and therefore can have
# WRITE/READWRITE access
if arg.access in [AccessType.WRITE, AccessType.SUM,
AccessType.READWRITE]:
# Check for write accesses
if arg.access in write_access_modes:
write_count += 1
if arg.argument_type in const.VALID_FIELD_NAMES:
field_count += 1
Expand Down Expand Up @@ -450,6 +456,14 @@ def fs_descriptors(self):
'''
return self._fs_descriptors

@property
def reduction_type(self) -> Optional[str]:
'''
:returns: the type of reduction operation performed by this Built-in
or None if it does not perform a reduction.
'''
return self._reduction_type

def get_dof_loop_index_symbol(self):
'''
Finds or creates the symbol representing the index in any loops
Expand Down Expand Up @@ -2350,6 +2364,7 @@ class LFRicXInnerproductYKern(LFRicBuiltIn):
'''
_case_name = "X_innerproduct_Y"
_datatype = "real"
_reduction_type = "sum"

@classmethod
def metadata(cls):
Expand All @@ -2361,7 +2376,7 @@ def metadata(cls):
'''
gh_datatype = LFRicConstants().MAPPING_INTRINSIC_TYPES[cls._datatype]
return cls._builtin_metadata([
ScalarArgMetadata(gh_datatype, "gh_sum"),
ScalarArgMetadata(gh_datatype, "gh_reduction"),
FieldArgMetadata(gh_datatype, "gh_read", "any_space_1"),
FieldArgMetadata(gh_datatype, "gh_read", "any_space_1")])

Expand Down Expand Up @@ -2397,6 +2412,7 @@ class LFRicXInnerproductXKern(LFRicBuiltIn):
'''
_case_name = "X_innerproduct_X"
_datatype = "real"
_reduction_type = "sum"

@classmethod
def metadata(cls):
Expand All @@ -2408,7 +2424,7 @@ def metadata(cls):
'''
gh_datatype = LFRicConstants().MAPPING_INTRINSIC_TYPES[cls._datatype]
return cls._builtin_metadata([
ScalarArgMetadata(gh_datatype, "gh_sum"),
ScalarArgMetadata(gh_datatype, "gh_reduction"),
FieldArgMetadata(gh_datatype, "gh_read", "any_space_1")])

def lower_to_language_level(self) -> Node:
Expand Down Expand Up @@ -2447,6 +2463,7 @@ class LFRicSumXKern(LFRicBuiltIn):
'''
_case_name = "sum_X"
_datatype = "real"
_reduction_type = "sum"

@classmethod
def metadata(cls):
Expand All @@ -2458,7 +2475,7 @@ def metadata(cls):
'''
gh_datatype = LFRicConstants().MAPPING_INTRINSIC_TYPES[cls._datatype]
return cls._builtin_metadata([
ScalarArgMetadata(gh_datatype, "gh_sum"),
ScalarArgMetadata(gh_datatype, "gh_reduction"),
FieldArgMetadata(gh_datatype, "gh_read", "any_space_1")])

def __str__(self):
Expand Down Expand Up @@ -2743,6 +2760,90 @@ def lower_to_language_level(self) -> Node:
# Create assignment and replace node
return self._replace_with_assignment(lhs, rhs)

# ------------------------------------------------------------------- #
# ============ Minimum, maximum value of real field elements) ======= #
# ------------------------------------------------------------------- #


class LFRicMinvalXKern(LFRicBuiltIn):
'''
Computes the (global) minimum scalar value held in
the supplied field.
'''
_case_name = "minval_X"
_datatype = "real"
_reduction_type = "min"

@classmethod
def metadata(cls) -> LFRicKernelMetadata:
"""
:returns: kernel metadata describing this built-in.
"""
return cls._builtin_metadata([
ScalarArgMetadata("gh_real", "gh_reduction"),
FieldArgMetadata("gh_real", "gh_read", "any_space_1")])

def __str__(self):
return (f"Built-in: {self._case_name} (compute the global minimum "
f"value contained in a field)")

def lower_to_language_level(self) -> Node:
'''
Lowers this LFRic-specific built-in kernel to language-level PSyIR.
This BuiltIn node is replaced by an Assignment node.

:returns: the lowered version of this node.

'''
super().lower_to_language_level()
# Get indexed references for the field (proxy) argument.
arg_refs = self.get_indexed_field_argument_references()
# Get a reference for the kernel scalar reduction argument.
lhs = self._reduction_reference()
minval = IntrinsicCall.create(IntrinsicCall.Intrinsic.MIN,
[lhs.copy(), arg_refs[0]])
return self._replace_with_assignment(lhs, minval)


class LFRicMaxvalXKern(LFRicBuiltIn):
'''
Computes the (global) maximum scalar value held in
the supplied field.
'''
_case_name = "maxval_X"
_datatype = "real"
_reduction_type = "max"

@classmethod
def metadata(cls) -> LFRicKernelMetadata:
"""
:returns: kernel metadata describing this built-in.
"""
return cls._builtin_metadata([
ScalarArgMetadata("gh_real", "gh_reduction"),
FieldArgMetadata("gh_real", "gh_read", "any_space_1")])

def __str__(self):
return (f"Built-in: {self._case_name} (compute the global maximum "
f"value contained in a field)")

def lower_to_language_level(self) -> Node:
'''
Lowers this LFRic-specific built-in kernel to language-level PSyIR.
This BuiltIn node is replaced by an Assignment node.

:returns: the lowered version of this node.

'''
super().lower_to_language_level()
# Get indexed references for the field (proxy) argument.
arg_refs = self.get_indexed_field_argument_references()
# Get a reference for the kernel scalar reduction argument.
lhs = self._reduction_reference()
minval = IntrinsicCall.create(IntrinsicCall.Intrinsic.MAX,
[lhs.copy(), arg_refs[0]])
return self._replace_with_assignment(lhs, minval)

# ------------------------------------------------------------------- #
# ============== Converting real to integer field elements ========== #
# ------------------------------------------------------------------- #
Expand Down Expand Up @@ -3270,6 +3371,9 @@ def lower_to_language_level(self) -> Node:
# Minimum of a real scalar value and real field elements
"min_aX": LFRicMinAXKern,
"inc_min_aX": LFRicIncMinAXKern,
# Minimum and maximum values contained in a field
"minval_X": LFRicMinvalXKern,
"maxval_X": LFRicMaxvalXKern,
# Converting real to integer field elements
"real_to_int_X": LFRicRealToIntXKern,
# Converting real to real field elements
Expand Down
6 changes: 3 additions & 3 deletions src/psyclone/domain/lfric/lfric_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ def __init__(self) -> None:
# pylint: disable=too-many-instance-attributes

# Supported access types
# gh_sum for scalars is restricted to iterates_over == 'dof'
LFRicConstants.VALID_SCALAR_ACCESS_TYPES = ["gh_read", "gh_sum"]
# Reduction for scalars is restricted to iterates_over == 'dof'
LFRicConstants.VALID_SCALAR_ACCESS_TYPES = ["gh_read", "gh_reduction"]
LFRicConstants.VALID_ARRAY_ACCESS_TYPES = ["gh_read"]
LFRicConstants.VALID_FIELD_ACCESS_TYPES = [
"gh_read", "gh_write", "gh_readwrite", "gh_inc", "gh_readinc"]
Expand All @@ -125,7 +125,7 @@ def __init__(self) -> None:
"gh_read", "gh_write", "gh_readwrite", "gh_inc", "gh_readinc"]

LFRicConstants.WRITE_ACCESSES = [
"gh_write", "gh_readwrite", "gh_inc", "gh_readinc", "gh_sum"]
"gh_write", "gh_readwrite", "gh_inc", "gh_readinc", "gh_reduction"]

# Supported LFRic API stencil types and directions
LFRicConstants.VALID_STENCIL_TYPES = ["x1d", "y1d", "xory1d", "cross",
Expand Down
Loading
Loading