# -----------------------------------------------------------------------------
# BSD 3-Clause License
#
# Copyright (c) 2017-2026, 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 R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
# A. B. G. Chalk STFC Daresbury Lab
# J. Henrichs, Bureau of Meteorology
# Modified I. Kavcic, J. G. Wallwork, O. Brunt and L. Turner, Met Office
# S. Valat, Inria / Laboratoire Jean Kuntzmann
# M. Schreiber, Univ. Grenoble Alpes / Inria / Lab. Jean Kuntzmann
# J. Dendy, Met Office
''' This module provides the LFRic redundant-computation transformation. '''
from typing import Any, Optional, Union
from psyclone.configuration import Config
from psyclone.domain.lfric import LFRicInvokeSchedule, LFRicConstants
from psyclone.psyir.nodes import DataNode, Directive
from psyclone.psyir.nodes.literal import Literal
from psyclone.psyir.nodes.loop import Loop
from psyclone.psyir.transformations.loop_trans import LoopTrans
from psyclone.psyir.transformations.transformation_error import (
TransformationError)
from psyclone.transformations import check_intergrid
from psyclone.utils import transformation_documentation_wrapper
[docs]
@transformation_documentation_wrapper
class LFRicRedundantComputationTrans(LoopTrans):
'''This transformation allows the user to modify a loop's bounds so
that redundant computation will be performed. Redundant
computation can result in halo exchanges being modified, new halo
exchanges being added or existing halo exchanges being removed.
* This transformation should be performed before any
parallelisation transformations (e.g. for OpenMP) to the loop in
question and will raise an exception if this is not the case.
* This transformation can not be applied to a loop containing a
reduction and will again raise an exception if this is the case.
* This transformation can only be used to add redundant
computation to a loop, not to remove it.
* This transformation allows a loop that is already performing
redundant computation to be modified, but only if the depth is
increased.
'''
def __str__(self):
return "Change iteration space to perform redundant computation"
[docs]
def validate(self, node: Loop, options=None, **kwargs):
'''Perform various checks to ensure that it is valid to apply the
RedundantComputation transformation to the supplied node
:param node: the supplied node on which we are performing
validity checks.
:raises TransformationError: if the parent of the loop is a
:py:class:`psyclone.psyir.nodes.Directive`.
:raises TransformationError: if the parent of the loop is not a
:py:class:`psyclone.psyir.nodes.Loop` or a
:py:class:`psyclone.psyGen.LFRicInvokeSchedule`.
:raises TransformationError: if the parent of the loop is
:py:class:`psyclone.psyir.nodes.Loop` but the original loop does
not iterate over 'cells_in_colour'.
:raises TransformationError: if the parent of the loop is a
:py:class:`psyclone.psyir.nodes.Loop` but the parent does not
iterate over 'colours'.
:raises TransformationError: if the parent of the loop is a
:py:class:`psyclone.psyir.nodes.Loop` but the parent's parent is
not a :py:class:`psyclone.psyGen.LFRicInvokeSchedule`.
:raises TransformationError: if this transformation is applied
when distributed memory is not switched on.
:raises TransformationError: if the loop does not iterate over
cells, dofs or colour.
:raises TransformationError: if the loop contains a kernel that
operates on halo cells or only on owned cells/dofs.
:raises TransformationError: if the transformation is setting the
loop to the maximum halo depth but the loop already computes
to the maximum halo depth.
:raises TransformationError: if the transformation is setting the
loop to the maximum halo depth but the loop contains a stencil
access (as this would result in the field being accessed
beyond the halo depth).
:raises TypeError: if the supplied depth value is of the wrong type.
:raises TransformationError: if the supplied depth value is less
than 1.
:raises TransformationError: if the supplied depth value is not
greater than 1 when a continuous loop is modified as this is
the minimum valid value.
:raises TransformationError: if the supplied depth value is not
greater than the existing depth value, as we should not need
to undo existing transformations.
:raises TransformationError: if a depth value has been supplied
but the loop has already been set to the maximum halo depth.
'''
if not options:
self.validate_options(**kwargs)
depth = self.get_option("depth", **kwargs)
else:
# TODO #2668: Deprecate options dictionary.
depth = options.get("depth")
# pylint: disable=too-many-branches
# check node is a loop
super().validate(node, options=options)
# Check loop's parent is the InvokeSchedule, or that it is nested
# in a colours loop and perform other colour(s) loop checks,
# otherwise halo exchange placement might fail. The only
# current example where the placement would fail is when
# directives have already been added. This could be fixed but
# it actually makes sense to require redundant computation
# transformations to be applied before adding directives so it
# is not particularly important.
dir_node = node.ancestor(Directive)
if dir_node:
raise TransformationError(
f"In the LFRicRedundantComputation transformation apply "
f"method the supplied loop is sits beneath a directive of "
f"type {type(dir_node)}. Redundant computation must be applied"
f" before directives are added.")
if not (isinstance(node.parent, LFRicInvokeSchedule) or
isinstance(node.parent.parent, Loop)):
raise TransformationError(
f"In the LFRicRedundantComputation transformation "
f"apply method the parent of the supplied loop must be the "
f"LFRicInvokeSchedule, or a Loop, but found "
f"{type(node.parent)}")
if isinstance(node.parent.parent, Loop):
if node.loop_type != "cells_in_colour":
raise TransformationError(
f"In the LFRicRedundantComputation transformation "
f"apply method, if the parent of the supplied Loop is "
f"also a Loop then the supplied Loop must iterate over "
f"'cells_in_colour', but found '{node.loop_type}'")
if node.parent.parent.loop_type != "colours":
raise TransformationError(
f"In the LFRicRedundantComputation transformation "
f"apply method, if the parent of the supplied Loop is "
f"also a Loop then the parent must iterate over "
f"'colours', but found '{node.parent.parent.loop_type}'")
if not isinstance(node.parent.parent.parent, LFRicInvokeSchedule):
raise TransformationError(
f"In the LFRicRedundantComputation transformation "
f"apply method, if the parent of the supplied Loop is "
f"also a Loop then the parent's parent must be the "
f"LFRicInvokeSchedule, but found {type(node.parent)}")
if not Config.get().distributed_memory:
raise TransformationError(
"In the LFRicRedundantComputation transformation apply "
"method distributed memory must be switched on")
# loop must iterate over cell-column, dof or cell-in-colour. Note, an
# empty loop_type iterates over cell-columns.
if node.loop_type not in ["", "dof", "cells_in_colour"]:
raise TransformationError(
f"In the LFRicRedundantComputation transformation apply "
f"method the loop type must be one of '' (cell-columns), 'dof'"
f" or 'cells_in_colour', but found '{node.loop_type}'")
const = LFRicConstants()
for kern in node.kernels():
if "halo" in kern.iterates_over:
raise TransformationError(
f"Cannot apply the {self.name} transformation to kernels "
f"that operate on halo cells but kernel '{kern.name}' "
f"operates on '{kern.iterates_over}'.")
if kern.iterates_over in const.NO_RC_ITERATION_SPACES:
raise TransformationError(
f"Cannot apply the {self.name} transformation to kernel "
f"'{kern.name}' because it does not support redundant "
f"computation (it operates on '{kern.iterates_over}').")
# We don't currently support the application of transformations to
# loops containing inter-grid kernels
check_intergrid(node)
# TODO #2668: Deprecate options dictionary.
if options:
depth = options.get("depth")
if depth is None:
if node.upper_bound_name in const.HALO_ACCESS_LOOP_BOUNDS:
if not node.upper_bound_halo_depth:
raise TransformationError(
"In the LFRicRedundantComputation transformation "
"apply method the loop is already set to the maximum "
"halo depth so this transformation does nothing")
for call in node.kernels():
for arg in call.arguments.args:
if arg.stencil:
raise TransformationError(
f"In the LFRicRedundantComputation "
f"transformation apply method the loop "
f"contains field '{arg.name}' with a stencil "
f"access in kernel '{call.name}', so it is "
f"invalid to set redundant computation to "
f"maximum depth")
else:
if not isinstance(depth, int):
raise TypeError(
f"In the LFRicRedundantComputation transformation "
f"apply method the supplied depth should be an integer but"
f" found type '{type(depth)}'")
if depth < 1:
raise TransformationError(
"In the LFRicRedundantComputation transformation "
"apply method the supplied depth is less than 1")
if node.upper_bound_name in const.HALO_ACCESS_LOOP_BOUNDS:
if node.upper_bound_halo_depth:
if isinstance(node.upper_bound_halo_depth, Literal):
upper_bound = int(node.upper_bound_halo_depth.value)
if upper_bound >= depth:
raise TransformationError(
f"In the LFRicRedundantComputation "
f"transformation apply method the supplied "
f"depth ({depth}) must be greater than the "
f"existing halo depth ({upper_bound})")
else:
raise TransformationError(
"In the LFRicRedundantComputation transformation "
"apply method the loop is already set to the maximum "
"halo depth so can't be set to a fixed value")
[docs]
def apply(self,
node: Loop,
options: Optional[dict[str, Any]] = None,
depth: Optional[Union[int, DataNode]] = None,
**kwargs):
# pylint:disable=arguments-renamed
'''Apply the redundant computation transformation to the loop
:py:obj:`node`. This transformation can be applied to loops iterating
over 'cells or 'dofs'. if :py:obj:`depth` is set to a value then the
value will be the depth of the field's halo over which redundant
computation will be performed. If :py:obj:`depth` is not set to a
value then redundant computation will be performed to the full depth
of the field's halo.
:param node: the loop to transform.
:param options: a dictionary with options for transformations.
:param depth: the depth to which to perform redundant computation.
Default is None in which case the full halo depth is used.
'''
if options:
# TODO #2668: Deprecate options dictionary.
depth = options.get("depth")
# TODO #2668: remove options dictionary.
self.validate(node, options=options, depth=depth, **kwargs)
loop = node
if loop.loop_type == "":
# Loop is over cells
loop.set_upper_bound("cell_halo", depth)
elif loop.loop_type == "cells_in_colour":
# Loop is over cells of a single colour
loop.set_upper_bound("colour_halo", depth)
elif loop.loop_type == "dof":
loop.set_upper_bound("dof_halo", depth)
else:
raise TransformationError(
f"Unsupported loop_type '{loop.loop_type}' found in "
f"LFRicRedundant ComputationTrans.apply()")
# Add/remove halo exchanges as required due to the redundant
# computation
loop.update_halo_exchanges()
# For Sphinx AutoAPI documentation generation.
__all__ = ["LFRicRedundantComputationTrans"]