Reference Guide  2.5.0
psyclone.domain.lfric.lfric_loop.LFRicLoop Class Reference
Inheritance diagram for psyclone.domain.lfric.lfric_loop.LFRicLoop:
Collaboration diagram for psyclone.domain.lfric.lfric_loop.LFRicLoop:

Public Member Functions

def __init__ (self, loop_type="", **kwargs)
 
def lower_to_language_level (self)
 
def node_str (self, colour=True)
 
def load (self, kern)
 
def set_lower_bound (self, name, index=None)
 
def set_upper_bound (self, name, index=None)
 
def upper_bound_name (self)
 
def upper_bound_halo_depth (self)
 
def update_halo_exchanges (self)
 
def create_halo_exchanges (self)
 
def start_expr (self)
 
def stop_expr (self)
 
def gen_code (self, parent)
 
def gen_mark_halos_clean_dirty (self, parent)
 
def independent_iterations (self, test_all_variables=False, signatures_to_ignore=None, dep_tools=None)
 
- Public Member Functions inherited from psyclone.domain.common.psylayer.psyloop.PSyLoop
def __eq__ (self, other)
 
def dag_name (self)
 
def valid_loop_types (self)
 
def loop_type (self)
 
def loop_type (self, value)
 
def field_space (self)
 
def field_space (self, my_field_space)
 
def field_name (self)
 
def field (self)
 
def field_name (self, my_field_name)
 
def iteration_space (self)
 
def iteration_space (self, it_space)
 
def kernel (self)
 
def kernel (self, kern)
 
def __str__ (self)
 
def has_inc_arg (self)
 
def unique_modified_args (self, arg_type)
 
def unique_fields_with_halo_reads (self)
 
def args_filter (self, arg_types=None, arg_accesses=None, unique=False)
 

Public Attributes

 loop_type
 
 variable
 
- Public Attributes inherited from psyclone.domain.common.psylayer.psyloop.PSyLoop
 loop_type
 
 field
 
 field_name
 
 field_space
 
 iteration_space
 
 kernel
 

Detailed Description

The LFRic-specific PSyLoop class. This passes the LFRic-specific
loop information to the base class so it creates the one
we require.  Creates LFRic-specific loop bounds when the code is
being generated.

:param str loop_type: the type (iteration space) of this loop.
:param kwargs: additional keyword arguments provided to the PSyIR node.
:type kwargs: unwrapped dict.

:raises InternalError: if an unrecognised loop_type is specified.

Definition at line 58 of file lfric_loop.py.

Member Function Documentation

◆ create_halo_exchanges()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.create_halo_exchanges (   self)
Add halo exchanges before this loop as required by fields within
this loop. To keep the logic simple we assume that any field
that accesses the halo will require a halo exchange and then
remove the halo exchange if this is not the case (when
previous writers perform sufficient redundant computation). It
is implemented this way as the halo exchange class determines
whether it is required or not so a halo exchange needs to
exist in order to find out. The appropriate logic is coded in
the _add_halo_exchange helper method. In some cases a new halo
exchange will replace an existing one. In this situation that
routine also removes the old one.

Definition at line 704 of file lfric_loop.py.

704  def create_halo_exchanges(self):
705  '''Add halo exchanges before this loop as required by fields within
706  this loop. To keep the logic simple we assume that any field
707  that accesses the halo will require a halo exchange and then
708  remove the halo exchange if this is not the case (when
709  previous writers perform sufficient redundant computation). It
710  is implemented this way as the halo exchange class determines
711  whether it is required or not so a halo exchange needs to
712  exist in order to find out. The appropriate logic is coded in
713  the _add_halo_exchange helper method. In some cases a new halo
714  exchange will replace an existing one. In this situation that
715  routine also removes the old one.
716 
717  '''
718  for halo_field in self.unique_fields_with_halo_reads():
719  # for each unique field in this loop that has its halo
720  # read (including annexed dofs), find the previous update
721  # of this field
722  prev_arg_list = halo_field.backward_write_dependencies()
723  if not prev_arg_list:
724  # field has no previous dependence so create new halo
725  # exchange(s) as we don't know the state of the fields
726  # halo on entry to the invoke
727  self._add_halo_exchange(halo_field)
728  else:
729  # field has one or more previous dependencies
730  if len(prev_arg_list) > 1:
731  # field has more than one previous dependencies so
732  # should be a vector
733  if halo_field.vector_size <= 1:
734  raise GenerationError(
735  f"Error in create_halo_exchanges. Expecting field "
736  f"'{halo_field.name}' to be a vector as it has "
737  f"multiple previous dependencies")
738  if len(prev_arg_list) != halo_field.vector_size:
739  raise GenerationError(
740  f"Error in create_halo_exchanges. Expecting a "
741  f"dependence for each vector index for field "
742  f"'{halo_field.name}' but the number of "
743  f"dependencies is '{halo_field.vector_size}' and "
744  f"the vector size is '{len(prev_arg_list)}'.")
745  for arg in prev_arg_list:
746  # Avoid circular import
747  # pylint: disable=import-outside-toplevel
748  from psyclone.dynamo0p3 import LFRicHaloExchange
749  if not isinstance(arg.call, LFRicHaloExchange):
750  raise GenerationError(
751  "Error in create_halo_exchanges. Expecting "
752  "all dependent nodes to be halo exchanges")
753  prev_node = prev_arg_list[0].call
754  # Avoid circular import
755  # pylint: disable=import-outside-toplevel
756  from psyclone.dynamo0p3 import LFRicHaloExchange
757  if not isinstance(prev_node, LFRicHaloExchange):
758  # previous dependence is not a halo exchange so
759  # call the add halo exchange logic which
760  # determines whether a halo exchange is required
761  # or not
762  self._add_halo_exchange(halo_field)
763 

