diff --git a/config/psyclone.cfg b/config/psyclone.cfg index 8b7355c864..6b3c0eed9f 100644 --- a/config/psyclone.cfg +++ b/config/psyclone.cfg @@ -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 diff --git a/doc/user_guide/lfric.rst b/doc/user_guide/lfric.rst index e449270675..deaa9e4a16 100644 --- a/doc/user_guide/lfric.rst +++ b/doc/user_guide/lfric.rst @@ -793,7 +793,7 @@ intents there are always ``intent(in)``. The Fortran intent of :ref:`scalars ` is still defined by their :ref:`access metadata ` as they are actual data. This means ``intent(in)`` for ``GH_READ`` and ``intent(out)`` -for ``GH_SUM`` (more details in :ref:`meta_args ` +for ``GH_REDUCTION`` (more details in :ref:`meta_args ` section below). The intent of other data structures is mandated by the relevant @@ -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 @@ -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:: @@ -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 @@ -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_ | GH_READ, GH_WRITE, | | | | | GH_READWRITE | @@ -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 ##################################### diff --git a/src/psyclone/configuration.py b/src/psyclone/configuration.py index 0647a84dbd..b95da64099 100644 --- a/src/psyclone/configuration.py +++ b/src/psyclone/configuration.py @@ -46,6 +46,7 @@ from configparser import (ConfigParser, MissingSectionHeaderError, ParsingError) from collections import namedtuple +import logging import os import re import sys @@ -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, ... @@ -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 diff --git a/src/psyclone/core/access_type.py b/src/psyclone/core/access_type.py index 4839523e60..1220d52a94 100644 --- a/src/psyclone/core/access_type.py +++ b/src/psyclone/core/access_type.py @@ -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 @@ -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 @@ -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(): diff --git a/src/psyclone/domain/lfric/lfric_builtins.py b/src/psyclone/domain/lfric/lfric_builtins.py index e194c6d93c..013e8f496d 100644 --- a/src/psyclone/domain/lfric/lfric_builtins.py +++ b/src/psyclone/domain/lfric/lfric_builtins.py @@ -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 @@ -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 @@ -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: @@ -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 @@ -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 @@ -2350,6 +2364,7 @@ class LFRicXInnerproductYKern(LFRicBuiltIn): ''' _case_name = "X_innerproduct_Y" _datatype = "real" + _reduction_type = "sum" @classmethod def metadata(cls): @@ -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")]) @@ -2397,6 +2412,7 @@ class LFRicXInnerproductXKern(LFRicBuiltIn): ''' _case_name = "X_innerproduct_X" _datatype = "real" + _reduction_type = "sum" @classmethod def metadata(cls): @@ -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: @@ -2447,6 +2463,7 @@ class LFRicSumXKern(LFRicBuiltIn): ''' _case_name = "sum_X" _datatype = "real" + _reduction_type = "sum" @classmethod def metadata(cls): @@ -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): @@ -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 ========== # # ------------------------------------------------------------------- # @@ -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 diff --git a/src/psyclone/domain/lfric/lfric_constants.py b/src/psyclone/domain/lfric/lfric_constants.py index ccdb6a636a..ef24c6dc34 100644 --- a/src/psyclone/domain/lfric/lfric_constants.py +++ b/src/psyclone/domain/lfric/lfric_constants.py @@ -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"] @@ -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", diff --git a/src/psyclone/domain/lfric/lfric_invoke.py b/src/psyclone/domain/lfric/lfric_invoke.py index f5a73639fc..3876fe87a6 100644 --- a/src/psyclone/domain/lfric/lfric_invoke.py +++ b/src/psyclone/domain/lfric/lfric_invoke.py @@ -40,8 +40,8 @@ base class from psyGen.py. ''' from psyclone.configuration import Config -from psyclone.core import AccessType -from psyclone.domain.lfric.lfric_constants import LFRicConstants +from psyclone.domain.lfric.lfric_builtins import LFRicBuiltIn +from psyclone.domain.lfric.lfric_loop import LFRicLoop from psyclone.errors import GenerationError, FieldNotFoundError from psyclone.psyGen import Invoke from psyclone.psyir.nodes import Assignment, Reference, Call, Literal @@ -65,7 +65,8 @@ class LFRicInvoke(Invoke): :raises GenerationError: if integer reductions are required in the PSy-layer. - + :raises GenerationError: if a global reduction operation other than sum + is required - TODO #2381. ''' # pylint: disable=too-many-instance-attributes # pylint: disable=too-many-locals @@ -73,7 +74,6 @@ def __init__(self, alg_invocation, idx, invokes): # Import here to avoid circular dependency # pylint: disable=import-outside-toplevel from psyclone.domain.lfric import LFRicInvokeSchedule - const = LFRicConstants() Invoke.__init__(self, alg_invocation, idx, LFRicInvokeSchedule, invokes) @@ -172,21 +172,25 @@ def __init__(self, alg_invocation, idx, invokes): # Lastly, add in halo exchange calls and global sums if # required. We only need to add halo exchange calls for fields # since operators are assembled in place and scalars don't - # have halos. We only need to add global sum calls for scalars - # which have a 'gh_sum' access. + # have halos. We only need to add global reduction calls for scalars + # which have a 'gh_reduction' access. if Config.get().distributed_memory: - # halo exchange calls - const = LFRicConstants() + # Halo exchange calls for loop in self.schedule.loops(): loop.create_halo_exchanges() - # global sum calls - for loop in self.schedule.loops(): - for scalar in loop.args_filter( - arg_types=const.VALID_SCALAR_NAMES, - arg_accesses=AccessType.get_valid_reduction_modes(), - unique=True): - global_sum = LFRicGlobalSum(scalar, parent=loop.parent) - loop.parent.children.insert(loop.position+1, global_sum) + # Global reductions + for kern in self.schedule.walk(LFRicBuiltIn): + if not kern.is_reduction: + continue + loop = kern.ancestor(LFRicLoop) + if kern.reduction_type == "sum": + global_red = LFRicGlobalSum(kern.reduction_arg, + parent=loop.parent) + else: + raise GenerationError( + "TODO #2381 - currently only global *sum* " + "reductions are supported.") + loop.parent.children.insert(loop.position+1, global_red) # Add the halo depth(s) for any kernel(s) that operate in the halos self._alg_unique_halo_depth_args = [] diff --git a/src/psyclone/parse/lfric_builtins_mod.f90 b/src/psyclone/parse/lfric_builtins_mod.f90 index 46bdaa1f06..f94519ccc5 100644 --- a/src/psyclone/parse/lfric_builtins_mod.f90 +++ b/src/psyclone/parse/lfric_builtins_mod.f90 @@ -560,7 +560,7 @@ module lfric_builtins_mod type, public, extends(kernel_type) :: X_innerproduct_Y private type(arg_type) :: meta_args(3) = (/ & - arg_type(GH_SCALAR, GH_REAL, GH_SUM ), & + arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION ), & arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1), & arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1) & /) @@ -573,7 +573,7 @@ module lfric_builtins_mod type, public, extends(kernel_type) :: X_innerproduct_X private type(arg_type) :: meta_args(2) = (/ & - arg_type(GH_SCALAR, GH_REAL, GH_SUM ), & + arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION ), & arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1) & /) integer :: operates_on = DOF @@ -589,7 +589,7 @@ module lfric_builtins_mod type, public, extends(kernel_type) :: sum_X private type(arg_type) :: meta_args(2) = (/ & - arg_type(GH_SCALAR, GH_REAL, GH_SUM ), & + arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION ), & arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1) & /) integer :: operates_on = DOF @@ -672,6 +672,32 @@ module lfric_builtins_mod procedure, nopass :: inc_min_aX_code end type inc_min_aX +! ------------------------------------------------------------------- ! +! ============ Minimum, maximum value of real field elements) ======= ! +! ------------------------------------------------------------------- ! + + type, public, extends(kernel_type) :: minval_X + private + type(arg_type) :: meta_args(2) = (/ & + arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION ), & + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1) & + /) + integer :: operates_on = DOF + contains + procedure, nopass :: minval_X_code + end type minval_X + + type, public, extends(kernel_type) :: maxval_X + private + type(arg_type) :: meta_args(2) = (/ & + arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION ), & + arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1) & + /) + integer :: operates_on = DOF + contains + procedure, nopass :: maxval_X_code + end type maxval_X + ! ------------------------------------------------------------------- ! ! ============== Converting real to integer field elements ========== ! ! ------------------------------------------------------------------- ! @@ -1169,6 +1195,13 @@ end subroutine min_aX_code subroutine inc_min_aX_code() end subroutine inc_min_aX_code + ! Minimum and maximum values contained within a field + subroutine minval_X_code() + end subroutine minval_X_code + + subroutine maxval_X_code() + end subroutine maxval_X_code + ! Converting real to integer field elements subroutine real_to_int_X_code() end subroutine real_to_int_X_code diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index aca54d3b71..74eb00d026 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -74,9 +74,6 @@ # may have FORTRAN_INTENT_NAMES = ["inout", "out", "in"] -# Mapping of access type to operator. -REDUCTION_OPERATOR_MAPPING = {AccessType.SUM: "+"} - def object_index(alist, item): ''' @@ -588,7 +585,7 @@ def unique_declns_by_intent(self, argument_types, intrinsic_type=None): for arg in self.unique_declarations(argument_types, intrinsic_type=intrinsic_type): first_arg = self.first_access(arg.declaration_name) - if first_arg.access in [AccessType.WRITE, AccessType.SUM]: + if first_arg.access in [AccessType.WRITE, AccessType.REDUCTION]: # If the first access is a write then the intent is # out irrespective of any other accesses. Note, # sum_args behave as if they are write_args from the @@ -1041,15 +1038,6 @@ def reduction_sum_loop(self, ''' tag = f"{self.name}:{self._reduction_arg.name}:local" local_symbol = table.lookup_with_tag(tag) - reduction_access = self._reduction_arg.access - if reduction_access not in REDUCTION_OPERATOR_MAPPING: - api_strings = [access.api_specific_name() - for access in REDUCTION_OPERATOR_MAPPING] - raise GenerationError( - f"Unsupported reduction access " - f"'{reduction_access.api_specific_name()}' found in " - f"LFRicBuiltIn:reduction_sum_loop(). Expected one of " - f"{api_strings}.") symtab = table thread_idx = symtab.lookup_with_tag("omp_thread_index") nthreads = symtab.lookup_with_tag("omp_num_threads") diff --git a/src/psyclone/psyir/tools/reduction_inference.py b/src/psyclone/psyir/tools/reduction_inference.py index 46d2e5f69b..8d8e52d28e 100644 --- a/src/psyclone/psyir/tools/reduction_inference.py +++ b/src/psyclone/psyir/tools/reduction_inference.py @@ -62,17 +62,18 @@ def _get_reduction_operator(self, node: Node) -> \ Union[BinaryOperation.Operator, IntrinsicCall.Intrinsic]: ''' :param node: the node to match against. + :returns: the reduction operator at the root of the given - DataNode or None if there isn't one. + DataNode or None if there isn't one. ''' if isinstance(node, BinaryOperation): - for op in self.red_ops: - if node.operator == op: - return node.operator + if node.operator in self.red_ops: + return node.operator + if isinstance(node, IntrinsicCall): - for op in self.red_ops: - if node.intrinsic == op: - return node.intrinsic + if node.intrinsic in self.red_ops: + return node.intrinsic + return None @staticmethod diff --git a/src/psyclone/tests/conftest.py b/src/psyclone/tests/conftest.py index 079d883258..7e24a1d5b5 100644 --- a/src/psyclone/tests/conftest.py +++ b/src/psyclone/tests/conftest.py @@ -102,6 +102,9 @@ def setup_psyclone_config(): independent of a potential psyclone config file installed by the user. ''' + # Ensure any Config object that has already been loaded is wiped. + Config._instance = None + config_file = Config.get_repository_config_file() # In case that PSyclone is installed and tested (e.g. GitHub Actions), diff --git a/src/psyclone/tests/core/access_type_test.py b/src/psyclone/tests/core/access_type_test.py index f1cb1ca2a6..afcde623ae 100644 --- a/src/psyclone/tests/core/access_type_test.py +++ b/src/psyclone/tests/core/access_type_test.py @@ -72,14 +72,18 @@ def test_api_specific_name(): assert AccessType.INQUIRY.api_specific_name() == "inquiry" assert AccessType.CONSTANT.api_specific_name() == "constant" assert AccessType.UNKNOWN.api_specific_name() == "unknown" - assert AccessType.get_valid_reduction_modes() == [AccessType.SUM] - assert AccessType.get_valid_reduction_names() == ["gh_sum"] + assert (set(AccessType.get_valid_reduction_modes()) == + set([AccessType.SUM, AccessType.MIN, AccessType.MAX])) + assert (set(AccessType.get_valid_reduction_names()) == + set(["gh_sum", "gh_min", "gh_max"])) # Use set to make this independent of the order: assert set(AccessType.all_write_accesses()) == set([AccessType.WRITE, AccessType.READWRITE, AccessType.INC, AccessType.READINC, - AccessType.SUM]) + AccessType.SUM, + AccessType.MAX, + AccessType.MIN]) assert set(AccessType.all_read_accesses()) == set([AccessType.READ, AccessType.READWRITE, AccessType.READINC, @@ -112,7 +116,7 @@ def test_all_write_accesses(): all_write_accesses = AccessType.all_write_accesses() assert isinstance(all_write_accesses, list) - assert len(all_write_accesses) == 5 + assert len(all_write_accesses) == 7 assert (len(all_write_accesses) == len(set(all_write_accesses))) assert all(isinstance(write_access, AccessType) diff --git a/src/psyclone/tests/domain/lfric/kernel/evaluator_targets_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/evaluator_targets_metadata_test.py index 19ace025fa..35f6349699 100644 --- a/src/psyclone/tests/domain/lfric/kernel/evaluator_targets_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/evaluator_targets_metadata_test.py @@ -40,6 +40,7 @@ from fparser.two import Fortran2003 +from psyclone.domain.lfric import LFRicConstants from psyclone.domain.lfric.kernel import EvaluatorTargetsMetadata @@ -145,7 +146,7 @@ def test_setter_errors(): with pytest.raises(ValueError) as info: metadata.evaluator_targets = ["invalid"] - assert ("The 'evaluator_targets' metadata should be a recognised value " - "(one of ['w3', 'wtheta', 'w2v', 'w2vtrace', 'w2broken', 'w0', " - "'w1', 'w2', 'w2trace', 'w2h', 'w2htrace', 'any_w2', 'wchi']) " - "but found 'invalid'." in str(info.value)) + const = LFRicConstants() + assert (f"The 'evaluator_targets' metadata should be a recognised value " + f"(one of {const.VALID_FUNCTION_SPACES}) but found " + f"'invalid'." in str(info.value)) 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 c23b04966d..6783c5fb93 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 @@ -40,6 +40,7 @@ from fparser.two import Fortran2003 +from psyclone.domain.lfric import LFRicConstants from psyclone.domain.lfric.kernel import FieldArgMetadata @@ -175,9 +176,10 @@ def test_check_access(): FieldArgMetadata.check_access("GH_READ") with pytest.raises(ValueError) as info: FieldArgMetadata.check_access("invalid") - assert ("The 'access descriptor' metadata should be a recognised value " - "(one of ['gh_read', 'gh_write', 'gh_readwrite', 'gh_inc', " - "'gh_readinc']) but found 'invalid'." in str(info.value)) + const = LFRicConstants() + assert (f"The 'access descriptor' metadata should be a recognised value " + f"(one of {const.VALID_FIELD_ACCESS_TYPES}) but found 'invalid'." + in str(info.value)) def test_function_space_setter_getter(): @@ -188,18 +190,10 @@ def test_function_space_setter_getter(): field_arg = FieldArgMetadata("GH_REAL", "GH_READ", "W0") with pytest.raises(ValueError) as info: field_arg.function_space = "invalid" - assert ("The 'function space' metadata should be a recognised value (one " - "of ['w3', 'wtheta', 'w2v', 'w2vtrace', 'w2broken', 'w0', 'w1', " - "'w2', 'w2trace', 'w2h', 'w2htrace', 'any_w2', 'wchi', " - "'any_space_1', 'any_space_2', 'any_space_3', 'any_space_4', " - "'any_space_5', 'any_space_6', 'any_space_7', 'any_space_8', " - "'any_space_9', 'any_space_10', 'any_discontinuous_space_1', " - "'any_discontinuous_space_2', 'any_discontinuous_space_3', " - "'any_discontinuous_space_4', 'any_discontinuous_space_5', " - "'any_discontinuous_space_6', 'any_discontinuous_space_7', " - "'any_discontinuous_space_8', 'any_discontinuous_space_9', " - "'any_discontinuous_space_10']) but found 'invalid'." - in str(info.value)) + const = LFRicConstants() + assert (f"The 'function space' metadata should be a recognised value (one " + f"of {const.VALID_FUNCTION_SPACE_NAMES}) but found " + f"'invalid'." in str(info.value)) field_arg.function_space = "w3" assert field_arg.function_space == "w3" field_arg.function_space = "W3" @@ -215,9 +209,10 @@ def test_stencil_getter_setter(): field_arg = FieldArgMetadata("GH_REAL", "GH_READ", "W0") with pytest.raises(ValueError) as info: field_arg.stencil = "invalid" - assert ("The 'stencil' metadata should be a recognised value (one of " - "['x1d', 'y1d', 'xory1d', 'cross', 'region', 'cross2d']) but " - "found 'invalid'." in str(info.value)) + const = LFRicConstants() + assert (f"The 'stencil' metadata should be a recognised value (one of " + f"{const.VALID_STENCIL_TYPES}) but " + f"found 'invalid'." in str(info.value)) field_arg.stencil = "x1d" assert field_arg.stencil == "x1d" field_arg.stencil = "X1D" 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 cada154869..32aad5f2f5 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 @@ -40,6 +40,7 @@ from fparser.two import Fortran2003 +from psyclone.domain.lfric import LFRicConstants from psyclone.domain.lfric.kernel import FieldVectorArgMetadata @@ -96,9 +97,10 @@ def test_init_invalid_stencil(): with pytest.raises(ValueError) as info: _ = FieldVectorArgMetadata( "GH_REAL", "GH_READ", "W0", "2", stencil="invalid") - assert ("The 'stencil' metadata should be a recognised value (one of " - "['x1d', 'y1d', 'xory1d', 'cross', 'region', 'cross2d']) but " - "found 'invalid'." in str(info.value)) + const = LFRicConstants() + assert (f"The 'stencil' metadata should be a recognised value (one of " + f"{const.VALID_STENCIL_TYPES}) but " + f"found 'invalid'." in str(info.value)) def test_get_metadata(): diff --git a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py index 010c5dc7b9..1d1860012d 100644 --- a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py @@ -41,6 +41,7 @@ from fparser.common.readfortran import FortranStringReader from fparser.two import Fortran2003 +from psyclone.domain.lfric import LFRicConstants from psyclone.domain.lfric.kernel import ( ColumnwiseOperatorArgMetadata, EvaluatorTargetsMetadata, FieldArgMetadata, FieldVectorArgMetadata, InterGridArgMetadata, InterGridVectorArgMetadata, @@ -782,8 +783,9 @@ def test_validate_cma_matrix_kernel(): # check that a scalar must be read only. with pytest.raises(ValueError) as info: ScalarArgMetadata("gh_real", "gh_write") - assert ("The 'access descriptor' metadata should be a recognised value " - "(one of ['gh_read', 'gh_sum']) but found 'gh_write'." + const = LFRicConstants() + assert (f"The 'access descriptor' metadata should be a recognised value " + f"(one of {const.VALID_SCALAR_ACCESS_TYPES}) but found 'gh_write'." in str(info.value)) # OK. diff --git a/src/psyclone/tests/domain/lfric/kernel/scalar_arg_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/scalar_arg_metadata_test.py index b1697dce2a..ee37fa07e8 100644 --- a/src/psyclone/tests/domain/lfric/kernel/scalar_arg_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/scalar_arg_metadata_test.py @@ -40,6 +40,7 @@ from fparser.two import Fortran2003 +from psyclone.domain.lfric import LFRicConstants from psyclone.domain.lfric.kernel import ScalarArgMetadata @@ -126,9 +127,10 @@ def test_check_datatype(): def test_check_access(): '''Test the check_access method works as expected.''' ScalarArgMetadata.check_access("GH_READ") - ScalarArgMetadata.check_access("gh_sum") + ScalarArgMetadata.check_access("gh_reduction") with pytest.raises(ValueError) as info: ScalarArgMetadata.check_access("invalid") - assert ("The 'access descriptor' metadata should be a recognised value " - "(one of ['gh_read', 'gh_sum']) but found 'invalid'." - in str(info.value)) + const = LFRicConstants() + assert (f"The 'access descriptor' metadata should be a recognised value " + f"(one of {const.VALID_SCALAR_ACCESS_TYPES}) but found " + f"'invalid'." in str(info.value)) diff --git a/src/psyclone/tests/domain/lfric/kernel/scalar_array_arg_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/scalar_array_arg_metadata_test.py index 6bd3a841d8..1aac4102e3 100644 --- a/src/psyclone/tests/domain/lfric/kernel/scalar_array_arg_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/scalar_array_arg_metadata_test.py @@ -41,6 +41,7 @@ from fparser.two import Fortran2003 +from psyclone.domain.lfric import LFRicConstants from psyclone.domain.lfric.kernel import ScalarArrayArgMetadata @@ -98,9 +99,10 @@ def test_check_access(): ScalarArrayArgMetadata.check_access("GH_READ") with pytest.raises(ValueError) as info: ScalarArrayArgMetadata.check_access("invalid") - assert ("The 'access descriptor' metadata should be a recognised value " - "(one of ['gh_read', 'gh_sum']) but found 'invalid'." - in str(info.value)) + const = LFRicConstants() + assert (f"The 'access descriptor' metadata should be a recognised value " + f"(one of {const.VALID_SCALAR_ACCESS_TYPES}) but found " + f"'invalid'." in str(info.value)) def test_get_array_ndims(): diff --git a/src/psyclone/tests/domain/lfric/lfric_array_mdata_test.py b/src/psyclone/tests/domain/lfric/lfric_array_mdata_test.py index 32fbd60182..33bb4b01d6 100644 --- a/src/psyclone/tests/domain/lfric/lfric_array_mdata_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_array_mdata_test.py @@ -220,17 +220,17 @@ def test_ad_array_type_no_readwrite(): def test_ad_array_type_no_sum(): ''' Tests that an error is raised when the argument descriptor - metadata for an array specifies 'GH_SUM' access (reduction). ''' + metadata for an array specifies 'GH_REDUCTION' access (reduction). ''' fparser.logging.disable(fparser.logging.CRITICAL) code = ARRAY_CODE.replace( "arg_type(gh_scalar_array, gh_real, gh_read, 1)", - "arg_type(gh_scalar_array, gh_real, gh_sum, 1)", 1) + "arg_type(gh_scalar_array, gh_real, gh_reduction, 1)", 1) ast = fpapi.parse(code, ignore_comments=False) name = "testkern_array_type" with pytest.raises(ParseError) as excinfo: _ = LFRicKernMetadata(ast, name=name) assert ("ScalarArray arguments must have read-only ('gh_read') " - "access but found 'gh_sum'" in str(excinfo.value)) + "access but found 'gh_reduction'" in str(excinfo.value)) def test_no_vector_array(): diff --git a/src/psyclone/tests/domain/lfric/lfric_builtins_test.py b/src/psyclone/tests/domain/lfric/lfric_builtins_test.py index a04a46a4da..275bf18766 100644 --- a/src/psyclone/tests/domain/lfric/lfric_builtins_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_builtins_test.py @@ -77,9 +77,9 @@ API = "lfric" -def builtin_from_file(filename): +def builtin_from_file(filename: str): ''' - :param str filename: the name of the file to check for the builtin. + :param filename: the name of the file to check for the builtin. :returns: the first builtin in the first invoke. ''' _, invoke_info = parse(os.path.join(BASE_PATH, filename), api=API) @@ -287,7 +287,7 @@ def test_builtin_write_and_readwrite(): def test_builtin_sum_and_readwrite(): '''Check that we raise an appropriate error if we encounter a built-in - that updates more than one argument where one is 'gh_sum' and one + that updates more than one argument where one is 'gh_reduction' and one is 'gh_readwrite'. ''' @@ -1853,7 +1853,7 @@ def test_sum_x(fortran_writer): assert isinstance(metadata, LFRicKernelMetadata) assert len(metadata.meta_args) == 2 assert isinstance(metadata.meta_args[0], ScalarArgMetadata) - assert metadata.meta_args[0].access == "gh_sum" + assert metadata.meta_args[0].access == "gh_reduction" assert metadata.meta_args[1].access == "gh_read" assert metadata.meta_args[1].function_space == "any_space_1" @@ -1872,7 +1872,7 @@ def test_x_innerproduct_x(fortran_writer): assert isinstance(metadata, LFRicKernelMetadata) assert len(metadata.meta_args) == 2 assert isinstance(metadata.meta_args[0], ScalarArgMetadata) - assert metadata.meta_args[0].access == "gh_sum" + assert metadata.meta_args[0].access == "gh_reduction" assert metadata.meta_args[1].access == "gh_read" assert metadata.meta_args[1].function_space == "any_space_1" @@ -1891,7 +1891,7 @@ def test_x_innerproduct_y(fortran_writer): assert isinstance(metadata, LFRicKernelMetadata) assert len(metadata.meta_args) == 3 assert isinstance(metadata.meta_args[0], ScalarArgMetadata) - assert metadata.meta_args[0].access == "gh_sum" + assert metadata.meta_args[0].access == "gh_reduction" assert metadata.meta_args[1].access == "gh_read" assert metadata.meta_args[1].function_space == "any_space_1" assert metadata.meta_args[2].access == "gh_read" @@ -1975,6 +1975,45 @@ def test_int_to_real_x_precision(tmpdir, kind_name): assert LFRicBuild(tmpdir).code_compiles(psy) +def test_minmaxval_x(fortran_writer): + ''' + Tests for the minval_x and maxval_x builtins. + ''' + metadata = lfric_builtins.LFRicMinvalXKern.metadata() + assert isinstance(metadata, LFRicKernelMetadata) + assert len(metadata.meta_args) == 2 + assert metadata.meta_args[0].access == "gh_reduction" + assert metadata.meta_args[1].access == "gh_read" + assert metadata.meta_args[1].function_space == "any_space_1" + metadata = lfric_builtins.LFRicMaxvalXKern.metadata() + assert isinstance(metadata, LFRicKernelMetadata) + assert len(metadata.meta_args) == 2 + assert metadata.meta_args[0].access == "gh_reduction" + assert metadata.meta_args[1].access == "gh_read" + assert metadata.meta_args[1].function_space == "any_space_1" + + _, invoke = get_invoke("15.10.9_min_max_X_builtin.f90", api=API, idx=0, + dist_mem=False) + kerns = invoke.schedule.kernels() + assert str(kerns[0]) == ("Built-in: minval_X (compute the global minimum " + "value contained in a field)") + code = fortran_writer(kerns[0]) + assert "amin = MIN(amin, f1_data(df))" in code, code + + assert str(kerns[1]) == ("Built-in: maxval_X (compute the global maximum " + "value contained in a field)") + code = fortran_writer(kerns[1]) + assert "amax = MAX(amax, f1_data(df))" in code, code + + # Currently psy-layer generation with DM enabled won't work because we only + # have support for global sums. TODO #2381. + with pytest.raises(GenerationError) as err: + _ = get_invoke("15.10.9_min_max_X_builtin.f90", api=API, idx=0, + dist_mem=True) + assert ("TODO #2381 - currently only global *sum* reductions are supported" + in str(err.value)) + + def test_real_to_int_x(fortran_writer): ''' Test the metadata, str and lower_to_language_level builtin methods. ''' metadata = lfric_builtins.LFRicRealToIntXKern.metadata() @@ -2121,7 +2160,7 @@ def test_scalar_int_builtin_error(monkeypatch): _, _ = parse(os.path.join(BASE_PATH, "16.2_integer_scalar_sum.f90"), api=API) - assert ("In the LFRic API a reduction access 'gh_sum' is only valid " + assert ("In the LFRic API a reduction access 'gh_reduction' is only valid " "with a real scalar argument, but a scalar argument with " "'gh_integer' data type was found" in str(excinfo.value)) diff --git a/src/psyclone/tests/domain/lfric/lfric_config_test.py b/src/psyclone/tests/domain/lfric/lfric_config_test.py index 9d08853868..d5ced1390d 100644 --- a/src/psyclone/tests/domain/lfric/lfric_config_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_config_test.py @@ -57,7 +57,7 @@ REPROD_PAD_SIZE = 8 [lfric] access_mapping = gh_read: read, gh_write: write, gh_readwrite: readwrite, - gh_inc: inc, gh_sum: sum + gh_inc: inc, gh_reduction: reduction COMPUTE_ANNEXED_DOFS = false supported_fortran_datatypes = real, integer, logical default_kind = real: r_def, integer: i_def, logical: l_def @@ -308,7 +308,8 @@ def test_access_mapping(): assert (api_config.get_access_mapping()["gh_readwrite"] == AccessType.READWRITE) assert api_config.get_access_mapping()["gh_inc"] == AccessType.INC - assert api_config.get_access_mapping()["gh_sum"] == AccessType.SUM + assert (api_config.get_access_mapping()["gh_reduction"] == + AccessType.REDUCTION) def test_compute_annexed_dofs(): 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 addd67eda6..7f318e09d9 100644 --- a/src/psyclone/tests/domain/lfric/lfric_field_mdata_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_field_mdata_test.py @@ -141,12 +141,12 @@ def test_ad_field_invalid_data_type(): "gh_read, w3)'." in str(excinfo.value)) -def test_field_gh_sum_invalid(): +def test_field_gh_reduction_invalid(): ''' Tests that an error is raised when a field is specified with - access type 'gh_sum'. ''' + access type 'gh_reduction'. ''' code = FIELD_CODE.replace( "arg_type(gh_field, gh_real, gh_read, w2)", - "arg_type(gh_field, gh_real, gh_sum, w2)", 1) + "arg_type(gh_field, gh_real, gh_reduction, w2)", 1) ast = fpapi.parse(code, ignore_comments=False) name = "testkern_field_type" with pytest.raises(ParseError) as excinfo: @@ -154,7 +154,7 @@ def test_field_gh_sum_invalid(): assert ("In the LFRic API, allowed accesses for fields on continuous " "function spaces that are arguments to kernels that operate on " "cell-columns are ['gh_read', 'gh_write', 'gh_inc', 'gh_readinc']," - " but found 'gh_sum' for 'w2'" in str(excinfo.value)) + " but found 'gh_reduction' for 'w2'" in str(excinfo.value)) def test_ad_fld_type_too_few_args(): diff --git a/src/psyclone/tests/domain/lfric/lfric_scalar_mdata_test.py b/src/psyclone/tests/domain/lfric/lfric_scalar_mdata_test.py index 1d01b65040..2e47f9e428 100644 --- a/src/psyclone/tests/domain/lfric/lfric_scalar_mdata_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_scalar_mdata_test.py @@ -198,8 +198,8 @@ def test_ad_scalar_type_no_write(): with pytest.raises(ParseError) as excinfo: _ = LFRicKernMetadata(ast, name=name) assert ("scalar arguments must have read-only ('gh_read') or a " - "reduction ['gh_sum'] access but found 'gh_write'" in - str(excinfo.value)) + "reduction ['gh_reduction'] access but found " + "'gh_write'" in str(excinfo.value)) def test_ad_scalar_type_no_inc(): @@ -215,8 +215,8 @@ def test_ad_scalar_type_no_inc(): with pytest.raises(ParseError) as excinfo: _ = LFRicKernMetadata(ast, name=name) assert ("scalar arguments must have read-only ('gh_read') or a " - "reduction ['gh_sum'] access but found 'gh_inc'" in - str(excinfo.value)) + "reduction ['gh_reduction'] access but found " + "'gh_inc'" in str(excinfo.value)) def test_ad_scalar_type_no_readwrite(): @@ -233,28 +233,28 @@ def test_ad_scalar_type_no_readwrite(): with pytest.raises(ParseError) as excinfo: _ = LFRicKernMetadata(ast, name=name) assert ("scalar arguments must have read-only ('gh_read') or a " - "reduction ['gh_sum'] access but found 'gh_readwrite'" in - str(excinfo.value)) + "reduction ['gh_reduction'] access but found " + "'gh_readwrite'" in str(excinfo.value)) @pytest.mark.parametrize("scalar_type", ["gh_integer", "gh_logical"]) def test_ad_integer_logical_scalar_type_no_sum(scalar_type): ''' Tests that an error is raised when the argument descriptor - metadata for an 'integer' or a 'logical' scalar specifies 'GH_SUM' + metadata for an 'integer' or a 'logical' scalar specifies 'GH_REDUCTION' access (reduction). ''' fparser.logging.disable(fparser.logging.CRITICAL) code = CODE.replace( f"arg_type(gh_scalar, {scalar_type}, gh_read)", - f"arg_type(gh_scalar, {scalar_type}, gh_sum)", 1) + f"arg_type(gh_scalar, {scalar_type}, gh_reduction)", 1) ast = fpapi.parse(code, ignore_comments=False) name = "testkern_qr_type" with pytest.raises(ParseError) as excinfo: _ = LFRicKernMetadata(ast, name=name) - assert (f"reduction access 'gh_sum' is only valid with a real scalar " - f"argument, but a scalar argument with '{scalar_type}' data type " - in str(excinfo.value)) + assert (f"reduction access 'gh_reduction' is only valid with a real " + f"scalar argument, but a scalar argument with '{scalar_type}' " + f"data type " in str(excinfo.value)) def test_no_vector_scalar(): @@ -485,14 +485,14 @@ def test_multiple_updated_scalar_args(): kernel that writes to more than one of its field and scalar arguments ''' fparser.logging.disable(fparser.logging.CRITICAL) code = CODE.replace("arg_type(gh_scalar, gh_real, gh_read)", - "arg_type(gh_scalar, gh_real, gh_sum)", 1) + "arg_type(gh_scalar, gh_real, gh_reduction)", 1) ast = fpapi.parse(code, ignore_comments=False) name = "testkern_qr_type" with pytest.raises(ParseError) as excinfo: _ = LFRicKernMetadata(ast, name=name) assert ("A user-supplied LFRic kernel must not write/update a scalar " "argument but kernel 'testkern_qr_type' has a scalar " - "argument with 'gh_sum' access." in str(excinfo.value)) + "argument with 'gh_reduction' access." in str(excinfo.value)) def test_scalar_different_data_types_invoke(): 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 95bcd0a593..9085d31544 100644 --- a/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_scalar_stubgen_test.py @@ -135,4 +135,4 @@ def test_stub_generate_with_scalar_sums_err(): assert ( "A user-supplied LFRic kernel must not write/update a scalar " "argument but kernel 'simple_with_reduction_type' has a scalar " - "argument with 'gh_sum' access." in str(err.value)) + "argument with 'gh_reduction' access." in str(err.value)) diff --git a/src/psyclone/tests/lfric_test.py b/src/psyclone/tests/lfric_test.py index 5562a03094..bcafd1622f 100644 --- a/src/psyclone/tests/lfric_test.py +++ b/src/psyclone/tests/lfric_test.py @@ -2785,14 +2785,14 @@ def test_operator_gh_sum_invalid(): fparser.logging.disable(fparser.logging.CRITICAL) code = CODE.replace( "arg_type(gh_operator, gh_real, gh_read, w2, w2)", - "arg_type(gh_operator, gh_real, gh_sum, w2, w2)", 1) + "arg_type(gh_operator, gh_real, gh_reduction, w2, w2)", 1) ast = fpapi.parse(code, ignore_comments=False) name = "testkern_qr_type" with pytest.raises(ParseError) as excinfo: _ = LFRicKernMetadata(ast, name=name) assert ("allowed accesses for operators are ['gh_read', 'gh_write', " "'gh_readwrite'] because they behave as discontinuous " - "quantities, but found 'gh_sum'" in str(excinfo.value)) + "quantities, but found 'gh_reduction'" in str(excinfo.value)) def test_derived_type_arg(dist_mem, tmpdir): diff --git a/src/psyclone/tests/test_files/lfric/15.10.9_min_max_X_builtin.f90 b/src/psyclone/tests/test_files/lfric/15.10.9_min_max_X_builtin.f90 new file mode 100644 index 0000000000..e83f626a0a --- /dev/null +++ b/src/psyclone/tests/test_files/lfric/15.10.9_min_max_X_builtin.f90 @@ -0,0 +1,52 @@ +! ----------------------------------------------------------------------------- +! BSD 3-Clause License +! +! Copyright (c) 2021-2025, Science and Technology Facilities Council. +! All rights reserved. +! +! Redistribution and use in source and binary forms, with or without +! modification, are permitted provided that the following conditions are met: +! +! * Redistributions of source code must retain the above copyright notice, this +! list of conditions and the following disclaimer. +! +! * Redistributions in binary form must reproduce the above copyright notice, +! this list of conditions and the following disclaimer in the documentation +! and/or other materials provided with the distribution. +! +! * Neither the name of the copyright holder nor the names of its +! contributors may be used to endorse or promote products derived from +! this software without specific prior written permission. +! +! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +! "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +! LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +! FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +! COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +! INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +! BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +! LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +! CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +! ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +! POSSIBILITY OF SUCH DAMAGE. +! ----------------------------------------------------------------------------- +! Authors: I. Kavcic, Met Office +! A. R. Porter, STFC Daresbury Laboratory + +program single_invoke + + ! Description: single point-wise operation (min/max of field elements) + ! specified in an invoke call. + use constants_mod, only: r_def + use field_mod, only: field_type + + implicit none + + type(field_type) :: f1 + real(r_def) :: amin, amax + + call invoke( minval_X(amin, f1), & + maxval_X(amax, f1) ) + +end program single_invoke diff --git a/src/psyclone/tests/test_files/lfric/int_reduction_builtins_mod.f90 b/src/psyclone/tests/test_files/lfric/int_reduction_builtins_mod.f90 index 7d8529fae5..90f7e26d3e 100644 --- a/src/psyclone/tests/test_files/lfric/int_reduction_builtins_mod.f90 +++ b/src/psyclone/tests/test_files/lfric/int_reduction_builtins_mod.f90 @@ -44,7 +44,7 @@ module lfric_builtins_mod type, public, extends(kernel_type) :: X_innerproduct_Y private type(arg_type) :: meta_args(3) = (/ & - arg_type(GH_SCALAR, GH_INTEGER, GH_SUM), & + arg_type(GH_SCALAR, GH_INTEGER, GH_REDUCTION), & arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1), & arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1) & /) diff --git a/src/psyclone/tests/test_files/lfric/invalid_builtins_mod.f90 b/src/psyclone/tests/test_files/lfric/invalid_builtins_mod.f90 index 549e854dbd..86125d3427 100644 --- a/src/psyclone/tests/test_files/lfric/invalid_builtins_mod.f90 +++ b/src/psyclone/tests/test_files/lfric/invalid_builtins_mod.f90 @@ -63,11 +63,12 @@ module lfric_builtins_mod end type aX_plus_Y !> An invalid built-in that updates two arguments where one is a scalar - !! reduction ('gh_sum') and the other is a field with 'gh_readwrite' access + !! reduction ('gh_reduction') and the other is a field with 'gh_readwrite' + !! access. type, public, extends(kernel_type) :: inc_aX_plus_Y private type(arg_type) :: meta_args(3) = (/ & - arg_type(GH_SCALAR, GH_REAL, GH_SUM ), & + arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION ), & arg_type(GH_FIELD, GH_REAL, GH_READWRITE, ANY_SPACE_1), & arg_type(GH_FIELD, GH_REAL, GH_READ, ANY_SPACE_1) & /) @@ -110,7 +111,7 @@ module lfric_builtins_mod type, public, extends(kernel_type) :: setval_X private type(arg_type) :: meta_args(2) = (/ & - arg_type(GH_SCALAR, GH_REAL, GH_SUM), & + arg_type(GH_SCALAR, GH_REAL, GH_REDUCTION), & arg_type(GH_SCALAR, GH_REAL, GH_READ) & /) integer :: operates_on = DOF diff --git a/src/psyclone/tests/test_files/lfric/testkern_simple_with_reduction_mod.f90 b/src/psyclone/tests/test_files/lfric/testkern_simple_with_reduction_mod.f90 index b383223987..564aececbc 100644 --- a/src/psyclone/tests/test_files/lfric/testkern_simple_with_reduction_mod.f90 +++ b/src/psyclone/tests/test_files/lfric/testkern_simple_with_reduction_mod.f90 @@ -43,7 +43,7 @@ module simple_with_reduction_mod type, extends(kernel_type) :: simple_with_reduction_type type(arg_type), dimension(3) :: meta_args = & - (/ arg_type(gh_scalar, gh_real, gh_sum), & + (/ arg_type(gh_scalar, gh_real, gh_reduction),& arg_type(gh_field, gh_real, gh_read, w1), & arg_type(gh_scalar, gh_integer, gh_read) /) integer :: operates_on = cell_column