References psyclone.domain.lfric.lfric_loop.LFRicLoop._add_halo_exchange(), psyclone.gocean1p0.GOLoop._add_halo_exchange(), and psyclone.domain.common.psylayer.psyloop.PSyLoop.unique_fields_with_halo_reads().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gen_code()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.gen_code (   self,
  parent 
)
 Call the base class to generate the code and then add any
required halo exchanges.

:param parent: an f2pygen object that will be the parent of \
    f2pygen objects created in this method.
:type parent: :py:class:`psyclone.f2pygen.BaseGen`

Definition at line 838 of file lfric_loop.py.

838  def gen_code(self, parent):
839  ''' Call the base class to generate the code and then add any
840  required halo exchanges.
841 
842  :param parent: an f2pygen object that will be the parent of \
843  f2pygen objects created in this method.
844  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
845 
846 
847  '''
848  # pylint: disable=too-many-statements, too-many-branches
849  # Check that we're not within an OpenMP parallel region if
850  # we are a loop over colours.
851  if self._loop_type == "colours" and self.is_openmp_parallel():
852  raise GenerationError("Cannot have a loop over colours within an "
853  "OpenMP parallel region.")
854 
855  super().gen_code(parent)
856  # TODO #1010: gen_code of this loop calls the PSyIR lowering version,
857  # but that method can not currently provide sibiling nodes because the
858  # ancestor is not PSyIR, so for now we leave the remainder of the
859  # gen_code logic here instead of removing the whole method.
860 
861  if not (Config.get().distributed_memory and
862  self._loop_type != "colour"):
863  # No need to add halo exchanges so we are done.
864  return
865 
866  # Set halo clean/dirty for all fields that are modified
867  if not self.unique_modified_args("gh_field"):
868  return
869 
870  if self.ancestor((ACCRegionDirective, OMPRegionDirective)):
871  # We cannot include calls to set halos dirty/clean within OpenACC
872  # or OpenMP regions. This is handled by the appropriate Directive
873  # class instead.
874  # TODO #1755 can this check be made more general (e.g. to include
875  # Extraction regions)?
876  return
877 
878  parent.add(CommentGen(parent, ""))
879  if self._loop_type != "null":
880  prev_node_name = "loop"
881  else:
882  prev_node_name = "kernel"
883  parent.add(CommentGen(parent, f" Set halos dirty/clean for fields "
884  f"modified in the above {prev_node_name}"))
885  parent.add(CommentGen(parent, ""))
886 
887  self.gen_mark_halos_clean_dirty(parent)
888 
889  parent.add(CommentGen(parent, ""))
890 

References psyclone.domain.common.psylayer.psyloop.PSyLoop._loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop._loop_type, psyclone.gocean1p0.GOLoop._loop_type, psyclone.psyir.nodes.node.Node.ancestor(), psyclone.domain.common.psylayer.psyloop.PSyLoop.gen_mark_halos_clean_dirty(), psyclone.domain.lfric.lfric_loop.LFRicLoop.gen_mark_halos_clean_dirty(), psyclone.psyir.nodes.node.Node.is_openmp_parallel(), and psyclone.domain.common.psylayer.psyloop.PSyLoop.unique_modified_args().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ gen_mark_halos_clean_dirty()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.gen_mark_halos_clean_dirty (   self,
  parent 
)
Generates the necessary code to mark halo regions for all modified
fields as clean or dirty following execution of this loop.

:param parent: the node in the f2pygen AST to which to add content.
:type parent: :py:class:`psyclone.f2pygen.BaseGen`

Reimplemented from psyclone.domain.common.psylayer.psyloop.PSyLoop.

Definition at line 891 of file lfric_loop.py.

891  def gen_mark_halos_clean_dirty(self, parent):
892  '''
893  Generates the necessary code to mark halo regions for all modified
894  fields as clean or dirty following execution of this loop.
895 
896  :param parent: the node in the f2pygen AST to which to add content.
897  :type parent: :py:class:`psyclone.f2pygen.BaseGen`
898 
899  '''
900  # Set halo clean/dirty for all fields that are modified
901  fields = self.unique_modified_args("gh_field")
902 
903  sym_table = self.ancestor(InvokeSchedule).symbol_table
904 
905  # First set all of the halo dirty unless we are
906  # subsequently going to set all of the halo clean
907  for field in fields:
908  # Avoid circular import
909  # pylint: disable=import-outside-toplevel
910  from psyclone.dynamo0p3 import HaloWriteAccess
911  # The HaloWriteAccess class provides information about how the
912  # supplied field is accessed within its parent loop
913  hwa = HaloWriteAccess(field, sym_table)
914  if not hwa.max_depth or hwa.dirty_outer:
915  # output set dirty as some of the halo will not be set to clean
916  if field.vector_size > 1:
917  # the range function below returns values from 1 to the
918  # vector size which is what we require in our Fortran code
919  for index in range(1, field.vector_size+1):
920  parent.add(CallGen(parent, name=field.proxy_name +
921  f"({index})%set_dirty()"))
922  else:
923  parent.add(CallGen(parent, name=field.proxy_name +
924  "%set_dirty()"))
925  # Now set appropriate parts of the halo clean where
926  # redundant computation has been performed.
927  if hwa.literal_depth:
928  # halo access(es) is/are to a fixed depth
929  halo_depth = hwa.literal_depth
930  if hwa.dirty_outer:
931  halo_depth -= 1
932  if halo_depth > 0:
933  if field.vector_size > 1:
934  # The range function below returns values from 1 to the
935  # vector size, as required in our Fortran code.
936  for index in range(1, field.vector_size+1):
937  parent.add(CallGen(
938  parent, name=f"{field.proxy_name}({index})%"
939  f"set_clean({halo_depth})"))
940  else:
941  parent.add(CallGen(
942  parent, name=f"{field.proxy_name}%set_clean("
943  f"{halo_depth})"))
944  elif hwa.max_depth:
945  # halo accesses(s) is/are to the full halo
946  # depth (-1 if continuous)
947  halo_depth = sym_table.lookup_with_tag(
948  "max_halo_depth_mesh").name
949 
950  if hwa.dirty_outer:
951  # a continuous field iterating over cells leaves the
952  # outermost halo dirty
953  halo_depth += "-1"
954  if field.vector_size > 1:
955  # the range function below returns values from 1 to the
956  # vector size which is what we require in our Fortran code
957  for index in range(1, field.vector_size+1):
958  call = CallGen(parent,
959  name=f"{field.proxy_name}({index})%"
960  f"set_clean({halo_depth})")
961  parent.add(call)
962  else:
963  call = CallGen(parent, name=f"{field.proxy_name}%"
964  f"set_clean({halo_depth})")
965  parent.add(call)
966 

References psyclone.psyir.nodes.node.Node.ancestor(), and psyclone.domain.common.psylayer.psyloop.PSyLoop.unique_modified_args().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ independent_iterations()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.independent_iterations (   self,
  test_all_variables = False,
  signatures_to_ignore = None,
  dep_tools = None 
)
This function is an LFRic-specific override of the default method
in the Loop class. It allows domain-specific rules to be applied when
determining whether or not loop iterations are independent.

:param bool test_all_variables: if True, it will test if all variable
    accesses are independent, otherwise it will stop after the first
    variable access is found that isn't.
:param signatures_to_ignore: list of signatures for which to skip
    the access checks.
:type signatures_to_ignore: Optional[
    List[:py:class:`psyclone.core.Signature`]]
:param dep_tools: an optional instance of DependencyTools so that the
    caller can access any diagnostic messages detailing why the loop
    iterations are not independent.
:type dep_tools: Optional[
    :py:class:`psyclone.psyir.tools.DependencyTools`]

:returns: True if the loop iterations are independent, False otherwise.
:rtype: bool

Definition at line 967 of file lfric_loop.py.

970  dep_tools=None):
971  '''
972  This function is an LFRic-specific override of the default method
973  in the Loop class. It allows domain-specific rules to be applied when
974  determining whether or not loop iterations are independent.
975 
976  :param bool test_all_variables: if True, it will test if all variable
977  accesses are independent, otherwise it will stop after the first
978  variable access is found that isn't.
979  :param signatures_to_ignore: list of signatures for which to skip
980  the access checks.
981  :type signatures_to_ignore: Optional[
982  List[:py:class:`psyclone.core.Signature`]]
983  :param dep_tools: an optional instance of DependencyTools so that the
984  caller can access any diagnostic messages detailing why the loop
985  iterations are not independent.
986  :type dep_tools: Optional[
987  :py:class:`psyclone.psyir.tools.DependencyTools`]
988 
989  :returns: True if the loop iterations are independent, False otherwise.
990  :rtype: bool
991 
992  '''
993  # pylint: disable=import-outside-toplevel
994  from psyclone.psyir.tools import DependencyTools, DTCode
995  if not dep_tools:
996  dtools = DependencyTools()
997  else:
998  dtools = dep_tools
999 
1000  if self.loop_type in ["null", "colours"]:
1001  # We know we can't parallelise these loops. ("null" means there
1002  # is no actual loop and "colours" is the *outer* loop over the
1003  # different colours used - it is the inner, "colour" loop over
1004  # cells of a single colour which can be parallelised.)
1005  return False
1006 
1007  try:
1008  stat = dtools.can_loop_be_parallelised(
1009  self, test_all_variables=test_all_variables,
1010  signatures_to_ignore=signatures_to_ignore)
1011  if stat:
1012  return True
1013  except (InternalError, KeyError):
1014  # LFRic still has symbols that don't exist in the symbol_table
1015  # until the gen_code() step, so the dependency analysis raises
1016  # errors in some cases.
1017  # TODO #1648 - when a transformation colours a loop we must
1018  # ensure "last_[halo]_cell_all_colours" is added to the symbol
1019  # table.
1020  return True
1021 
1022  # The generic DA says that this loop cannot be parallelised. However,
1023  # we use domain-specific information to qualify this.
1024  if self.loop_type == "colour":
1025  # This loop is either over cells of a single colour.
1026  # According to LFRic rules this is safe to parallelise.
1027  return True
1028 
1029  if self.loop_type == "dof":
1030  # The generic DA can't see the PSyIR of this Builtin (because it
1031  # hasn't been lowered to language level) so we use
1032  # domain-specific knowledge about its properties.
1033  if self.kernel.is_reduction:
1034  dtools._add_message(
1035  f"Builtin '{self.kernel.name}' performs a reduction",
1036  DTCode.WARN_SCALAR_REDUCTION)
1037  return False
1038  return True
1039 
1040  if self.loop_type == "":
1041  # We can parallelise a non-coloured loop if it only updates
1042  # quantities on discontinuous function spaces. If an LFRic kernel
1043  # updates quantities on a continuous function space then it must
1044  # have at least one argument with GH_INC access. Therefore, we
1045  # can simply check whether or not it has such an argument in order
1046  # to infer the continuity of the space.
1047  if self.has_inc_arg():
1048  dtools._add_message(
1049  f"Kernel '{self.kernel.name}' performs an INC update",
1050  DTCode.ERROR_WRITE_WRITE_RACE)
1051  return False
1052  return True
1053 
1054  raise InternalError(f"independent_iterations: loop of type "
1055  f"'{self.loop_type}' is not supported.")
1056 
1057 
1058 # ---------- Documentation utils -------------------------------------------- #
1059 # The list of module members that we wish AutoAPI to generate
1060 # documentation for. (See https://psyclone-ref.readthedocs.io)

References psyclone.domain.common.psylayer.psyloop.PSyLoop.has_inc_arg(), psyclone.domain.common.psylayer.psyloop.PSyLoop.kernel, psyclone.domain.common.psylayer.psyloop.PSyLoop.loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop.loop_type, psyclone.gocean1p0.GOLoop.loop_type, and psyclone.psyir.nodes.loop.Loop.loop_type().

Here is the call graph for this function:

◆ load()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.load (   self,
  kern 
)
Load the state of this Loop using the supplied Kernel
object. This method is provided so that we can individually
construct Loop objects for a given kernel call.

:param kern: Kernel object to use to populate state of Loop
:type kern: :py:class:`psyclone.domain.lfric.LFRicKern`

:raises GenerationError: if the field updated by the kernel has an \
    unexpected function space or if the kernel's 'operates-on' is \
    not consistent with the loop type.

Definition at line 198 of file lfric_loop.py.

198  def load(self, kern):
199  '''
200  Load the state of this Loop using the supplied Kernel
201  object. This method is provided so that we can individually
202  construct Loop objects for a given kernel call.
203 
204  :param kern: Kernel object to use to populate state of Loop
205  :type kern: :py:class:`psyclone.domain.lfric.LFRicKern`
206 
207  :raises GenerationError: if the field updated by the kernel has an \
208  unexpected function space or if the kernel's 'operates-on' is \
209  not consistent with the loop type.
210 
211  '''
212  self._kern = kern
213 
214  self._field = kern.arguments.iteration_space_arg()
215  self._field_name = self._field.name
216  self._field_space = self._field.function_space
217 
218  if self.loop_type == "null" and kern.iterates_over != "domain":
219  raise GenerationError(
220  f"A LFRicLoop of type 'null' can only contain a kernel that "
221  f"operates on the 'domain' but kernel '{kern.name}' operates "
222  f"on '{kern.iterates_over}'.")
223  self._iteration_space = kern.iterates_over # cell_columns etc.
224 
225  # Loop bounds
226  self.set_lower_bound("start")
227  const = LFRicConstants()
228  if isinstance(kern, LFRicBuiltIn):
229  # If the kernel is a built-in/pointwise operation
230  # then this loop must be over DoFs
231  if Config.get().api_conf("dynamo0.3").compute_annexed_dofs \
232  and Config.get().distributed_memory \
233  and not kern.is_reduction:
234  self.set_upper_bound("nannexed")
235  else:
236  self.set_upper_bound("ndofs")
237  else:
238  if Config.get().distributed_memory:
239  if self._field.is_operator:
240  # We always compute operators redundantly out to the L1
241  # halo
242  self.set_upper_bound("cell_halo", index=1)
243  elif (self.field_space.orig_name in
244  const.VALID_DISCONTINUOUS_NAMES):
245  # Iterate to ncells for all discontinuous quantities,
246  # including any_discontinuous_space
247  self.set_upper_bound("ncells")
248  elif (self.field_space.orig_name in
249  const.CONTINUOUS_FUNCTION_SPACES):
250  # Must iterate out to L1 halo for continuous quantities
251  # unless the only arguments that are updated all have
252  # 'GH_WRITE' access. The only time such an access is
253  # permitted for a field on a continuous space is when the
254  # kernel is implemented such that any writes to a given
255  # shared dof are guaranteed to write the same value. There
256  # is therefore no need to iterate into the L1 halo in order
257  # to get correct values for annexed dofs.
258  if not kern.all_updates_are_writes:
259  self.set_upper_bound("cell_halo", index=1)
260  else:
261  self.set_upper_bound("ncells")
262  elif (self.field_space.orig_name in
263  const.VALID_ANY_SPACE_NAMES):
264  # We don't know whether any_space is continuous or not
265  # so we have to err on the side of caution and assume that
266  # it is. Again, if the only arguments that are updated have
267  # 'GH_WRITE' access then we can relax this condition.
268  if not kern.all_updates_are_writes:
269  self.set_upper_bound("cell_halo", index=1)
270  else:
271  self.set_upper_bound("ncells")
272  else:
273  raise GenerationError(
274  f"Unexpected function space found. Expecting one of "
275  f"{const.VALID_FUNCTION_SPACES} but found "
276  f"'{self.field_space.orig_name}'")
277  else: # sequential
278  self.set_upper_bound("ncells")
279 

References psyclone.domain.common.psylayer.psyloop.PSyLoop._field, psyclone.domain.lfric.arg_index_to_metadata_index.ArgIndexToMetadataIndex._field(), psyclone.domain.lfric.lfric_loop.LFRicLoop._field, psyclone.domain.lfric.metadata_to_arguments_rules.MetadataToArgumentsRules._field(), psyclone.psyGen.HaloExchange._field, psyclone.domain.common.psylayer.psyloop.PSyLoop._field_name, psyclone.domain.lfric.lfric_loop.LFRicLoop._field_name, psyclone.domain.common.psylayer.psyloop.PSyLoop._field_space, psyclone.domain.lfric.lfric_loop.LFRicLoop._field_space, psyclone.gocean1p0.GOLoop._field_space, psyclone.domain.common.psylayer.psyloop.PSyLoop._iteration_space, psyclone.domain.lfric.lfric_loop.LFRicLoop._iteration_space, psyclone.gocean1p0.GOLoop._iteration_space, psyclone.domain.common.psylayer.psyloop.PSyLoop._kern, psyclone.domain.lfric.arg_ordering.ArgOrdering._kern, psyclone.domain.lfric.lfric_loop.LFRicLoop._kern, psyclone.domain.common.psylayer.psyloop.PSyLoop.field_space, psyclone.gocean1p0.GOLoop.field_space, psyclone.domain.common.psylayer.psyloop.PSyLoop.loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop.loop_type, psyclone.gocean1p0.GOLoop.loop_type, psyclone.psyir.nodes.loop.Loop.loop_type(), psyclone.domain.lfric.lfric_loop.LFRicLoop.set_lower_bound(), and psyclone.domain.lfric.lfric_loop.LFRicLoop.set_upper_bound().

Here is the call graph for this function:

◆ lower_to_language_level()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.lower_to_language_level (   self)
In-place replacement of DSL or high-level concepts into generic
PSyIR constructs. This function replaces a LFRicLoop with a PSyLoop
and inserts the loop boundaries into the new PSyLoop, or removes
the loop node in case of a domain kernel. Once TODO #1731 is done
(which should fix the loop boundaries, which atm rely on index of
the loop in the schedule, i.e. can change when transformations are
applied), this function can likely be removed.

:returns: the lowered version of this node.
:rtype: :py:class:`psyclone.psyir.node.Node`

Definition at line 119 of file lfric_loop.py.

119  def lower_to_language_level(self):
120  '''In-place replacement of DSL or high-level concepts into generic
121  PSyIR constructs. This function replaces a LFRicLoop with a PSyLoop
122  and inserts the loop boundaries into the new PSyLoop, or removes
123  the loop node in case of a domain kernel. Once TODO #1731 is done
124  (which should fix the loop boundaries, which atm rely on index of
125  the loop in the schedule, i.e. can change when transformations are
126  applied), this function can likely be removed.
127 
128  :returns: the lowered version of this node.
129  :rtype: :py:class:`psyclone.psyir.node.Node`
130 
131  '''
132  if self._loop_type != "null":
133  # This is not a 'domain' loop (i.e. there is a real loop). First
134  # check that there isn't any validation issues with the node.
135  for child in self.loop_body.children:
136  child.validate_global_constraints()
137 
138  # Then generate the loop bounds, this needs to be done BEFORE
139  # lowering the loop body because it needs kernel information.
140  start = self.start_expr.copy()
141  stop = self.stop_expr.copy()
142  step = self.step_expr.copy()
143 
144  # Now we can lower the nodes in the loop body
145  for child in self.loop_body.children:
146  child.lower_to_language_level()
147 
148  # Finally create the new lowered Loop and replace the domain one
149  loop = Loop.create(self._variable, start, stop, step, [])
150  loop.loop_body._symbol_table = \
151  self.loop_body.symbol_table.shallow_copy()
152  loop.children[3] = self.loop_body.copy()
153  self.replace_with(loop)
154  lowered_node = loop
155  else:
156  # If loop_type is "null" we do not need a loop at all, just the
157  # kernel in its loop_body
158  for child in self.loop_body.children:
159  child.lower_to_language_level()
160  # TODO #1010: This restriction can be removed when also lowering
161  # the parent InvokeSchedule
162  if len(self.loop_body.children) > 1:
163  raise NotImplementedError(
164  f"Lowering LFRic domain loops that produce more than one "
165  f"children is not yet supported, but found:\n "
166  f"{self.view()}")
167  lowered_node = self.loop_body[0].detach()
168  self.replace_with(lowered_node)
169 
170  return lowered_node
171 

References psyclone.domain.common.psylayer.psyloop.PSyLoop._loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop._loop_type, psyclone.gocean1p0.GOLoop._loop_type, psyclone.psyir.nodes.loop.Loop._variable, psyclone.psyir.nodes.loop.Loop.loop_body(), psyclone.psyir.nodes.while_loop.WhileLoop.loop_body(), psyclone.psyir.nodes.node.Node.replace_with(), psyclone.domain.lfric.lfric_loop.LFRicLoop.start_expr(), psyclone.psyir.nodes.loop.Loop.start_expr(), psyclone.psyir.nodes.loop.Loop.step_expr(), psyclone.domain.lfric.lfric_loop.LFRicLoop.stop_expr(), and psyclone.psyir.nodes.loop.Loop.stop_expr().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ node_str()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.node_str (   self,
  colour = True 
)
 Creates a text summary of this loop node. We override this
method from the Loop class because, in Dynamo0.3, the function
space is now an object and we need to call orig_name on it. We
also include the upper loop bound as this can now be modified.

:param bool colour: whether or not to include control codes for colour.

:returns: text summary of this node, optionally with control codes \
          for colour highlighting.
:rtype: str

Reimplemented from psyclone.domain.common.psylayer.psyloop.PSyLoop.

Definition at line 172 of file lfric_loop.py.

172  def node_str(self, colour=True):
173  ''' Creates a text summary of this loop node. We override this
174  method from the Loop class because, in Dynamo0.3, the function
175  space is now an object and we need to call orig_name on it. We
176  also include the upper loop bound as this can now be modified.
177 
178  :param bool colour: whether or not to include control codes for colour.
179 
180  :returns: text summary of this node, optionally with control codes \
181  for colour highlighting.
182  :rtype: str
183 
184  '''
185  if self._loop_type == "null":
186  return f"{self.coloured_name(colour)}[type='null']"
187 
188  if self._upper_bound_halo_depth:
189  upper_bound = (f"{self._upper_bound_name}"
190  f"({self._upper_bound_halo_depth})")
191  else:
192  upper_bound = self._upper_bound_name
193  return (f"{self.coloured_name(colour)}[type='{self._loop_type}', "
194  f"field_space='{self._field_space.orig_name}', "
195  f"it_space='{self.iteration_space}', "
196  f"upper_bound='{upper_bound}']")
197 

References psyclone.domain.common.psylayer.psyloop.PSyLoop._loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop._loop_type, psyclone.gocean1p0.GOLoop._loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_halo_depth, and psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_name.

Here is the caller graph for this function:

◆ set_lower_bound()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.set_lower_bound (   self,
  name,
  index = None 
)
 Set the lower bounds of this loop 

Definition at line 280 of file lfric_loop.py.

280  def set_lower_bound(self, name, index=None):
281  ''' Set the lower bounds of this loop '''
282  const = LFRicConstants()
283  if name not in const.VALID_LOOP_BOUNDS_NAMES:
284  raise GenerationError(
285  "The specified lower bound loop name is invalid")
286  if name in ["inner"] + const.HALO_ACCESS_LOOP_BOUNDS and index < 1:
287  raise GenerationError(
288  "The specified index '{index}' for this lower loop bound is "
289  "invalid")
290  self._lower_bound_name = name
291  self._lower_bound_index = index
292 

References psyclone.domain.lfric.lfric_loop.LFRicLoop._lower_bound_index, and psyclone.domain.lfric.lfric_loop.LFRicLoop._lower_bound_name.

Here is the caller graph for this function:

◆ set_upper_bound()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.set_upper_bound (   self,
  name,
  index = None 
)
Set the upper bound of this loop

:param name: A loop upper bound name. This should be a supported name.
:type name: String
:param index: An optional argument indicating the depth of halo
:type index: int

Definition at line 293 of file lfric_loop.py.

293  def set_upper_bound(self, name, index=None):
294  '''Set the upper bound of this loop
295 
296  :param name: A loop upper bound name. This should be a supported name.
297  :type name: String
298  :param index: An optional argument indicating the depth of halo
299  :type index: int
300 
301  '''
302  const = LFRicConstants()
303  if name not in const.VALID_LOOP_BOUNDS_NAMES:
304  raise GenerationError(
305  f"The specified upper loop bound name is invalid. Expected "
306  f"one of {const.VALID_LOOP_BOUNDS_NAMES} but found '{name}'")
307  if name == "start":
308  raise GenerationError("'start' is not a valid upper bound")
309  # Only halo bounds and inner may have an index. We could just
310  # test for index here and assume that index is None for other
311  # types of bounds, but checking the type of bound as well is a
312  # safer option.
313  if name in (["inner"] + const.HALO_ACCESS_LOOP_BOUNDS) and \
314  index is not None:
315  if index < 1:
316  raise GenerationError(
317  f"The specified index '{index}' for this upper loop bound "
318  f"is invalid")
319  self._upper_bound_name = name
320  self._upper_bound_halo_depth = index
321 

References psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_halo_depth, and psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_name.

Here is the caller graph for this function:

◆ start_expr()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.start_expr (   self)
:returns: the PSyIR for the lower bound of this loop.
:rtype: :py:class:`psyclone.psyir.Node`

Definition at line 765 of file lfric_loop.py.

765  def start_expr(self):
766  '''
767  :returns: the PSyIR for the lower bound of this loop.
768  :rtype: :py:class:`psyclone.psyir.Node`
769 
770  '''
771  inv_sched = self.ancestor(Routine)
772  sym_table = inv_sched.symbol_table
773  loops = inv_sched.loops()
774  posn = None
775  for index, loop in enumerate(loops):
776  if loop is self:
777  posn = index
778  break
779  root_name = f"loop{posn}_start"
780  lbound = sym_table.find_or_create_integer_symbol(root_name,
781  tag=root_name)
782  self.children[0] = Reference(lbound)
783  return self.children[0]
784 

References psyclone.psyir.nodes.node.Node.ancestor(), psyclone.f2pygen.BaseGen.children(), psyclone.psyGen.InlinedKern.children, and psyclone.psyir.nodes.node.Node.children().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ stop_expr()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.stop_expr (   self)
:returns: the PSyIR for the upper bound of this loop.
:rtype: :py:class:`psyclone.psyir.Node`

Definition at line 786 of file lfric_loop.py.

786  def stop_expr(self):
787  '''
788  :returns: the PSyIR for the upper bound of this loop.
789  :rtype: :py:class:`psyclone.psyir.Node`
790 
791  '''
792  inv_sched = self.ancestor(Routine)
793  sym_table = inv_sched.symbol_table
794 
795  if self._loop_type == "colour":
796  # If this loop is over all cells of a given colour then we must
797  # lookup the loop bound as it depends on the current colour.
798  parent_loop = self.ancestor(Loop)
799  colour_var = parent_loop.variable
800 
801  asym = self.kernel.last_cell_all_colours_symbol
802  const = LFRicConstants()
803 
804  if self.upper_bound_name in const.HALO_ACCESS_LOOP_BOUNDS:
805  if self._upper_bound_halo_depth:
806  # TODO: #696 Add kind (precision) once the
807  # LFRicInvokeSchedule constructor has been extended to
808  # create the necessary symbols.
809  halo_depth = Literal(str(self._upper_bound_halo_depth),
810  INTEGER_TYPE)
811  else:
812  # We need to go to the full depth of the halo.
813  root_name = "mesh"
814  if self.kernels()[0].is_intergrid:
815  root_name += f"_{self._field_name}"
816  depth_sym = sym_table.lookup_with_tag(
817  f"max_halo_depth_{root_name}")
818  halo_depth = Reference(depth_sym)
819 
820  return ArrayReference.create(asym, [Reference(colour_var),
821  halo_depth])
822  return ArrayReference.create(asym, [Reference(colour_var)])
823 
824  # This isn't a 'colour' loop so we have already set-up a
825  # variable that holds the upper bound.
826  loops = inv_sched.loops()
827  posn = None
828  for index, loop in enumerate(loops):
829  if loop is self:
830  posn = index
831  break
832  root_name = f"loop{posn}_stop"
833  ubound = sym_table.find_or_create_integer_symbol(root_name,
834  tag=root_name)
835  self.children[1] = Reference(ubound)
836  return self.children[1]
837 

References psyclone.domain.common.psylayer.psyloop.PSyLoop._loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop._loop_type, psyclone.gocean1p0.GOLoop._loop_type, psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_halo_depth, psyclone.psyir.nodes.node.Node.ancestor(), psyclone.f2pygen.BaseGen.children(), psyclone.psyGen.InlinedKern.children, psyclone.psyir.nodes.node.Node.children(), psyclone.domain.common.psylayer.psyloop.PSyLoop.kernel, psyclone.psyir.nodes.node.Node.kernels(), and psyclone.domain.lfric.lfric_loop.LFRicLoop.upper_bound_name().

Here is the call graph for this function:
Here is the caller graph for this function:

◆ update_halo_exchanges()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.update_halo_exchanges (   self)
add and/or remove halo exchanges due to changes in the loops
bounds

Definition at line 675 of file lfric_loop.py.

675  def update_halo_exchanges(self):
676  '''add and/or remove halo exchanges due to changes in the loops
677  bounds'''
678  # this call adds any new halo exchanges that are
679  # required. This is done by adding halo exchanges before this
680  # loop for any fields in the loop that require a halo exchange
681  # and don't already have one
682  self.create_halo_exchanges()
683  # Now remove any existing halo exchanges that are no longer
684  # required. This is done by removing halo exchanges after this
685  # loop where a field in this loop previously had a forward
686  # dependence on a halo exchange but no longer does
687  # pylint: disable=too-many-nested-blocks
688  # Avoid circular import
689  # pylint: disable=import-outside-toplevel
690  from psyclone.dynamo0p3 import LFRicHaloExchange
691  for call in self.kernels():
692  for arg in call.arguments.args:
693  if arg.access in AccessType.all_write_accesses():
694  dep_arg_list = arg.forward_read_dependencies()
695  for dep_arg in dep_arg_list:
696  if isinstance(dep_arg.call, LFRicHaloExchange):
697  # found a halo exchange as a forward dependence
698  # ask the halo exchange if it is required
699  halo_exchange = dep_arg.call
700  required, _ = halo_exchange.required()
701  if not required:
702  halo_exchange.detach()
703 

References psyclone.domain.lfric.lfric_loop.LFRicLoop.create_halo_exchanges(), psyclone.gocean1p0.GOLoop.create_halo_exchanges(), and psyclone.psyir.nodes.node.Node.kernels().

Here is the call graph for this function:

◆ upper_bound_halo_depth()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.upper_bound_halo_depth (   self)
Returns the index of the upper loop bound. This is None if the upper
bound name is not in HALO_ACCESS_LOOP_BOUNDS.

:returns: the depth of the halo for a loops upper bound. If it \
    is None then a depth has not been provided. The depth value is \
    only valid when the upper-bound name is associated with a halo \
    e.g. 'cell_halo'.
:rtype: int

Definition at line 328 of file lfric_loop.py.

328  def upper_bound_halo_depth(self):
329  '''Returns the index of the upper loop bound. This is None if the upper
330  bound name is not in HALO_ACCESS_LOOP_BOUNDS.
331 
332  :returns: the depth of the halo for a loops upper bound. If it \
333  is None then a depth has not been provided. The depth value is \
334  only valid when the upper-bound name is associated with a halo \
335  e.g. 'cell_halo'.
336  :rtype: int
337 
338  '''
339  return self._upper_bound_halo_depth
340 

References psyclone.domain.lfric.lfric_loop.LFRicLoop._add_field_component_halo_exchange(), psyclone.domain.common.psylayer.psyloop.PSyLoop._field_name, psyclone.domain.lfric.lfric_loop.LFRicLoop._field_name, psyclone.domain.common.psylayer.psyloop.PSyLoop._kern, psyclone.domain.lfric.arg_ordering.ArgOrdering._kern, psyclone.domain.lfric.lfric_loop.LFRicLoop._kern, psyclone.domain.lfric.lfric_loop.LFRicLoop._lower_bound_index, psyclone.domain.lfric.lfric_loop.LFRicLoop._lower_bound_name, psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_halo_depth, psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_name, psyclone.psyir.nodes.node.Node.ancestor(), psyclone.domain.common.psylayer.psyloop.PSyLoop.kernel, psyclone.f2pygen.BaseGen.parent(), psyclone.psyir.nodes.node.Node.parent(), psyclone.f2pygen.Directive.position(), psyclone.psyir.nodes.node.Node.position, and psyclone.psyir.nodes.node.Node.walk().

Here is the call graph for this function:

◆ upper_bound_name()

def psyclone.domain.lfric.lfric_loop.LFRicLoop.upper_bound_name (   self)
 Returns the name of the upper loop bound 

Definition at line 323 of file lfric_loop.py.

323  def upper_bound_name(self):
324  ''' Returns the name of the upper loop bound '''
325  return self._upper_bound_name
326 

References psyclone.domain.lfric.lfric_loop.LFRicLoop._upper_bound_name.

Here is the caller graph for this function:

The documentation for this class was generated from the following file: