39 ''' This module implements the PSyclone Dynamo 0.3 API by 1)
40 specialising the required base classes in parser.py (KernelType) and
41 adding a new class (DynFuncDescriptor03) to capture function descriptor
42 metadata and 2) specialising the required base classes in psyGen.py
43 (PSy, Invokes, Invoke, InvokeSchedule, Loop, Kern, Inf, Arguments and
48 from collections
import OrderedDict, namedtuple
49 from dataclasses
import dataclass
50 from typing
import Any
52 from psyclone
import psyGen
59 LFRicCollection, LFRicConstants,
60 LFRicSymbolTable, LFRicKernCallFactory,
61 LFRicKern, LFRicInvokes, LFRicTypes,
63 from psyclone.errors import GenerationError, InternalError, FieldNotFoundError
65 DeallocateGen, DeclGen, DoGen,
66 ModuleGen, TypeDeclGen, UseGen, PSyIRGen)
70 KernelArgument, HaloExchange, GlobalSum,
74 Reference, ACCEnterDataDirective, ScopingNode, ArrayOfStructuresReference,
75 StructureReference, Literal, IfBlock, Call, BinaryOperation, IntrinsicCall)
77 UnresolvedType, DataTypeSymbol,
78 ContainerSymbol, ImportInterface,
79 ArrayType, UnsupportedFortranType)
91 def qr_basis_alloc_args(first_dim, basis_fn):
93 Generate the list of dimensions required to allocate the
94 supplied basis/diff-basis function
96 :param str first_dim: the variable name for the first dimension
97 :param basis_fn: dict holding details on the basis function
99 :type basis_fn: dict containing 'shape', 'fspace' and and 'qr_var' keys
100 holding the quadrature shape, FunctionSpace and name
101 of the associated quadrature variable (as specified in the
102 Algorithm layer), respectively
103 :return: list of dimensions to use to allocate array
104 :rtype: list of strings
106 :raises InternalError: if an unrecognised quadrature shape is encountered.
107 :raises NotImplementedError: if a quadrature shape other than \
108 "gh_quadrature_xyoz" is supplied.
110 const = LFRicConstants()
111 if basis_fn[
"shape"]
not in const.VALID_QUADRATURE_SHAPES:
113 f
"Unrecognised shape ('{basis_fn['''shape''']}') specified in "
114 f
"dynamo0p3.qr_basis_alloc_args(). Should be one of: "
115 f
"{const.VALID_QUADRATURE_SHAPES}")
117 qr_var =
"_" + basis_fn[
"qr_var"]
124 if basis_fn[
"shape"] ==
"gh_quadrature_xyoz":
125 alloc_args = [first_dim, basis_fn[
"fspace"].ndf_name,
126 "np_xy"+qr_var,
"np_z"+qr_var]
132 elif basis_fn[
"shape"] ==
"gh_quadrature_face":
133 alloc_args = [first_dim, basis_fn[
"fspace"].ndf_name,
134 "np_xyz"+qr_var,
"nfaces"+qr_var]
135 elif basis_fn[
"shape"] ==
"gh_quadrature_edge":
136 alloc_args = [first_dim, basis_fn[
"fspace"].ndf_name,
137 "np_xyz"+qr_var,
"nedges"+qr_var]
139 raise NotImplementedError(
140 f
"Unrecognised shape '{basis_fn['''shape''']}' specified in "
141 f
"dynamo0p3.qr_basis_alloc_args(). Should be one of: "
142 f
"{const.VALID_QUADRATURE_SHAPES}")
149 ''' The Dynamo 0.3 API includes a function-space descriptor as
150 well as an argument descriptor which is not supported by the base
151 classes. This class captures the information specified in a
152 function-space descriptor. '''
154 def __init__(self, func_type):
156 if func_type.name !=
'func_type':
158 f
"In the dynamo0.3 API each meta_func entry must be of type "
159 f
"'func_type' but found '{func_type.name}'")
160 if len(func_type.args) < 2:
162 f
"In the dynamo0.3 API each meta_func entry must have at "
163 f
"least 2 args, but found {len(func_type.args)}")
166 for idx, arg
in enumerate(func_type.args):
168 if arg.name
not in const.VALID_FUNCTION_SPACE_NAMES:
170 f
"In the dynamo0p3 API the 1st argument of a "
171 f
"meta_func entry should be a valid function space "
172 f
"name (one of {const.VALID_FUNCTION_SPACE_NAMES}), "
173 f
"but found '{arg.name}' in '{func_type}'")
176 if arg.name
not in const.VALID_METAFUNC_NAMES:
178 f
"In the dynamo0.3 API, the 2nd argument and all "
179 f
"subsequent arguments of a meta_func entry should "
180 f
"be one of {const.VALID_METAFUNC_NAMES}, but found "
181 f
"'{arg.name}' in '{func_type}'")
184 f
"In the dynamo0.3 API, it is an error to specify an "
185 f
"operator name more than once in a meta_func entry, "
186 f
"but '{arg.name}' is replicated in '{func_type}'")
188 self.
_name_name = func_type.name
192 ''' Returns the name of the descriptors function space '''
197 ''' Returns a list of operators that are associated with this
198 descriptors function space '''
202 return f
"DynFuncDescriptor03({self._func_type})"
205 res =
"DynFuncDescriptor03 object" + os.linesep
206 res += f
" name='{self._name}'" + os.linesep
207 res += f
" nargs={len(self._operator_names)+1}" + os.linesep
208 res += f
" function_space_name[{0}] = '{self._function_space_name}'" \
211 res += f
" operator_name[{idx+1}] = '{arg}'" + \
218 Class responsible for parsing reference-element metadata and storing
219 the properties that a kernel requires.
221 :param str kernel_name: name of the Kernel that the metadata is for.
222 :param type_declns: list of fparser1 parse tree nodes representing type \
223 declaration statements
224 :type type_declns: list of :py:class:`fparser.one.typedecl_statements.Type`
226 :raises ParseError: if an unrecognised reference-element property is found.
227 :raises ParseError: if a duplicate reference-element property is found.
233 Enumeration of the various properties of the Reference Element
234 (that a kernel can request). The names of each of these corresponds to
235 the names that must be used in kernel metadata.
238 NORMALS_TO_HORIZONTAL_FACES = 1
239 NORMALS_TO_VERTICAL_FACES = 2
241 OUTWARD_NORMALS_TO_HORIZONTAL_FACES = 4
242 OUTWARD_NORMALS_TO_VERTICAL_FACES = 5
243 OUTWARD_NORMALS_TO_FACES = 6
245 def __init__(self, kernel_name, type_declns):
252 for line
in type_declns:
253 for entry
in line.selector:
254 if entry ==
"reference_element_data_type":
257 re_properties = getkerneldescriptors(
258 kernel_name, line, var_name=
"meta_reference_element",
259 var_type=
"reference_element_data_type")
270 for re_prop
in re_properties:
271 for arg
in re_prop.args:
272 self.properties.append(
273 self.Property[str(arg).upper()])
274 except KeyError
as err:
277 sorted_names = sorted([prop.name
for prop
in self.Property])
279 f
"Unsupported reference-element property: '{arg}'. Supported "
280 f
"values are: {sorted_names}")
from err
283 for prop
in self.properties:
284 if self.properties.count(prop) > 1:
285 raise ParseError(f
"Duplicate reference-element property "
291 Enumeration of the various properties of the mesh that a kernel may
292 require (either named in metadata or implicitly, depending on the type
299 NCELL_2D_NO_HALOS = 3
304 Parses any mesh-property kernel metadata and stores the properties that
307 :param str kernel_name: name of the kernel that the metadata is for.
308 :param type_declns: list of fparser1 parse tree nodes representing type \
309 declaration statements.
310 :type type_declns: list of :py:class:`fparser.one.typedecl_statements.Type`
312 :raises ParseError: if an unrecognised mesh property is found.
313 :raises ParseError: if a duplicate mesh property is found.
319 supported_properties = [MeshProperty.ADJACENT_FACE]
321 def __init__(self, kernel_name, type_declns):
328 for line
in type_declns:
329 for entry
in line.selector:
330 if entry ==
"mesh_data_type":
333 mesh_props = getkerneldescriptors(
334 kernel_name, line, var_name=
"meta_mesh",
335 var_type=
"mesh_data_type")
346 for prop
in mesh_props:
347 for arg
in prop.args:
348 mesh_prop = MeshProperty[str(arg).upper()]
352 except KeyError
as err:
356 raise ParseError(f
"Unsupported mesh property in metadata: "
357 f
"'{arg}'. Supported values are: "
358 f
"{supported_mesh_prop}")
from err
375 The LFRic-specific PSy class. This creates an LFRic-specific
376 Invokes object (which controls all the required invocation calls).
377 It also overrides the PSy gen method so that we generate
378 LFRic-specific PSy module code.
380 :param invoke_info: object containing the required invocation information \
381 for code optimisation and generation.
382 :type invoke_info: :py:class:`psyclone.parse.algorithm.FileInfo`
385 def __init__(self, invoke_info):
388 ScopingNode._symbol_table_class = LFRicSymbolTable
389 PSy.__init__(self, invoke_info)
395 const_mod = const.UTILITIES_MOD_MAP[
"constants"][
"module"]
396 infmod_list = [const_mod]
401 for data_type_info
in const.DATA_TYPE_MAP.values():
402 infmod_list.append(data_type_info[
"module"])
406 (k, set())
for k
in infmod_list)
412 api_config = Config.get().api_conf(
"dynamo0.3")
413 kind_names.add(api_config.default_kind[
"integer"])
418 for invoke
in self.
invokesinvokes.invoke_list:
419 schedule = invoke.schedule
420 for kernel
in schedule.kernels():
421 for arg
in kernel.args:
423 kind_names.add(arg.precision)
431 :returns: a name for the PSy layer. This is used as the PSy module \
432 name. We override the default value as the Met Office \
433 prefer "_psy" to be appended, rather than prepended.
437 return self.
_name_name +
"_psy"
442 :returns: the unmodified PSy-layer name.
446 return self.
_name_name
451 :returns: the dictionary that holds the names of the required \
452 LFRic infrastructure modules to create "use" \
453 statements in the PSy-layer modules.
462 Generate PSy code for the LFRic (Dynamo0.3) API.
464 :returns: root node of generated Fortran AST.
465 :rtype: :py:class:`psyir.nodes.Node`
473 for routine
in self.
containercontainer.children:
474 if not isinstance(routine, InvokeSchedule):
475 psy_module.add(
PSyIRGen(psy_module, routine))
478 self.
invokesinvokes.gen_code(psy_module)
490 infmod_types = sorted(
492 psy_module.add(
UseGen(psy_module, name=infmod,
493 only=
True, funcnames=infmod_types))
496 return psy_module.root
501 Holds all information on the the mesh properties required by either an
502 invoke or a kernel stub. Note that the creation of a suitable mesh
503 object is handled in the `DynMeshes` class. This class merely deals with
504 extracting the necessary properties from that object and providing them to
507 :param node: kernel or invoke for which to manage mesh properties.
508 :type node: :py:class:`psyclone.domain.lfric.LFRicKern` or \
509 :py:class:`psyclone.dynamo0p3.LFRicInvoke`
512 def __init__(self, node):
513 super().__init__(node)
519 for call
in self.
_calls_calls:
521 self.
_properties_properties += [prop
for prop
in call.mesh.properties
525 if call.iterates_over ==
"domain":
526 if MeshProperty.NCELL_2D_NO_HALOS
not in self.
_properties_properties:
527 self.
_properties_properties.append(MeshProperty.NCELL_2D_NO_HALOS)
530 if call.cma_operation:
531 if MeshProperty.NCELL_2D
not in self.
_properties_properties:
532 self.
_properties_properties.append(MeshProperty.NCELL_2D)
536 name_lower = prop.name.lower()
537 if prop.name
in [
"NCELL_2D",
"NCELL_2D_NO_HALOS"]:
539 self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
540 name_lower, tag=name_lower)
544 name_lower, 2, ScalarType.Intrinsic.INTEGER,
548 kern_call_arg_list=None):
551 Provides the list of kernel arguments associated with the mesh
552 properties that the kernel requires. Optionally adds variable
553 access information if var_accesses is given.
555 :param bool stub: whether or not we are generating code for a \
557 :param var_accesses: optional VariablesAccessInfo instance to store \
558 the information about variable accesses.
559 :type var_accesses: \
560 :py:class:`psyclone.core.VariablesAccessInfo`
561 :param kern_call_arg_list: an optional KernCallArgList instance \
562 used to store PSyIR representation of the arguments.
563 :type kern_call_arg_list: \
564 Optional[:py:class:`psyclone.domain.lfric.KernCallArgList`]
566 :returns: the kernel arguments associated with the mesh properties.
569 :raises InternalError: if the class has been constructed for an \
570 invoke rather than a single kernel call.
571 :raises InternalError: if an unsupported mesh property is encountered.
576 "LFRicMeshProperties.kern_args() can only be called when "
577 "LFRicMeshProperties has been instantiated for a kernel "
578 "rather than an invoke.")
583 if prop == MeshProperty.ADJACENT_FACE:
587 RefElementMetaData.Property.NORMALS_TO_HORIZONTAL_FACES
588 in self.
_kernel_kernel.reference_element.properties
or
590 OUTWARD_NORMALS_TO_HORIZONTAL_FACES
591 in self.
_kernel_kernel.reference_element.properties)
593 if kern_call_arg_list:
594 sym = kern_call_arg_list.\
595 append_integer_reference(
"nfaces_re_h")
599 find_or_create_integer_symbol(
600 "nfaces_re_h", tag=
"nfaces_re_h").name
601 arg_list.append(name)
602 if var_accesses
is not None:
604 AccessType.READ, self.
_kernel_kernel)
606 adj_face =
"adjacent_face"
607 if not stub
and kern_call_arg_list:
611 kern_call_arg_list.cell_ref_name(var_accesses)
612 adj_face_sym = kern_call_arg_list. \
613 append_array_reference(adj_face,
615 ScalarType.Intrinsic.INTEGER)
617 adj_face = adj_face_sym.name
619 var_accesses.add_access(
Signature(adj_face),
620 AccessType.READ, self.
_kernel_kernel,
624 adj_face = self.
_symbol_table_symbol_table.find_or_create_tag(
625 "adjacent_face").name
627 if self.
_kernel_kernel.is_coloured():
628 colour_name =
"colour"
629 cmap_name = self.
_symbol_table_symbol_table.find_or_create_tag(
630 "cmap", root_name=
"cmap").name
631 adj_face += (f
"(:,{cmap_name}({colour_name},"
634 adj_face += f
"(:,{cell_name})"
635 arg_list.append(adj_face)
637 if var_accesses
and not kern_call_arg_list:
641 var_accesses.add_access(
Signature(adj_face),
642 AccessType.READ, self.
_kernel_kernel,
646 f
"kern_args: found unsupported mesh property '{prop}' "
647 f
"when generating arguments for kernel "
648 f
"'{self._kernel.name}'. Only members of the MeshProperty "
649 f
"Enum are permitted ({list(MeshProperty)}).")
653 def _invoke_declarations(self, parent):
655 Creates the necessary declarations for variables needed in order to
656 provide mesh properties to a kernel call.
658 :param parent: node in the f2pygen AST to which to add declarations.
659 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
661 :raises InternalError: if this class has been instantiated for a \
662 kernel instead of an invoke.
663 :raises InternalError: if an unsupported mesh property is found.
666 api_config = Config.get().api_conf(
"dynamo0.3")
670 "_invoke_declarations() cannot be called because "
671 "LFRicMeshProperties has been instantiated for a kernel and "
677 if prop == MeshProperty.ADJACENT_FACE:
678 adj_face = self.
_symbol_table_symbol_table.find_or_create_tag(
679 "adjacent_face").name +
"(:,:) => null()"
680 parent.add(
DeclGen(parent, datatype=
"integer",
681 kind=api_config.default_kind[
"integer"],
682 pointer=
True, entity_decls=[adj_face]))
683 elif prop == MeshProperty.NCELL_2D_NO_HALOS:
684 name = self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
686 tag=
"ncell_2d_no_halos").name
687 parent.add(
DeclGen(parent, datatype=
"integer",
688 kind=api_config.default_kind[
"integer"],
689 entity_decls=[name]))
690 elif prop == MeshProperty.NCELL_2D:
691 name = self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
692 "ncell_2d", tag=
"ncell_2d").name
693 parent.add(
DeclGen(parent, datatype=
"integer",
694 kind=api_config.default_kind[
"integer"],
695 entity_decls=[name]))
698 f
"Found unsupported mesh property '{prop}' when generating"
699 f
" invoke declarations. Only members of the MeshProperty "
700 f
"Enum are permitted ({list(MeshProperty)}).")
702 def _stub_declarations(self, parent):
704 Creates the necessary declarations for the variables needed in order
705 to provide properties of the mesh in a kernel stub.
707 :param parent: node in the f2pygen AST to which to add declarations.
708 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
710 :raises InternalError: if the class has been instantiated for an \
711 invoke and not a kernel.
712 :raises InternalError: if an unsupported mesh property is encountered.
715 api_config = Config.get().api_conf(
"dynamo0.3")
719 "_stub_declarations() cannot be called because "
720 "LFRicMeshProperties has been instantiated for an invoke and "
724 if prop == MeshProperty.ADJACENT_FACE:
725 adj_face = self.
_symbol_table_symbol_table.find_or_create_array(
726 "adjacent_face", 2, ScalarType.Intrinsic.INTEGER,
727 tag=
"adjacent_face").name
731 find_or_create_integer_symbol(
"nfaces_re_h",
732 tag=
"nfaces_re_h").name
735 parent, datatype=
"integer",
736 kind=api_config.default_kind[
"integer"],
738 intent=
"in", entity_decls=[adj_face]))
739 elif prop == MeshProperty.NCELL_2D:
740 ncell_2d = self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
741 "ncell_2d", tag=
"ncell_2d")
743 DeclGen(parent, datatype=
"integer",
744 kind=api_config.default_kind[
"integer"],
745 intent=
"in", entity_decls=[ncell_2d.name]))
748 f
"Found unsupported mesh property '{prop}' when generating"
749 f
" declarations for kernel stub. Only members of the "
750 f
"MeshProperty Enum are permitted ({list(MeshProperty)})")
754 Creates the f2pygen nodes for the initialisation of properties of
757 :param parent: node in the f2pygen tree to which to add statements.
758 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
760 :raises InternalError: if an unsupported mesh property is encountered.
766 need_colour_limits =
False
767 need_colour_halo_limits =
False
768 for call
in self.
_calls_calls:
769 if call.is_coloured()
and not call.is_intergrid:
770 loop = call.parent.parent
772 if loop.upper_bound_name
in const.HALO_ACCESS_LOOP_BOUNDS:
773 need_colour_halo_limits =
True
775 need_colour_limits =
True
777 if not self.
_properties_properties
and not (need_colour_limits
or
778 need_colour_halo_limits):
785 parent.add(
CommentGen(parent,
" Initialise mesh properties"))
788 mesh = self.
_symbol_table_symbol_table.find_or_create_tag(
"mesh").name
791 if prop == MeshProperty.ADJACENT_FACE:
792 adj_face = self.
_symbol_table_symbol_table.find_or_create_tag(
793 "adjacent_face").name
794 parent.add(
AssignGen(parent, pointer=
True, lhs=adj_face,
795 rhs=mesh+
"%get_adjacent_face()"))
797 elif prop == MeshProperty.NCELL_2D_NO_HALOS:
798 name = self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
799 "ncell_2d_no_halos", tag=
"ncell_2d_no_halos").name
801 rhs=mesh+
"%get_last_edge_cell()"))
803 elif prop == MeshProperty.NCELL_2D:
804 name = self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
805 "ncell_2d", tag=
"ncell_2d").name
807 rhs=mesh+
"%get_ncells_2d()"))
810 f
"Found unsupported mesh property '{str(prop)}' when "
811 f
"generating initialisation code. Only members of the "
812 f
"MeshProperty Enum are permitted ({list(MeshProperty)})")
814 if need_colour_halo_limits:
816 "last_halo_cell_all_colours").name
817 rhs = f
"{mesh}%get_last_halo_cell_all_colours()"
818 parent.add(
AssignGen(parent, lhs=lhs, rhs=rhs))
819 if need_colour_limits:
821 "last_edge_cell_all_colours").name
822 rhs = f
"{mesh}%get_last_edge_cell_all_colours()"
823 parent.add(
AssignGen(parent, lhs=lhs, rhs=rhs))
828 Holds all information on the properties of the Reference Element
829 required by an Invoke or a Kernel stub.
831 :param node: Kernel or Invoke for which to manage Reference-Element \
833 :type node: :py:class:`psyclone.domain.lfric.LFRicKern` or \
834 :py:class:`psyclone.dynamo0p3.LFRicInvoke`
836 :raises InternalError: if an unsupported reference-element property \
841 def __init__(self, node):
843 super().__init__(node)
852 for call
in self.
_calls_calls:
853 if call.reference_element:
854 self.
_properties_properties.extend(call.reference_element.properties)
855 if call.mesh
and call.mesh.properties:
871 symtab.find_or_create_tag(
"reference_element").name
894 if (RefElementMetaData.Property.NORMALS_TO_HORIZONTAL_FACES
896 RefElementMetaData.Property.OUTWARD_NORMALS_TO_HORIZONTAL_FACES
899 self.
_nfaces_h_symbol_nfaces_h_symbol = symtab.find_or_create_integer_symbol(
900 "nfaces_re_h", tag=
"nfaces_re_h")
902 if (RefElementMetaData.Property.NORMALS_TO_VERTICAL_FACES
904 RefElementMetaData.Property.OUTWARD_NORMALS_TO_VERTICAL_FACES
906 self.
_nfaces_v_symbol_nfaces_v_symbol = symtab.find_or_create_integer_symbol(
907 "nfaces_re_v", tag=
"nfaces_re_v")
909 if (RefElementMetaData.Property.NORMALS_TO_FACES
911 RefElementMetaData.Property.OUTWARD_NORMALS_TO_FACES
913 self.
_nfaces_symbol_nfaces_symbol = symtab.find_or_create_integer_symbol(
914 "nfaces_re", tag=
"nfaces_re")
920 if prop == RefElementMetaData.Property.NORMALS_TO_HORIZONTAL_FACES:
921 name =
"normals_to_horiz_faces"
923 symtab.find_or_create_array(name, 2,
924 ScalarType.Intrinsic.REAL,
931 OUTWARD_NORMALS_TO_HORIZONTAL_FACES):
932 name =
"out_normals_to_horiz_faces"
934 symtab.find_or_create_array(name, 2,
935 ScalarType.Intrinsic.REAL,
942 NORMALS_TO_VERTICAL_FACES):
943 name =
"normals_to_vert_faces"
945 symtab.find_or_create_array(name, 2,
946 ScalarType.Intrinsic.REAL,
953 OUTWARD_NORMALS_TO_VERTICAL_FACES):
954 name =
"out_normals_to_vert_faces"
956 symtab.find_or_create_array(name, 2,
957 ScalarType.Intrinsic.REAL,
964 elif prop == RefElementMetaData.Property.NORMALS_TO_FACES:
965 name =
"normals_to_faces"
967 symtab.find_or_create_array(name, 2,
968 ScalarType.Intrinsic.REAL,
974 elif prop == RefElementMetaData.Property.OUTWARD_NORMALS_TO_FACES:
975 name =
"out_normals_to_faces"
977 symtab.find_or_create_array(name, 2,
978 ScalarType.Intrinsic.REAL,
985 all_props = [str(sprop)
988 f
"Unsupported reference-element property ('{prop}') "
989 f
"found when generating arguments for kernel "
990 f
"'{self._kernel.name}'. Supported properties are: "
995 :returns: the argument list for kernel call/stub arguments.
1001 nfaces = list(OrderedDict.fromkeys(argdict.values()))
1002 kern_args = nfaces + list(argdict.keys())
1003 return [sym.name
for sym
in kern_args]
1007 :returns: the argument symbol list for kernel call/stub arguments.
1008 :rtype: List[:py:class:`psyclone.psyir.symbols.Symbol`]
1013 nfaces = list(OrderedDict.fromkeys(argdict.values()))
1014 return nfaces + list(argdict.keys())
1016 def _invoke_declarations(self, parent):
1018 Create the necessary declarations for the variables needed in order
1019 to provide properties of the reference element in a Kernel call.
1021 :param parent: node in the f2pygen AST to which to add declarations.
1022 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1028 nface_vars = list(OrderedDict.fromkeys(
1037 api_config = Config.get().api_conf(
"dynamo0.3")
1040 refelem_type = const.REFELEMENT_TYPE_MAP[
"refelement"][
"type"]
1041 refelem_mod = const.REFELEMENT_TYPE_MAP[
"refelement"][
"module"]
1042 parent.add(
UseGen(parent, name=refelem_mod, only=
True,
1043 funcnames=[refelem_type]))
1046 datatype=refelem_type,
1047 entity_decls=[self.
_ref_elem_name_ref_elem_name +
" => null()"]))
1049 parent.add(
DeclGen(parent, datatype=
"integer",
1050 kind=api_config.default_kind[
"integer"],
1051 entity_decls=[var.name
for var
in nface_vars]))
1058 array_decls = [f
"{sym.name}(:,:)"
1060 my_kind = api_config.default_kind[
"real"]
1061 parent.add(
DeclGen(parent, datatype=
"real", kind=my_kind,
1062 allocatable=
True, entity_decls=array_decls))
1064 const_mod = const.UTILITIES_MOD_MAP[
"constants"][
"module"]
1065 const_mod_uses = self.
_invoke_invoke.invokes.psy.infrastructure_modules[
1067 const_mod_uses.add(my_kind)
1069 def _stub_declarations(self, parent):
1071 Create the necessary declarations for the variables needed in order
1072 to provide properties of the reference element in a Kernel stub.
1074 :param parent: node in the f2pygen AST to which to add declarations.
1075 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1078 api_config = Config.get().api_conf(
"dynamo0.3")
1085 nfaces_h = self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
1086 "nfaces_re_h", tag=
"nfaces_re_h")
1088 scalars.append(nfaces_h)
1090 for nface
in scalars:
1091 parent.add(
DeclGen(parent, datatype=
"integer",
1092 kind=api_config.default_kind[
"integer"],
1093 intent=
"in", entity_decls=[nface.name]))
1097 dimension = f
"3,{sym.name}"
1098 parent.add(
DeclGen(parent, datatype=
"real",
1099 kind=api_config.default_kind[
"real"],
1100 intent=
"in", dimension=dimension,
1101 entity_decls=[arr.name]))
1105 Creates the f2pygen nodes representing the necessary initialisation
1106 code for properties of the reference element.
1108 :param parent: node in the f2pygen tree to which to add statements.
1109 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1118 " Get the reference element and query its properties"))
1121 mesh_obj_name = self.
_symbol_table_symbol_table.find_or_create_tag(
"mesh").name
1123 rhs=mesh_obj_name+
"%get_reference_element()"))
1129 "%get_number_horizontal_faces()"))
1134 rhs=self.
_ref_elem_name_ref_elem_name +
"%get_number_vertical_faces()"))
1145 name=f
"{self._ref_elem_name}%get_normals_to_"
1146 f
"horizontal_faces("
1147 f
"{self._horiz_face_normals_symbol.name})"))
1153 name=f
"{self._ref_elem_name}%get_outward_normals_to_"
1154 f
"horizontal_faces("
1155 f
"{self._horiz_face_out_normals_symbol.name})"))
1160 name=f
"{self._ref_elem_name}%get_normals_to_vertical_"
1161 f
"faces({self._vert_face_normals_symbol.name})"))
1167 name=f
"{self._ref_elem_name}%get_outward_normals_to_"
1169 f
"({self._vert_face_out_normals_symbol.name})"))
1174 name=f
"{self._ref_elem_name}%get_normals_to_faces"
1175 f
"({self._face_normals_symbol.name})"))
1181 name=f
"{self._ref_elem_name}%get_outward_normals_to_"
1182 f
"faces({self._face_out_normals_symbol.name})"))
1187 Handles the declaration and initialisation of all function-space-related
1188 quantities required by an Invoke.
1190 :param invoke: the Invoke or Kernel object.
1192 def __init__(self, kern_or_invoke):
1193 super().__init__(kern_or_invoke)
1209 self.
_kernel_kernel
and self.
_kernel_kernel.cma_operation !=
"matrix-matrix":
1210 self.
_var_list_var_list.append(function_space.ndf_name)
1217 if self.
_invoke_invoke
and self.
_invoke_invoke.field_on_space(function_space):
1218 if not (self.
_dofs_only_dofs_only
and Config.get().distributed_memory):
1219 self.
_var_list_var_list.append(function_space.undf_name)
1220 elif self.
_kernel_kernel
and \
1221 function_space.field_on_space(self.
_kernel_kernel.arguments):
1222 self.
_var_list_var_list.append(function_space.undf_name)
1224 def _stub_declarations(self, parent):
1226 Add function-space-related declarations to a Kernel stub.
1228 :param parent: the node in the f2pygen AST representing the kernel \
1229 stub to which to add declarations.
1230 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1233 api_config = Config.get().api_conf(
"dynamo0.3")
1237 parent.add(
DeclGen(parent, datatype=
"integer",
1238 kind=api_config.default_kind[
"integer"],
1239 intent=
"in", entity_decls=self.
_var_list_var_list))
1241 def _invoke_declarations(self, parent):
1243 Add function-space-related declarations to a PSy-layer routine.
1245 :param parent: the node in the f2pygen AST to which to add \
1247 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1250 api_config = Config.get().api_conf(
"dynamo0.3")
1254 parent.add(
DeclGen(parent, datatype=
"integer",
1255 kind=api_config.default_kind[
"integer"],
1260 Create the code that initialises function-space quantities.
1262 :param parent: the node in the f2pygen AST representing the PSy-layer \
1264 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1275 if not (self.
_dofs_only_dofs_only
and Config.get().distributed_memory):
1278 " Initialise number of DoFs for " +
1279 function_space.mangled_name))
1283 arg = self.
_invoke_invoke.arg_for_funcspace(function_space)
1284 name = arg.proxy_name_indexed
1287 ndf_name = function_space.ndf_name
1288 parent.add(
AssignGen(parent, lhs=ndf_name,
1290 "%" + arg.ref_name(function_space) +
1297 if not (self.
_dofs_only_dofs_only
and Config.get().distributed_memory):
1298 if self.
_invoke_invoke.field_on_space(function_space):
1299 undf_name = function_space.undf_name
1300 parent.add(
AssignGen(parent, lhs=undf_name,
1302 arg.ref_name(function_space) +
1308 Handles all proxy-related declarations and initialisation. Unlike other
1309 sub-classes of LFRicCollection, we do not have to handle Kernel-stub
1310 generation since Kernels know nothing about proxies.
1312 An instance of this class is instantiated for each Invoke before the
1313 PSy Layer is constructed. For each unique field or operator argument to
1314 a kernel in the Invoke it:
1316 * Creates a DataSymbol for the corresponding proxy;
1317 * Creates a DataSymbol for the pointer to the data array accessed via
1318 the proxy. If the argument is a field vector then a DataSymbol is
1319 created for each component of the vector;
1320 * Tags that DataSymbol so that the correct symbol can always be looked
1321 up, irrespective of any name clashes;
1323 Note that since the Fortran standard forbids (Note 12.34 in the
1324 Fortran2008 standard) aliasing of effective arguments that are written to,
1325 the set of unique kernel arguments must refer to unique memory locations
1326 or to those that are read only.
1329 def __init__(self, node):
1330 super().__init__(node)
1332 real_field_args = self.
_invoke_invoke.unique_declarations(
1333 argument_types=const.VALID_FIELD_NAMES,
1334 intrinsic_type=const.MAPPING_DATA_TYPES[
"gh_real"])
1335 int_field_args = self.
_invoke_invoke.unique_declarations(
1336 argument_types=const.VALID_FIELD_NAMES,
1337 intrinsic_type=const.MAPPING_DATA_TYPES[
"gh_integer"])
1338 op_args = self.
_invoke_invoke.unique_declarations(
1339 argument_types=const.VALID_OPERATOR_NAMES)
1342 ctable = self.
_invoke_invoke.schedule.parent.symbol_table
1344 for arg
in real_field_args + int_field_args + op_args:
1347 if arg.argument_type ==
"gh_columnwise_operator":
1350 ctable.add_lfric_precision_symbol(arg.precision)
1351 intrinsic_type =
"integer" if arg
in int_field_args
else "real"
1352 suffix = const.ARG_TYPE_SUFFIX_MAPPING[arg.argument_type]
1353 if arg.vector_size > 1:
1354 for idx
in range(1, arg.vector_size+1):
1357 new_name = self.
_symbol_table_symbol_table.next_available_name(
1358 f
"{arg.name}_{idx}_{suffix}")
1359 tag = f
"{arg.name}_{idx}:{suffix}"
1361 self.
_add_symbol_add_symbol(new_name, tag, intrinsic_type, arg, 1)
1366 new_name = self.
_symbol_table_symbol_table.next_available_name(
1367 f
"{arg.name}_{suffix}")
1368 tag = f
"{arg.name}:{suffix}"
1370 rank = 1
if arg
not in op_args
else 3
1371 self.
_add_symbol_add_symbol(new_name, tag, intrinsic_type, arg, rank)
1373 def _add_symbol(self, name, tag, intrinsic_type, arg, rank):
1375 Creates a new DataSymbol representing either an LFRic field or
1376 operator and adds it to the SymbolTable associated with this class.
1377 The Symbol is of UnsupportedFortranType because it is a pointer
1378 to the internal data array and the PSyIR does not support pointers. The
1379 remainder of the type information is fully supplied in the
1380 `partial_datatype` property of the UnsupportedFortranType.
1381 The supplied Symbol name is assumed not to already exist in the
1382 SymbolTable (e.g. it is obtained with the `next_available_name` method
1383 of SymbolTable) because it is used in constructing the
1384 UnsupportedFortranType which must be done before the Symbol is created.
1386 :param str name: the name of the new Symbol.
1387 :param str tag: the tag to associate with the new Symbol.
1388 :param str intrinsic_type: whether the Symbol represents "real" or
1390 :param arg: the metadata description of the associated kernel argument.
1391 :type arg: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
1392 :param int rank: the rank of the array represented by the Symbol.
1395 if intrinsic_type ==
"real":
1396 lfric_type =
"LFRicRealScalarDataType"
1398 lfric_type =
"LFRicIntegerScalarDataType"
1400 array_type = ArrayType(
1402 [ArrayType.Extent.DEFERRED]*rank)
1406 index_str =
",".join(rank*[
":"])
1407 dtype = UnsupportedFortranType(
1408 f
"{intrinsic_type}(kind={arg.precision}), pointer, "
1409 f
"dimension({index_str}) :: {name} => null()",
1410 partial_datatype=array_type)
1413 symbol_type=DataSymbol,
1427 def _invoke_declarations(self, parent):
1429 Insert declarations of all proxy-related quantities into the PSy layer.
1431 :param parent: the node in the f2pygen AST representing the PSy- \
1433 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1437 const_mod = const.UTILITIES_MOD_MAP[
"constants"][
"module"]
1443 real_field_args = self.
_invoke_invoke.unique_declarations(
1444 argument_types=const.VALID_FIELD_NAMES,
1445 intrinsic_type=const.MAPPING_DATA_TYPES[
"gh_real"])
1446 int_field_args = self.
_invoke_invoke.unique_declarations(
1447 argument_types=const.VALID_FIELD_NAMES,
1448 intrinsic_type=const.MAPPING_DATA_TYPES[
"gh_integer"])
1453 field_datatype_map = OrderedDict()
1454 for arg
in real_field_args + int_field_args:
1457 (arg.proxy_data_type, arg.module_name)].append(arg)
1462 (arg.proxy_data_type, arg.module_name)] = [arg]
1466 for (fld_type, fld_mod), args
in field_datatype_map.items():
1467 arg_list = [arg.proxy_declaration_name
for arg
in args]
1469 entity_decls=arg_list))
1470 (self.
_invoke_invoke.invokes.psy.
1471 infrastructure_modules[fld_mod].add(fld_type))
1476 (self.
_invoke_invoke.invokes.psy.infrastructure_modules[const_mod].
1478 suffix = const.ARG_TYPE_SUFFIX_MAPPING[arg.argument_type]
1479 if arg.vector_size > 1:
1481 for idx
in range(1, arg.vector_size+1):
1482 ttext = f
"{arg.name}_{idx}:{suffix}"
1483 vsym = table.lookup_with_tag(ttext)
1484 entity_names.append(vsym.name)
1486 ttext = f
"{arg.name}:{suffix}"
1487 sym = table.lookup_with_tag(ttext)
1488 entity_names = [sym.name]
1492 parent, datatype=arg.intrinsic_type,
1493 kind=arg.precision, dimension=
":",
1494 entity_decls=[f
"{name} => null()" for
1495 name
in entity_names],
1499 op_args = self.
_invoke_invoke.unique_declarations(
1500 argument_types=[
"gh_operator"])
1502 operators_datatype_map = OrderedDict()
1503 for op_arg
in op_args:
1505 operators_datatype_map[op_arg.proxy_data_type].append(op_arg)
1509 operators_datatype_map[op_arg.proxy_data_type] = [op_arg]
1511 for operator_datatype, operators_list
in \
1512 operators_datatype_map.items():
1513 operators_names = [arg.proxy_declaration_name
for
1514 arg
in operators_list]
1515 parent.add(
TypeDeclGen(parent, datatype=operator_datatype,
1516 entity_decls=operators_names))
1517 for arg
in operators_list:
1519 suffix = const.ARG_TYPE_SUFFIX_MAPPING[arg.argument_type]
1520 ttext = f
"{name}:{suffix}"
1521 sym = table.lookup_with_tag(ttext)
1523 parent.add(
DeclGen(parent, datatype=
"real",
1526 entity_decls=[f
"{sym.name} => null()"],
1528 op_mod = operators_list[0].module_name
1530 (self.
_invoke_invoke.invokes.psy.infrastructure_modules[op_mod].
1531 add(operator_datatype))
1533 (self.
_invoke_invoke.invokes.psy.infrastructure_modules[const_mod].
1537 cma_op_args = self.
_invoke_invoke.unique_declarations(
1538 argument_types=[
"gh_columnwise_operator"])
1539 cma_op_proxy_decs = [arg.proxy_declaration_name
for
1541 if cma_op_proxy_decs:
1542 op_type = cma_op_args[0].proxy_data_type
1543 op_mod = cma_op_args[0].module_name
1546 entity_decls=cma_op_proxy_decs))
1547 (self.
_invoke_invoke.invokes.psy.infrastructure_modules[op_mod].
1552 Insert code into the PSy layer to initialise all necessary proxies.
1554 :param parent: node in the f2pygen AST representing the PSy-layer
1556 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1558 :raises InternalError: if a kernel argument of an unrecognised type
1564 " Initialise field and/or operator proxies"))
1566 for arg
in self.
_invoke_invoke.psy_unique_vars:
1572 suffix = const.ARG_TYPE_SUFFIX_MAPPING[arg.argument_type]
1574 if arg.vector_size > 1:
1578 for idx
in range(1, arg.vector_size+1):
1581 lhs=arg.proxy_name+
"("+str(idx)+
")",
1582 rhs=arg.name+
"("+str(idx)+
")%get_proxy()"))
1584 f
"{arg.name}_{idx}:{suffix}").name
1588 rhs=f
"{arg.proxy_name}({idx})%data",
1591 parent.add(
AssignGen(parent, lhs=arg.proxy_name,
1592 rhs=arg.name+
"%get_proxy()"))
1595 f
"{arg.name}:{suffix}").name
1599 rhs=f
"{arg.proxy_name}%data",
1601 elif arg.is_operator:
1602 if arg.argument_type ==
"gh_columnwise_operator":
1605 elif arg.argument_type ==
"gh_operator":
1607 f
"{arg.name}:{suffix}").name
1611 rhs=f
"{arg.proxy_name}%local_stencil",
1615 f
"Kernel argument '{arg.name}' is a recognised "
1616 f
"operator but its type ('{arg.argument_type}') is"
1617 f
" not supported by DynProxies.initialise()")
1620 f
"Kernel argument '{arg.name}' of type "
1621 f
"'{arg.argument_type}' not "
1622 f
"handled in DynProxies.initialise()")
1627 Handles all entities required by kernels that operate on cell-columns.
1629 :param kern_or_invoke: the Kernel or Invoke for which to manage cell \
1631 :type kern_or_invoke: :py:class:`psyclone.domain.lfric.LFRicKern` or \
1632 :py:class:`psyclone.dynamo0p3.LFRicInvoke`
1634 : raises GenerationError: if an Invoke has no field or operator arguments.
1637 def __init__(self, kern_or_invoke):
1638 super().__init__(kern_or_invoke)
1641 "nlayers", symbol_type=
LFRicTypes(
"MeshHeightDataSymbol")).name
1649 for var
in self.
_invoke_invoke.psy_unique_vars:
1650 if not var.is_scalar:
1655 "Cannot create an Invoke with no field/operator arguments.")
1658 def _invoke_declarations(self, parent):
1660 Declare entities required for iterating over cells in the Invoke.
1662 :param parent: the f2pygen node representing the PSy-layer routine.
1663 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1666 api_config = Config.get().api_conf(
"dynamo0.3")
1671 parent.add(
DeclGen(parent, datatype=
"integer",
1672 kind=api_config.default_kind[
"integer"],
1675 def _stub_declarations(self, parent):
1677 Declare entities required for a kernel stub that operates on
1680 :param parent: the f2pygen node representing the Kernel stub.
1681 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1684 api_config = Config.get().api_conf(
"dynamo0.3")
1686 if self.
_kernel_kernel.cma_operation
not in [
"apply",
"matrix-matrix"]:
1687 parent.add(
DeclGen(parent, datatype=
"integer",
1688 kind=api_config.default_kind[
"integer"],
1689 intent=
"in", entity_decls=[self.
_nlayers_name_nlayers_name]))
1693 Look-up the number of vertical layers in the mesh in the PSy layer.
1695 :param parent: the f2pygen node representing the PSy-layer routine.
1696 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1701 parent.add(
CommentGen(parent,
" Initialise number of layers"))
1705 rhs=self.
_first_var_first_var.proxy_name_indexed +
"%" +
1706 self.
_first_var_first_var.ref_name() +
"%get_nlayers()"))
1711 Handles all entities associated with Local-Matrix-Assembly Operators.
1713 def _stub_declarations(self, parent):
1715 Declare all LMA-related quantities in a Kernel stub.
1717 :param parent: the f2pygen node representing the Kernel stub.
1718 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1721 api_config = Config.get().api_conf(
"dynamo0.3")
1723 lma_args = psyGen.args_filter(
1724 self.
_kernel_kernel.arguments.args, arg_types=[
"gh_operator"])
1726 parent.add(
DeclGen(parent, datatype=
"integer",
1727 kind=api_config.default_kind[
"integer"],
1728 intent=
"in", entity_decls=[
"cell"]))
1729 for arg
in lma_args:
1730 size = arg.name+
"_ncell_3d"
1731 op_dtype = arg.intrinsic_type
1732 op_kind = arg.precision
1733 parent.add(
DeclGen(parent, datatype=
"integer",
1734 kind=api_config.default_kind[
"integer"],
1735 intent=
"in", entity_decls=[size]))
1736 ndf_name_to = arg.function_space_to.ndf_name
1737 ndf_name_from = arg.function_space_from.ndf_name
1738 parent.add(
DeclGen(parent, datatype=op_dtype, kind=op_kind,
1739 dimension=
",".join([ndf_name_to,
1740 ndf_name_from, size]),
1742 entity_decls=[arg.name]))
1744 def _invoke_declarations(self, parent):
1746 Declare all LMA-related quantities in a PSy-layer routine.
1747 Note: PSy layer in LFRic does not modify the LMA operator objects.
1748 Hence, their Fortran intents are always "in" (the data updated in the
1749 kernels is only pointed to from the LMA operator object and is thus
1750 not a part of the object).
1752 :param parent: the f2pygen node representing the PSy-layer routine.
1753 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1757 op_args = self.
_invoke_invoke.unique_declarations(
1758 argument_types=[
"gh_operator"])
1760 operators_datatype_map = OrderedDict()
1761 for op_arg
in op_args:
1763 operators_datatype_map[op_arg.data_type].append(op_arg)
1766 operators_datatype_map[op_arg.data_type] = [op_arg]
1768 for op_datatype, op_list
in operators_datatype_map.items():
1769 operators_names = [arg.declaration_name
for arg
in op_list]
1771 parent, datatype=op_datatype,
1772 entity_decls=operators_names, intent=
"in"))
1773 op_mod = op_list[0].module_name
1776 (self.
_invoke_invoke.invokes.psy.infrastructure_modules[op_mod].
1782 Holds all information on the Column-Matrix-Assembly operators
1783 required by an Invoke or Kernel stub.
1785 :param node: either an Invoke schedule or a single Kernel object.
1786 :type node: :py:class:`psyclone.dynamo0p3.DynSchedule` or \
1787 :py:class:`psyclone.domain.lfric.LFRicKern`
1792 cma_same_fs_params = [
"nrow",
"bandwidth",
"alpha",
1793 "beta",
"gamma_m",
"gamma_p"]
1796 cma_diff_fs_params = [
"nrow",
"ncol",
"bandwidth",
"alpha",
1797 "beta",
"gamma_m",
"gamma_p"]
1799 def __init__(self, node):
1800 super().__init__(node)
1811 self.
_cma_ops_cma_ops = OrderedDict()
1815 for call
in self.
_calls_calls:
1816 if call.cma_operation:
1818 cma_args = psyGen.args_filter(
1819 call.arguments.args,
1820 arg_types=[
"gh_columnwise_operator"])
1823 for arg
in cma_args:
1824 if arg.name
not in self.
_cma_ops_cma_ops:
1825 if arg.function_space_to.orig_name != \
1826 arg.function_space_from.orig_name:
1827 self.
_cma_ops_cma_ops[arg.name] = {
1831 self.
_cma_ops_cma_ops[arg.name] = {
1834 self.
_cma_ops_cma_ops[arg.name][
"intent"] = arg.intent
1835 self.
_cma_ops_cma_ops[arg.name][
"datatype"] = \
1837 self.
_cma_ops_cma_ops[arg.name][
"kind"] = arg.precision
1846 suffix = const.ARG_TYPE_SUFFIX_MAPPING[
"gh_columnwise_operator"]
1847 for op_name
in self.
_cma_ops_cma_ops:
1848 new_name = self.
_symbol_table_symbol_table.next_available_name(
1849 f
"{op_name}_{suffix}")
1850 tag = f
"{op_name}:{suffix}"
1851 arg = self.
_cma_ops_cma_ops[op_name][
"arg"]
1853 array_type = ArrayType(
1854 LFRicTypes(
"LFRicRealScalarDataType")(precision),
1855 [ArrayType.Extent.DEFERRED]*3)
1856 index_str =
",".join(3*[
":"])
1857 dtype = UnsupportedFortranType(
1858 f
"real(kind={arg.precision}), pointer, "
1859 f
"dimension({index_str}) :: {new_name} => null()",
1860 partial_datatype=array_type)
1861 symtab.new_symbol(new_name,
1862 symbol_type=DataSymbol,
1866 for param
in self.
_cma_ops_cma_ops[op_name][
"params"]:
1867 symtab.find_or_create_integer_symbol(
1868 f
"{op_name}_{param}", tag=f
"{op_name}:{param}:{suffix}")
1872 Generates the calls to the LFRic infrastructure that look-up
1873 the various components of each CMA operator. Adds these as
1874 children of the supplied parent node.
1876 :param parent: f2pygen node representing the PSy-layer routine.
1877 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1886 " Look-up information for each CMA operator"))
1890 suffix = const.ARG_TYPE_SUFFIX_MAPPING[
"gh_columnwise_operator"]
1892 for op_name
in self.
_cma_ops_cma_ops:
1896 f
"{op_name}:{suffix}").name
1897 parent.add(
AssignGen(parent, lhs=cma_name, pointer=
True,
1898 rhs=self.
_cma_ops_cma_ops[op_name][
"arg"].
1899 proxy_name_indexed+
"%columnwise_matrix"))
1901 for param
in self.
_cma_ops_cma_ops[op_name][
"params"]:
1902 param_name = self.
_symbol_table_symbol_table.find_or_create_tag(
1903 f
"{op_name}:{param}:{suffix}").name
1904 parent.add(
AssignGen(parent, lhs=param_name,
1905 rhs=self.
_cma_ops_cma_ops[op_name][
"arg"].
1906 proxy_name_indexed+
"%"+param))
1908 def _invoke_declarations(self, parent):
1910 Generate the necessary PSy-layer declarations for all column-wise
1911 operators and their associated parameters.
1912 Note: PSy layer in LFRic does not modify the CMA operator objects.
1913 Hence, their Fortran intents are always "in" (the data updated in the
1914 kernels is only pointed to from the column-wise operator object and is
1915 thus not a part of the object).
1917 :param parent: the f2pygen node representing the PSy-layer routine.
1918 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1921 api_config = Config.get().api_conf(
"dynamo0.3")
1929 cma_op_args = self.
_invoke_invoke.unique_declarations(
1930 argument_types=[
"gh_columnwise_operator"])
1932 cma_op_arg_list = [arg.declaration_name
for arg
in cma_op_args]
1934 op_type = cma_op_args[0].data_type
1935 op_mod = cma_op_args[0].module_name
1938 entity_decls=cma_op_arg_list,
1940 (self.
_invoke_invoke.invokes.psy.infrastructure_modules[op_mod].
1944 suffix = const.ARG_TYPE_SUFFIX_MAPPING[
"gh_columnwise_operator"]
1945 for op_name
in self.
_cma_ops_cma_ops:
1947 tag_name = f
"{op_name}:{suffix}"
1948 cma_name = self.
_symbol_table_symbol_table.lookup_with_tag(tag_name).name
1949 cma_dtype = self.
_cma_ops_cma_ops[op_name][
"datatype"]
1950 cma_kind = self.
_cma_ops_cma_ops[op_name][
"kind"]
1951 parent.add(
DeclGen(parent, datatype=cma_dtype,
1952 kind=cma_kind, pointer=
True,
1954 entity_decls=[f
"{cma_name} => null()"]))
1956 const_mod = const.UTILITIES_MOD_MAP[
"constants"][
"module"]
1957 const_mod_uses = self.
_invoke_invoke.invokes.psy. \
1958 infrastructure_modules[const_mod]
1962 const_mod_uses.add(cma_kind)
1966 for param
in self.
_cma_ops_cma_ops[op_name][
"params"]:
1967 name = f
"{op_name}_{param}"
1968 tag = f
"{op_name}:{param}:{suffix}"
1969 sym = self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
1971 param_names.append(sym.name)
1972 parent.add(
DeclGen(parent, datatype=
"integer",
1973 kind=api_config.default_kind[
"integer"],
1974 entity_decls=param_names))
1976 def _stub_declarations(self, parent):
1978 Generate all necessary declarations for CMA operators being passed to
1981 :param parent: f2pygen node representing the Kernel stub.
1982 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
1985 api_config = Config.get().api_conf(
"dynamo0.3")
1995 parent.add(
DeclGen(parent, datatype=
"integer",
1996 kind=api_config.default_kind[
"integer"],
1997 intent=
"in", entity_decls=[
"cell",
"ncell_2d"]))
2000 suffix = const.ARG_TYPE_SUFFIX_MAPPING[
"gh_columnwise_operator"]
2002 for op_name
in self.
_cma_ops_cma_ops:
2007 for param
in self.
_cma_ops_cma_ops[op_name][
"params"]:
2008 param_name = symtab.find_or_create_tag(
2009 f
"{op_name}:{param}:{suffix}",
2010 root_name=f
"{op_name}_{param}").name
2011 _local_args.append(param_name)
2012 parent.add(
DeclGen(parent, datatype=
"integer",
2013 kind=api_config.default_kind[
"integer"],
2014 intent=
"in", entity_decls=_local_args))
2016 bandwidth = symtab.find_or_create_tag(
2017 f
"{op_name}:bandwidth:{suffix}",
2018 root_name=f
"{op_name}_bandwidth").name
2019 nrow = symtab.find_or_create_tag(
2020 f
"{op_name}:nrow:{suffix}",
2021 root_name=f
"{op_name}_nrow").name
2022 intent = self.
_cma_ops_cma_ops[op_name][
"intent"]
2023 op_dtype = self.
_cma_ops_cma_ops[op_name][
"datatype"]
2024 op_kind = self.
_cma_ops_cma_ops[op_name][
"kind"]
2025 parent.add(
DeclGen(parent, datatype=op_dtype, kind=op_kind,
2026 dimension=
",".join([bandwidth,
2028 intent=intent, entity_decls=[op_name]))
2033 Holds all mesh-related information (including colour maps if
2034 required). If there are no inter-grid kernels then there is only
2035 one mesh object required (when calling kernels with operates_on==domain,
2036 colouring, doing distributed memory or querying the reference element).
2037 However, kernels performing inter-grid operations require multiple mesh
2038 objects as well as mesh maps and other quantities.
2040 There are two types of inter-grid operation; the first is "prolongation"
2041 where a field on a coarse mesh is mapped onto a fine mesh. The second
2042 is "restriction" where a field on a fine mesh is mapped onto a coarse
2045 :param invoke: the Invoke for which to extract information on all \
2046 required inter-grid operations.
2047 :type invoke: :py:class:`psyclone.dynamo0p3.LFRicInvoke`
2048 :param unique_psy_vars: list of arguments to the PSy-layer routine.
2049 :type unique_psy_vars: list of \
2050 :py:class:`psyclone.dynamo0p3.DynKernelArgument` objects.
2053 def __init__(self, invoke, unique_psy_vars):
2061 self.
_schedule_schedule = invoke.schedule
2070 for var
in unique_psy_vars:
2071 if not var.is_scalar:
2078 non_intergrid_kernels = []
2079 has_intergrid =
False
2080 for call
in self.
_schedule_schedule.coded_kernels():
2082 if (call.reference_element.properties
or call.mesh.properties
or
2083 call.iterates_over ==
"domain" or call.cma_operation):
2084 _name_set.add(
"mesh")
2086 if not call.is_intergrid:
2087 non_intergrid_kernels.append(call)
2089 has_intergrid =
True
2091 _name_set.add(f
"mesh_{call._intergrid_ref.fine.name}")
2092 _name_set.add(f
"mesh_{call._intergrid_ref.coarse.name}")
2096 if non_intergrid_kernels
and has_intergrid:
2098 f
"An invoke containing inter-grid kernels must contain no "
2099 f
"other kernel types but kernels "
2100 f
"'{''', '''.join([c.name for c in non_intergrid_kernels])}' "
2101 f
"in invoke '{invoke.name}' are not inter-grid kernels.")
2114 if not _name_set
and Config.get().distributed_memory:
2116 _name_set.add(
"mesh")
2120 def _add_mesh_symbols(self, mesh_tags):
2122 Add DataSymbols for the supplied list of mesh names and store the
2123 corresponding list of tags.
2125 A ContainerSymbol is created for the LFRic mesh module and a TypeSymbol
2126 for the mesh type. If distributed memory is enabled then a DataSymbol
2127 to hold the maximum halo depth is created for each mesh.
2129 :param mesh_tags: tag names for every mesh object required.
2130 :type mesh_tags: list of str
2141 mmod = const.MESH_TYPE_MAP[
"mesh"][
"module"]
2142 mtype = const.MESH_TYPE_MAP[
"mesh"][
"type"]
2145 mmod, symbol_type=ContainerSymbol)
2147 mtype_sym = self.
_symbol_table_symbol_table.find_or_create_tag(
2148 mtype, symbol_type=DataTypeSymbol,
2149 datatype=UnresolvedType(),
2150 interface=ImportInterface(csym))
2153 for name
in mesh_tags:
2154 name_list.append(self.
_symbol_table_symbol_table.find_or_create_tag(
2155 name, symbol_type=DataSymbol, datatype=mtype_sym).name)
2157 if Config.get().distributed_memory:
2160 for name
in mesh_tags:
2161 var_name = f
"max_halo_depth_{name}"
2162 self.
_symbol_table_symbol_table.find_or_create_integer_symbol(
2163 var_name, tag=var_name)
2165 def _colourmap_init(self):
2167 Sets-up information on any required colourmaps. This cannot be done
2168 in the constructor since colouring is applied by Transformations
2169 and happens after the Schedule has already been constructed. Therefore,
2170 this method is called at code-generation time.
2175 non_intergrid_kern =
None
2176 sym_tab = self.
_schedule_schedule.symbol_table
2178 for call
in [call
for call
in self.
_schedule_schedule.coded_kernels()
if
2179 call.is_coloured()]:
2183 if (call.parent.parent.upper_bound_name
in
2184 const.HALO_ACCESS_LOOP_BOUNDS):
2189 if not call.is_intergrid:
2190 non_intergrid_kern = call
2196 carg_name = call._intergrid_ref.coarse.name
2198 base_name =
"cmap_" + carg_name
2199 colour_map = sym_tab.find_or_create_array(
2200 base_name, 2, ScalarType.Intrinsic.INTEGER,
2203 base_name =
"ncolour_" + carg_name
2204 ncolours = sym_tab.find_or_create_integer_symbol(
2205 base_name, tag=base_name)
2207 if (Config.get().distributed_memory
and
2208 not call.all_updates_are_writes):
2211 base_name =
"last_halo_cell_all_colours_" + carg_name
2212 last_cell = self.
_schedule_schedule.symbol_table.find_or_create_array(
2213 base_name, 2, ScalarType.Intrinsic.INTEGER, tag=base_name)
2217 base_name =
"last_edge_cell_all_colours_" + carg_name
2218 last_cell = self.
_schedule_schedule.symbol_table.find_or_create_array(
2219 base_name, 1, ScalarType.Intrinsic.INTEGER, tag=base_name)
2221 call._intergrid_ref.set_colour_info(colour_map, ncolours,
2231 colour_map = non_intergrid_kern.colourmap
2233 ncolours = sym_tab.find_or_create_integer_symbol(
2234 "ncolour", tag=
"ncolour").name
2236 sym_tab.find_or_create_array(
2237 "last_halo_cell_all_colours", 2,
2238 ScalarType.Intrinsic.INTEGER,
2239 tag=
"last_halo_cell_all_colours")
2241 sym_tab.find_or_create_array(
2242 "last_edge_cell_all_colours", 1,
2243 ScalarType.Intrinsic.INTEGER,
2244 tag=
"last_edge_cell_all_colours")
2248 Declare variables specific to mesh objects.
2250 :param parent: the parent node to which to add the declarations
2251 :type parent: :py:class:`psyclone.f2pygen.BaseGen`
2255 api_config = Config.get().api_conf(
"dynamo0.3")
2263 mtype = const.MESH_TYPE_MAP[
"mesh"][
"type"]
2264 mmod = const.MESH_TYPE_MAP[
"mesh"][
"module"]
2265 mmap_type = const.MESH_TYPE_MAP[
"mesh_map"][
"type"]
2266 mmap_mod = const.MESH_TYPE_MAP[
"mesh_map"][
"module"]
2268 name = self.
_symbol_table_symbol_table.lookup_with_tag(mtype).name
2269 parent.add(
UseGen(parent, name=mmod, only=
True,
2272 parent.add(
UseGen(parent, name=mmap_mod, only=
True,
2273 funcnames=[mmap_type]))
2276 name = self.
_symbol_table_symbol_table.lookup_with_tag(tag_name).name
2277 parent.add(
TypeDeclGen(parent, pointer=
True, datatype=mtype,
2278 entity_decls=[name +
" => null()"]))
2280 if Config.get().distributed_memory:
2282 f
"max_halo_depth_{tag_name}").name
2283 parent.add(
DeclGen(parent, datatype=
"integer",
2284 kind=api_config.default_kind[
"integer"],
2285 entity_decls=[name]))
2291 entity_decls=[kern.mmap +
" => null()"]))
2293 DeclGen(parent, pointer=
True, datatype=
"integer",
2294 kind=api_config.default_kind[
"integer"],
2295 entity_decls=[kern.cell_map +
"(:,:,:) => null()"]))
2299 parent.add(
DeclGen(parent, datatype=
"integer",
2300 kind=api_config.default_kind[
"integer"],
2301 entity_decls=[kern.ncell_fine,
2303 kern.ncellpercelly]))
2305 if kern.colourmap_symbol:
2307 DeclGen(parent, datatype=
"integer",
2308 kind=api_config.default_kind[
"integer"],
2310 entity_decls=[kern.colourmap_symbol.name+
"(:,:)"]))
2312 DeclGen(parent, datatype=
"integer",
2313 kind=api_config.default_kind[
"integer"],
2314 entity_decls=[kern.ncolours_var_symbol.name]))
2318 dim_list = len(kern.last_cell_var_symbol.datatype.shape)*
":"
2319 decln = (f
"{kern.last_cell_var_symbol.name}("
2320 f
"{','.join(dim_list)})")
2322 DeclGen(parent, datatype=
"integer", allocatable=
True,
2323 kind=api_config.default_kind[
"integer"],
2324 entity_decls=[decln]))
2331 csym = self.
_schedule_schedule.symbol_table.lookup_with_tag(
"cmap")
2332 colour_map = csym.name
2334 base_name =
"ncolour"
2336 self.
_schedule_schedule.symbol_table.find_or_create_tag(base_name).name
2338 parent.add(
DeclGen(parent, datatype=
"integer",
2339 kind=api_config.default_kind[
"integer"],
2341 entity_decls=[colour_map+
"(:,:)"]))
2342 parent.add(
DeclGen(parent, datatype=
"integer",
2343 kind=api_config.default_kind[
"integer"],
2344 entity_decls=[ncolours]))
2346 last_cell = self.
_symbol_table_symbol_table.find_or_create_tag(
2347 "last_halo_cell_all_colours")
2348 parent.add(
DeclGen(parent, datatype=
"integer",
2349 kind=api_config.default_kind[
"integer"],
2351 entity_decls=[last_cell.name+
"(:,:)"]))
2353 last_cell = self.
_symbol_table_symbol_table.find_or_create_tag(
2354 "last_edge_cell_all_colours")
2355 parent.add(
DeclGen(parent, datatype=
"integer",
2356 kind=api_config.default_kind[
"integer"],
2358 entity_decls=[last_cell.name+
"(:)"]))
2362 Initialise parameters specific to inter-grid kernels.
2364 :param parent: the parent node to which to add the initialisations.
2365 :type parent: :py:class:`psyclone.f2pygen.BaseGen`
2379 parent.add(
CommentGen(parent,
" Create a mesh object"))
2381 rhs =
"%".join([self.
_first_var_first_var.proxy_name_indexed,
2382 self.
_first_var_first_var.ref_name(),
"get_mesh()"])
2383 mesh_name = self.
_symbol_table_symbol_table.lookup_with_tag(
2385 parent.add(
AssignGen(parent, pointer=
True, lhs=mesh_name, rhs=rhs))
2386 if Config.get().distributed_memory:
2389 depth_name = self.
_symbol_table_symbol_table.lookup_with_tag(
2390 f
"max_halo_depth_{self._mesh_tag_names[0]}").name
2391 parent.add(
AssignGen(parent, lhs=depth_name,
2392 rhs=f
"{mesh_name}%get_halo_depth()"))
2395 parent.add(
CommentGen(parent,
" Get the colourmap"))
2398 colour_map = self.
_schedule_schedule.symbol_table.find_or_create_tag(
2401 self.
_schedule_schedule.symbol_table.find_or_create_tag(
"ncolour")\
2405 parent, lhs=ncolour, rhs=f
"{mesh_name}%get_ncolours()"))
2407 parent.add(
AssignGen(parent, pointer=
True, lhs=colour_map,
2408 rhs=f
"{mesh_name}%get_colour_map()"))
2413 " Look-up mesh objects and loop limits for inter-grid kernels"))
2424 fine_mesh = self.
_schedule_schedule.symbol_table.find_or_create_tag(
2425 f
"mesh_{dig.fine.name}").name
2426 coarse_mesh = self.
_schedule_schedule.symbol_table.find_or_create_tag(
2427 f
"mesh_{dig.coarse.name}").name
2428 if fine_mesh
not in initialised:
2429 initialised.append(fine_mesh)
2433 rhs=
"%".join([dig.fine.proxy_name_indexed,
2434 dig.fine.ref_name(),
2436 if Config.get().distributed_memory:
2438 self.
_schedule_schedule.symbol_table.find_or_create_tag(
2439 f
"max_halo_depth_mesh_{dig.fine.name}").name)
2441 parent.add(
AssignGen(parent, lhs=max_halo_f_mesh,
2442 rhs=f
"{fine_mesh}%get_halo_depth()"))
2443 if coarse_mesh
not in initialised:
2444 initialised.append(coarse_mesh)
2448 rhs=
"%".join([dig.coarse.proxy_name_indexed,
2449 dig.coarse.ref_name(),
2451 if Config.get().distributed_memory:
2453 self.
_schedule_schedule.symbol_table.find_or_create_tag(
2454 f
"max_halo_depth_mesh_{dig.coarse.name}").name)
2456 parent, lhs=max_halo_c_mesh,
2457 rhs=f
"{coarse_mesh}%get_halo_depth()"))
2460 if dig.mmap
not in initialised:
2461 initialised.append(dig.mmap)
2465 rhs=f
"{coarse_mesh}%get_mesh_map({fine_mesh})"))
2468 if dig.cell_map
not in initialised:
2469 initialised.append(dig.cell_map)
2471 AssignGen(parent, pointer=
True, lhs=dig.cell_map,
2472 rhs=dig.mmap+
"%get_whole_cell_map()"))
2475 if dig.ncell_fine
not in initialised:
2476 initialised.append(dig.ncell_fine)
2477 if Config.get().distributed_memory:
2482 rhs=(fine_mesh+
"%get_last_halo_cell"
2487 rhs=
"%".join([dig.fine.proxy_name,
2488 dig.fine.ref_name(),
2492 if dig.ncellpercellx
not in initialised:
2493 initialised.append(dig.ncellpercellx)
2495 AssignGen(parent, lhs=dig.ncellpercellx,
2497 "%get_ntarget_cells_per_source_x()"))
2500 if dig.ncellpercelly
not in initialised:
2501 initialised.append(dig.ncellpercelly)
2503 AssignGen(parent, lhs=dig.ncellpercelly,
2505 "%get_ntarget_cells_per_source_y()"))
2508 if dig.colourmap_symbol:
2510 parent.add(
AssignGen(parent, lhs=dig.ncolours_var_symbol.name,
2511 rhs=coarse_mesh +
"%get_ncolours()"))
2513 parent.add(
AssignGen(parent, lhs=dig.colourmap_symbol.name,
2515 rhs=coarse_mesh +
"%get_colour_map()"))
2517 sym = dig.last_cell_var_symbol
2518 if len(sym.datatype.shape) == 2:
2520 name =
"%get_last_halo_cell_all_colours()"
2523 name =
"%get_last_edge_cell_all_colours()"
2524 parent.add(
AssignGen(parent, lhs=sym.name,
2525 rhs=coarse_mesh + name))
2530 :returns: A list of objects describing the intergrid kernels used in
2532 :rtype: list[:py:class:`psyclone.dynamo3p0.DynInterGrid`]
2535 for call
in self.
_schedule_schedule.coded_kernels():
2536 if call.is_intergrid:
2537 intergrids.append(call._intergrid_ref)
2543 Holds information on quantities required by an inter-grid kernel.
2545 :param fine_arg: Kernel argument on the fine mesh.
2546 :type fine_arg: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
2547 :param coarse_arg: Kernel argument on the coarse mesh.
2548 :type coarse_arg: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
2551 def __init__(self, fine_arg, coarse_arg):
2554 self.
coarsecoarse = coarse_arg
2555 self.
finefine = fine_arg
2558 symtab = self.
coarsecoarse.call.ancestor(InvokeSchedule).symbol_table
2561 base_mmap_name = f
"mmap_{fine_arg.name}_{coarse_arg.name}"
2562 self.
mmapmmap = symtab.find_or_create_tag(base_mmap_name).name
2565 name = f
"ncell_{fine_arg.name}"
2566 self.
ncell_finencell_fine = symtab.find_or_create_integer_symbol(
2567 name, tag=name).name
2569 name = f
"ncpc_{fine_arg.name}_{coarse_arg.name}_x"
2570 self.
ncellpercellxncellpercellx = symtab.find_or_create_integer_symbol(
2571 name, tag=name).name
2573 name = f
"ncpc_{fine_arg.name}_{coarse_arg.name}_y"
2574 self.
ncellpercellyncellpercelly = symtab.find_or_create_integer_symbol(
2575 name, tag=name).name
2577 base_name =
"cell_map_" + coarse_arg.name
2578 sym = symtab.find_or_create_array(base_name, 3,
2579 ScalarType.Intrinsic.INTEGER,
2593 '''Sets the colour_map, number of colours, and
2594 last cell of a particular colour.
2596 :param colour_map: the colour map symbol.
2597 :type: colour_map:py:class:`psyclone.psyir.symbols.Symbol`
2598 :param ncolours: the number of colours.
2599 :type: ncolours: :py:class:`psyclone.psyir.symbols.Symbol`
2600 :param last_cell: the last halo cell of a particular colour.
2601 :type last_cell: :py:class:`psyclone.psyir.symbols.Symbol`
2610 ''':returns: the colour map symbol.
2611 :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
2617 ''':returns: the symbol for storing the number of colours.
2618 :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
2624 ''':returns: the last halo/edge cell variable.
2625 :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
2631 ''' Holds all information on the basis and differential basis
2632 functions required by an invoke or kernel call. This covers both those
2633 required for quadrature and for evaluators.
2635 :param node: either the schedule of an Invoke or a single Kernel object \
2636 for which to extract information on all required \
2637 basis/diff-basis functions.
2638 :type node: :py:class:`psyclone.dynamo0p3.DynInvokeSchedule` or \
2639 :py:class:`psyclone.domain.lfric.LFRicKern`
2641 :raises InternalError: if a call has an unrecognised evaluator shape.
2646 qr_dim_vars = {
"xyoz": [
"np_xy",
"np_z"],
2647 "edge": [
"np_xyz",
"nedges"],
2648 "face": [
"np_xyz",
"nfaces"]}
2650 qr_weight_vars = {
"xyoz": [
"weights_xy",
"weights_z"],
2651 "edge": [
"weights_xyz"],
2652 "face": [
"weights_xyz"]}
2654 def __init__(self, node):
2656 super().__init__(node)
2667 self.
_qr_vars_qr_vars = OrderedDict()
2673 for call
in self.
_calls_calls:
2675 if isinstance(call, LFRicBuiltIn)
or not call.eval_shapes:
2679 for shape, rule
in call.qr_rules.items():
2682 if shape
not in self.
_qr_vars_qr_vars:
2687 if rule.psy_name
not in self.
_qr_vars_qr_vars[shape]:
2690 self.
_qr_vars_qr_vars[shape].append(rule.psy_name)
2692 if "gh_evaluator" in call.eval_shapes:
2701 for fs_name
in call.eval_targets:
2706 call.eval_targets[fs_name]
2717 Get the name of the variable holding the first dimension of a
2720 :param function_space: the function space the basis function is for
2721 :type function_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
2722 :return: a Fortran variable name
2726 return "dim_" + function_space.mangled_name
2731 Get the size of the first dimension of a basis function.
2733 :param function_space: the function space the basis function is for
2734 :type function_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
2735 :return: an integer length.
2738 :raises GenerationError: if an unsupported function space is supplied \
2739 (e.g. ANY_SPACE_*, ANY_DISCONTINUOUS_SPACE_*)
2741 if function_space.has_scalar_basis:
2743 elif function_space.has_vector_basis:
2752 f
"Unsupported space for basis function, "
2753 f
"expecting one of {const.VALID_FUNCTION_SPACES} but found "
2754 f
"'{function_space.orig_name}'")
2760 Get the name of the variable holding the first dimension of a
2761 differential basis function.
2763 :param function_space: the function space the diff-basis function \
2765 :type function_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
2766 :return: a Fortran variable name.
2770 return "diff_dim_" + function_space.mangled_name
2775 Get the size of the first dimension of an array for a
2776 differential basis function.
2778 :param function_space: the function space the diff-basis function \
2780 :type function_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
2781 :return: an integer length.
2784 :raises GenerationError: if an unsupported function space is \
2785 supplied (e.g. ANY_SPACE_*, \
2786 ANY_DISCONTINUOUS_SPACE_*)
2789 if function_space.has_scalar_diff_basis:
2791 elif function_space.has_vector_diff_basis:
2801 f
"Unsupported space for differential basis function, "
2802 f
"expecting one of {const.VALID_FUNCTION_SPACES} but found "
2803 f
"'{function_space.orig_name}'")
2806 def _setup_basis_fns_for_call(self, call):
2808 Populates self._basis_fns with entries describing the basis
2809 functions required by the supplied Call.
2811 :param call: the kernel call for which basis functions are required.
2812 :type call: :py:class:`psyclone.domain.lfric.LFRicKern`
2814 :raises InternalError: if the supplied call is of incorrect type.
2815 :raises InternalError: if the supplied call has an unrecognised \
2818 if not isinstance(call, LFRicKern):
2819 raise InternalError(f
"Expected a LFRicKern object but got: "
2824 for fsd
in call.fs_descriptors.descriptors:
2829 arg, fspace = call.arguments.get_arg_on_space_name(fsd.fs_name)
2831 for shape
in call.eval_shapes:
2835 entry = {
"shape": shape,
2838 if shape
in const.VALID_QUADRATURE_SHAPES:
2841 entry[
"qr_var"] = call.qr_rules[shape].psy_name
2847 entry[
"nodal_fspaces"] = [
None]
2848 elif shape ==
"gh_evaluator":
2850 entry[
"qr_var"] =
None
2853 entry[
"nodal_fspaces"] = [items[0]
for items
in
2854 call.eval_targets.values()]
2857 f
"'{shape}'. Should be one of "
2858 f
"{const.VALID_EVALUATOR_SHAPES}")
2863 if fsd.requires_basis:
2864 entry[
"type"] =
"basis"
2866 if fsd.requires_diff_basis:
2870 diff_entry = entry.copy()
2871 diff_entry[
"type"] =
"diff-basis"
2874 def _stub_declarations(self, parent):
2876 Insert the variable declarations required by the basis functions into
2879 :param parent: the f2pygen node representing the Kernel stub.
2880 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
2882 :raises InternalError: if an unsupported quadrature shape is found.
2885 api_config = Config.get().api_conf(
"dynamo0.3")
2891 supported_shapes = [
"gh_quadrature_xyoz",
"gh_quadrature_face",
2892 "gh_quadrature_edge"]
2898 parent.add(
DeclGen(parent, datatype=
"integer",
2899 kind=api_config.default_kind[
"integer"],
2900 intent=
"in", entity_decls=var_dims))
2901 for basis
in basis_arrays:
2902 parent.add(
DeclGen(parent, datatype=
"real",
2903 kind=api_config.default_kind[
"real"],
2905 dimension=
",".join(basis_arrays[basis]),
2906 entity_decls=[basis]))
2910 for shape
in self.
_qr_vars_qr_vars:
2911 qr_name =
"_qr_" + shape.split(
"_")[-1]
2912 if shape ==
"gh_quadrature_xyoz":
2913 datatype = const.QUADRATURE_TYPE_MAP[shape][
"intrinsic"]
2914 kind = const.QUADRATURE_TYPE_MAP[shape][
"kind"]
2916 parent, datatype=datatype, kind=kind,
2917 intent=
"in", dimension=
"np_xy"+qr_name,
2918 entity_decls=[
"weights_xy"+qr_name]))
2920 parent, datatype=datatype, kind=kind,
2921 intent=
"in", dimension=
"np_z"+qr_name,
2922 entity_decls=[
"weights_z"+qr_name]))
2923 elif shape ==
"gh_quadrature_face":
2926 datatype=const.QUADRATURE_TYPE_MAP[shape][
"intrinsic"],
2927 kind=const.QUADRATURE_TYPE_MAP[shape][
"kind"], intent=
"in",
2928 dimension=
",".join([
"np_xyz"+qr_name,
"nfaces"+qr_name]),
2929 entity_decls=[
"weights_xyz"+qr_name]))
2930 elif shape ==
"gh_quadrature_edge":
2933 datatype=const.QUADRATURE_TYPE_MAP[shape][
"intrinsic"],
2934 kind=const.QUADRATURE_TYPE_MAP[shape][
"kind"], intent=
"in",
2935 dimension=
",".join([
"np_xyz"+qr_name,
"nedges"+qr_name]),
2936 entity_decls=[
"weights_xyz"+qr_name]))
2939 f
"Quadrature shapes other than {supported_shapes} are not "
2940 f
"yet supported - got: '{shape}'")
2942 def _invoke_declarations(self, parent):
2944 Add basis-function declarations to the PSy layer.
2946 :param parent: f2pygen node represening the PSy-layer routine.
2947 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
2952 for shape
in const.VALID_QUADRATURE_SHAPES:
2959 QUADRATURE_TYPE_MAP[shape][
"type"],
2960 entity_decls=self.
_qr_vars_qr_vars[shape],
2965 for var
in self.
_qr_vars_qr_vars[shape]:
2967 self.
_symbol_table_symbol_table.find_or_create_tag(var+
"_proxy")
2973 QUADRATURE_TYPE_MAP[shape][
"proxy_type"],
2974 entity_decls=var_names))
2978 Create the declarations and assignments required for the
2979 basis-functions required by an invoke. These are added as children
2980 of the supplied parent node in the AST.
2982 :param parent: the node in the f2pygen AST that will be the
2983 parent of all of the declarations and assignments.
2984 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
2986 :raises InternalError: if an invalid entry is encountered in the \
2987 self._basis_fns list.
2990 api_config = Config.get().api_conf(
"dynamo0.3")
2992 basis_declarations = []
2998 UseGen(parent, name=const.
2999 FUNCTION_SPACE_TYPE_MAP[
"function_space"][
"module"],
3000 only=
True, funcnames=[
"BASIS",
"DIFF_BASIS"]))
3004 parent.add(
CommentGen(parent,
" Look-up quadrature variables"))
3009 quad_map = const.QUADRATURE_TYPE_MAP[shp]
3010 parent.add(
UseGen(parent,
3011 name=quad_map[
"module"],
3013 funcnames=[quad_map[
"type"],
3014 quad_map[
"proxy_type"]]))
3024 " Initialise evaluator-related quantities "
3025 "for the target function spaces"))
3028 for (fspace, arg)
in self.
_eval_targets_eval_targets.values():
3031 nodes_name =
"nodes_" + fspace.mangled_name
3033 parent, lhs=nodes_name,
3034 rhs=
"%".join([arg.proxy_name_indexed, arg.ref_name(fspace),
3037 my_kind = api_config.default_kind[
"real"]
3038 parent.add(
DeclGen(parent, datatype=
"real",
3041 entity_decls=[nodes_name+
"(:,:) => null()"]))
3042 const_mod = const.UTILITIES_MOD_MAP[
"constants"][
"module"]
3043 const_mod_uses = self.
_invoke_invoke.invokes.psy. \
3044 infrastructure_modules[const_mod]
3048 const_mod_uses.add(my_kind)
3052 parent.add(
CommentGen(parent,
" Allocate basis/diff-basis arrays"))
3058 if basis_fn[
'type'] ==
"basis":
3060 dim_space =
"get_dim_space()"
3061 elif basis_fn[
'type'] ==
"diff-basis":
3064 dim_space =
"get_dim_space_diff()"
3067 f
"Unrecognised type of basis function: "
3068 f
"'{basis_fn['''type''']}'. Should be either 'basis' or "
3071 if first_dim
not in var_dim_list:
3072 var_dim_list.append(first_dim)
3074 [basis_fn[
"arg"].proxy_name_indexed,
3075 basis_fn[
"arg"].ref_name(basis_fn[
"fspace"]),
3077 parent.add(
AssignGen(parent, lhs=first_dim, rhs=rhs))
3083 parent.add(
DeclGen(parent, datatype=
"integer",
3084 kind=api_config.default_kind[
"integer"],
3085 entity_decls=var_dims))
3087 basis_declarations = []
3088 for basis
in basis_arrays:
3091 basis+
"("+
", ".join(basis_arrays[basis])+
")"))
3092 basis_declarations.append(
3093 basis+
"("+
",".join([
":"]*len(basis_arrays[basis]))+
")")
3096 if basis_declarations:
3097 my_kind = api_config.default_kind[
"real"]
3098 parent.add(
DeclGen(parent, datatype=
"real", kind=my_kind,
3100 entity_decls=basis_declarations))
3108 def _basis_fn_declns(self):
3110 Extracts all information relating to the necessary declarations
3111 for basis-function arrays.
3113 :returns: a 2-tuple containing a list of dimensioning variables & a \
3114 dict of basis arrays.
3115 :rtype: (list of str, dict)
3117 :raises InternalError: if neither self._invoke or self._kernel are set.
3118 :raises InternalError: if an unrecognised type of basis function is \
3120 :raises InternalError: if an unrecognised evaluator shape is \
3122 :raises InternalError: if there is no name for the quadrature object \
3123 when generating PSy-layer code.
3129 basis_arrays = OrderedDict()
3141 if basis_fn[
'type'] ==
"basis":
3148 "have either a Kernel or an "
3149 "Invoke. Should be impossible.")
3150 basis_name =
"gh_basis"
3151 elif basis_fn[
'type'] ==
"diff-basis":
3160 "but do not have either a Kernel or "
3161 "an Invoke. Should be impossible.")
3162 basis_name =
"gh_diff_basis"
3165 f
"Unrecognised type of basis function: "
3166 f
"'{basis_fn['''type''']}'. Should be either 'basis' or "
3169 if self.
_invoke_invoke
and first_dim
not in var_dim_list:
3170 var_dim_list.append(first_dim)
3172 if basis_fn[
"shape"]
in const.VALID_QUADRATURE_SHAPES:
3174 qr_var = basis_fn[
"qr_var"]
3177 f
"Quadrature '{basis_fn['''shape''']}' is required but"
3178 f
" have no name for the associated Quadrature object.")
3180 op_name = basis_fn[
"fspace"].get_operator_name(basis_name,
3182 if op_name
in basis_arrays:
3188 alloc_args = qr_basis_alloc_args(first_dim, basis_fn)
3189 for arg
in alloc_args:
3193 if not arg[0].isdigit()
and arg
not in var_dim_list:
3194 var_dim_list.append(arg)
3195 basis_arrays[op_name] = alloc_args
3197 elif basis_fn[
"shape"].lower() ==
"gh_evaluator":
3200 for target_space
in basis_fn[
"nodal_fspaces"]:
3201 op_name = basis_fn[
"fspace"].\
3202 get_operator_name(basis_name,
3203 qr_var=basis_fn[
"qr_var"],
3204 on_space=target_space)
3205 if op_name
in basis_arrays:
3209 basis_arrays[op_name] = [
3211 basis_fn[
"fspace"].ndf_name,
3212 target_space.ndf_name]
3215 f
"Unrecognised evaluator shape: '{basis_fn['''shape''']}'."
3216 f
" Should be one of {const.VALID_EVALUATOR_SHAPES}")
3218 return (var_dim_list, basis_arrays)
3220 def _initialise_xyz_qr(self, parent):
3222 Add in the initialisation of variables needed for XYZ
3225 :param parent: the node in the AST representing the PSy subroutine
3226 in which to insert the initialisation
3227 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
3234 def _initialise_xyoz_qr(self, parent):
3236 Add in the initialisation of variables needed for XYoZ
3239 :param parent: the node in the AST representing the PSy subroutine
3240 in which to insert the initialisation
3241 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
3244 api_config = Config.get().api_conf(
"dynamo0.3")
3246 if "gh_quadrature_xyoz" not in self._qr_vars:
3249 for qr_arg_name
in self._qr_vars[
"gh_quadrature_xyoz"]:
3256 parent, datatype=
"integer",
3257 kind=api_config.default_kind[
"integer"],
3258 entity_decls=[name+
"_"+qr_arg_name
3259 for name
in self.qr_dim_vars[
"xyoz"]]))
3260 decl_list = [name+
"_"+qr_arg_name+
"(:) => null()"
3261 for name
in self.qr_weight_vars[
"xyoz"]]
3262 const = LFRicConstants()
3264 const.QUADRATURE_TYPE_MAP[
"gh_quadrature_xyoz"][
"intrinsic"]
3265 kind = const.QUADRATURE_TYPE_MAP[
"gh_quadrature_xyoz"][
"kind"]
3267 DeclGen(parent, datatype=datatype, kind=kind,
3268 pointer=
True, entity_decls=decl_list))
3269 const_mod = const.UTILITIES_MOD_MAP[
"constants"][
"module"]
3270 const_mod_uses = self._invoke.invokes.psy. \
3271 infrastructure_modules[const_mod]
3275 const_mod_uses.add(kind)
3278 proxy_name = qr_arg_name +
"_proxy"
3280 AssignGen(parent, lhs=proxy_name,
3281 rhs=qr_arg_name+
"%"+
"get_quadrature_proxy()"))
3283 for qr_var
in self.qr_dim_vars[
"xyoz"]:
3285 AssignGen(parent, lhs=qr_var+
"_"+qr_arg_name,
3286 rhs=proxy_name+
"%"+qr_var))
3288 for qr_var
in self.qr_weight_vars[
"xyoz"]:
3290 AssignGen(parent, pointer=
True,
3291 lhs=qr_var+
"_"+qr_arg_name,
3292 rhs=proxy_name+
"%"+qr_var))
3294 def _initialise_xoyoz_qr(self, parent):
3296 Add in the initialisation of variables needed for XoYoZ
3299 :param parent: the node in the AST representing the PSy subroutine \
3300 in which to insert the initialisation.
3301 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
3308 def _initialise_face_or_edge_qr(self, parent, qr_type):
3310 Add in the initialisation of variables needed for face or edge
3313 :param parent: the node in the AST representing the PSy subroutine \
3314 in which to insert the initialisation.
3315 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
3316 :param str qr_type: whether to generate initialisation code for \
3317 "face" or "edge" quadrature.
3319 :raises InternalError: if `qr_type` is not "face" or "edge".
3322 if qr_type
not in [
"face",
"edge"]:
3323 raise InternalError(
3324 f
"_initialise_face_or_edge_qr: qr_type argument must be "
3325 f
"either 'face' or 'edge' but got: '{qr_type}'")
3327 quadrature_name = f
"gh_quadrature_{qr_type}"
3329 if quadrature_name
not in self._qr_vars:
3332 api_config = Config.get().api_conf(
"dynamo0.3")
3333 symbol_table = self._symbol_table
3335 for qr_arg_name
in self._qr_vars[quadrature_name]:
3340 symbol_table.find_or_create_integer_symbol(
3341 name+
"_"+qr_arg_name, tag=name+
"_"+qr_arg_name).name
3342 for name
in self.qr_dim_vars[qr_type]]
3343 parent.add(DeclGen(parent, datatype=
"integer",
3344 kind=api_config.default_kind[
"integer"],
3345 entity_decls=decl_list))
3347 names = [f
"{name}_{qr_arg_name}"
3348 for name
in self.qr_weight_vars[qr_type]]
3350 symbol_table.find_or_create_array(name, 2,
3351 ScalarType.Intrinsic.REAL,
3353 +
"(:,:) => null()" for name
in names]
3354 const = LFRicConstants()
3355 datatype = const.QUADRATURE_TYPE_MAP[quadrature_name][
"intrinsic"]
3356 kind = const.QUADRATURE_TYPE_MAP[quadrature_name][
"kind"]
3358 DeclGen(parent, datatype=datatype, pointer=
True, kind=kind,
3359 entity_decls=decl_list))
3360 const_mod = const.UTILITIES_MOD_MAP[
"constants"][
"module"]
3361 const_mod_uses = self._invoke.invokes.psy. \
3362 infrastructure_modules[const_mod]
3366 const_mod_uses.add(kind)
3368 proxy_name = symbol_table.find_or_create_tag(
3369 qr_arg_name+
"_proxy").name
3371 AssignGen(parent, lhs=proxy_name,
3372 rhs=qr_arg_name+
"%"+
"get_quadrature_proxy()"))
3375 for qr_var
in self.qr_dim_vars[qr_type]:
3377 AssignGen(parent, lhs=qr_var+
"_"+qr_arg_name,
3378 rhs=proxy_name+
"%"+qr_var))
3380 for qr_var
in self.qr_weight_vars[qr_type]:
3382 AssignGen(parent, pointer=
True,
3383 lhs=qr_var+
"_"+qr_arg_name,
3384 rhs=proxy_name+
"%"+qr_var))
3386 def _compute_basis_fns(self, parent):
3388 Generates the necessary Fortran to compute the values of
3389 any basis/diff-basis arrays required
3391 :param parent: Node in the f2pygen AST which will be the parent
3392 of the assignments created in this routine
3393 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
3397 const = LFRicConstants()
3398 api_config = Config.get().api_conf(
"dynamo0.3")
3400 loop_var_list = set()
3404 parent.add(CommentGen(parent,
""))
3405 parent.add(CommentGen(parent,
" Compute basis/diff-basis arrays"))
3406 parent.add(CommentGen(parent,
""))
3408 for basis_fn
in self._basis_fns:
3414 if basis_fn[
"type"] ==
"diff-basis":
3415 basis_name =
"gh_diff_basis"
3416 basis_type =
"DIFF_BASIS"
3417 first_dim = self.diff_basis_first_dim_name(basis_fn[
"fspace"])
3418 elif basis_fn[
"type"] ==
"basis":
3419 basis_name =
"gh_basis"
3420 basis_type =
"BASIS"
3421 first_dim = self.basis_first_dim_name(basis_fn[
"fspace"])
3423 raise InternalError(
3424 f
"Unrecognised type of basis function: "
3425 f
"'{basis_fn['''type''']}'. Expected one of 'basis' or "
3427 if basis_fn[
"shape"]
in const.VALID_QUADRATURE_SHAPES:
3428 op_name = basis_fn[
"fspace"].\
3429 get_operator_name(basis_name, qr_var=basis_fn[
"qr_var"])
3430 if op_name
in op_name_list:
3433 op_name_list.append(op_name)
3436 args = [basis_type, basis_fn[
"arg"].proxy_name_indexed +
"%" +
3437 basis_fn[
"arg"].ref_name(basis_fn[
"fspace"]),
3438 first_dim, basis_fn[
"fspace"].ndf_name, op_name]
3443 name=basis_fn[
"qr_var"]+
"%compute_function",
3445 elif basis_fn[
"shape"].lower() ==
"gh_evaluator":
3448 for space
in basis_fn[
"nodal_fspaces"]:
3449 op_name = basis_fn[
"fspace"].\
3450 get_operator_name(basis_name, on_space=space)
3451 if op_name
in op_name_list:
3454 op_name_list.append(op_name)
3456 nodal_loop_var =
"df_nodal"
3457 loop_var_list.add(nodal_loop_var)
3460 nodal_dof_loop = DoGen(
3461 parent, nodal_loop_var,
"1", space.ndf_name)
3462 parent.add(nodal_dof_loop)
3464 dof_loop_var =
"df_" + basis_fn[
"fspace"].mangled_name
3465 loop_var_list.add(dof_loop_var)
3467 dof_loop = DoGen(nodal_dof_loop, dof_loop_var,
3468 "1", basis_fn[
"fspace"].ndf_name)
3469 nodal_dof_loop.add(dof_loop)
3470 lhs = op_name +
"(:," +
"df_" + \
3471 basis_fn[
"fspace"].mangled_name +
"," +
"df_nodal)"
3472 rhs = (f
"{basis_fn['arg'].proxy_name_indexed}%"
3473 f
"{basis_fn['arg'].ref_name(basis_fn['fspace'])}%"
3474 f
"call_function({basis_type},{dof_loop_var},nodes_"
3475 f
"{space.mangled_name}(:,{nodal_loop_var}))")
3476 dof_loop.add(AssignGen(dof_loop, lhs=lhs, rhs=rhs))
3478 raise InternalError(
3479 f
"Unrecognised shape '{basis_fn['''shape''']}' specified "
3480 f
"for basis function. Should be one of: "
3481 f
"{const.VALID_EVALUATOR_SHAPES}")
3484 parent.add(DeclGen(parent, datatype=
"integer",
3485 kind=api_config.default_kind[
"integer"],
3486 entity_decls=sorted(loop_var_list)))
3490 Add code to deallocate all basis/diff-basis function arrays
3492 :param parent: node in the f2pygen AST to which the deallocate \
3493 calls will be added.
3494 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
3496 :raises InternalError: if an unrecognised type of basis function \
3502 parent.add(
CommentGen(parent,
" Deallocate basis arrays"))
3505 func_space_var_names = set()
3508 if basis_fn[
"type"] ==
"basis":
3509 basis_name =
"gh_basis"
3510 elif basis_fn[
"type"] ==
"diff-basis":
3511 basis_name =
"gh_diff_basis"
3514 f
"Unrecognised type of basis function: "
3515 f
"'{basis_fn['''type''']}'. Should be one of 'basis' or "
3517 for fspace
in basis_fn[
"nodal_fspaces"]:
3518 op_name = basis_fn[
"fspace"].\
3519 get_operator_name(basis_name,
3520 qr_var=basis_fn[
"qr_var"],
3522 func_space_var_names.add(op_name)
3524 if func_space_var_names:
3526 parent.add(
DeallocateGen(parent, sorted(func_space_var_names)))
3531 Manages declarations and initialisation of quantities required by
3532 kernels that need boundary condition information.
3534 :param node: the Invoke or Kernel stub for which we are to handle \
3535 any boundary conditions.
3536 :type node: :py:class:`psyclone.dynamo0p3.LFRicInvoke` or \
3537 :py:class:`psyclone.domain.lfric.LFRicKern`
3539 :raises GenerationError: if a kernel named "enforce_bc_code" is found \
3540 but does not have an argument on ANY_SPACE_1.
3541 :raises GenerationError: if a kernel named "enforce_operator_bc_code" is \
3542 found but does not have exactly one argument.
3546 BoundaryDofs = namedtuple(
"BoundaryDofs", [
"argument",
"function_space"])
3548 def __init__(self, node):
3549 super().__init__(node)
3557 MetadataToArgumentsRules)
3558 for call
in self.
_calls_calls:
3559 if MetadataToArgumentsRules.bc_kern_regex.match(call.name):
3561 for fspace
in call.arguments.unique_fss:
3562 if fspace.orig_name ==
"any_space_1":
3567 "The enforce_bc_code kernel must have an argument on "
3568 "ANY_SPACE_1 but failed to find such an argument.")
3569 farg = call.arguments.get_arg_on_space(bc_fs)
3571 elif call.name.lower() ==
"enforce_operator_bc_code":
3573 if len(call.arguments.args) != 1:
3575 f
"The enforce_operator_bc_code kernel must have "
3576 f
"exactly one argument but found "
3577 f
"{len(call.arguments.args)}")
3578 op_arg = call.arguments.args[0]
3579 bc_fs = op_arg.function_space_to
3582 def _invoke_declarations(self, parent):
3584 Add declarations for any boundary-dofs arrays required by an Invoke.
3586 :param parent: node in the PSyIR to which to add declarations.
3587 :type parent: :py:class:`psyclone.psyir.nodes.Node`
3590 api_config = Config.get().api_conf(
"dynamo0.3")
3593 name =
"boundary_dofs_" + dofs.argument.name
3594 parent.add(
DeclGen(parent, datatype=
"integer",
3595 kind=api_config.default_kind[
"integer"],
3597 entity_decls=[name+
"(:,:) => null()"]))
3599 def _stub_declarations(self, parent):
3601 Add declarations for any boundary-dofs arrays required by a kernel.
3603 :param parent: node in the PSyIR to which to add declarations.
3604 :type parent: :py:class:`psyclone.psyir.nodes.Node`
3607 api_config = Config.get().api_conf(
"dynamo0.3")
3610 name =
"boundary_dofs_" + dofs.argument.name
3611 ndf_name = dofs.function_space.ndf_name
3612 parent.add(
DeclGen(parent, datatype=
"integer",
3613 kind=api_config.default_kind[
"integer"],
3615 dimension=
",".join([ndf_name,
"2"]),
3616 entity_decls=[name]))
3620 Initialise any boundary-dofs arrays required by an Invoke.
3622 :param parent: node in PSyIR to which to add declarations.
3623 :type parent: :py:class:`psyclone.psyir.nodes.Node`
3627 name =
"boundary_dofs_" + dofs.argument.name
3629 parent, pointer=
True, lhs=name,
3630 rhs=
"%".join([dofs.argument.proxy_name,
3631 dofs.argument.ref_name(dofs.function_space),
3632 "get_boundary_dofs()"])))
3636 ''' The Dynamo specific InvokeSchedule sub-class. This passes the Dynamo-
3637 specific factories for creating kernel and infrastructure calls
3638 to the base class so it creates the ones we require.
3640 :param str name: name of the Invoke.
3641 :param arg: list of KernelCalls parsed from the algorithm layer.
3642 :type arg: list of :py:class:`psyclone.parse.algorithm.KernelCall`
3643 :param reserved_names: optional list of names that are not allowed in the \
3644 new InvokeSchedule SymbolTable.
3645 :type reserved_names: list of str
3646 :param parent: the parent of this node in the PSyIR.
3647 :type parent: :py:class:`psyclone.psyir.nodes.Node`
3651 def __init__(self, name, arg, reserved_names=None, parent=None):
3652 super().__init__(name, LFRicKernCallFactory,
3653 LFRicBuiltInCallFactory, arg, reserved_names,
3657 ''' Creates a text summary of this node.
3659 :param bool colour: whether or not to include control codes for colour.
3661 :returns: text summary of this node, optionally with control codes \
3662 for colour highlighting.
3666 return (self.coloured_name(colour) +
"[invoke='" + self.
invokeinvokeinvoke.name +
3667 "', dm=" + str(Config.get().distributed_memory)+
"]")
3672 Dynamo specific global sum class which can be added to and
3673 manipulated in a schedule.
3675 :param scalar: the kernel argument for which to perform a global sum.
3676 :type scalar: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
3677 :param parent: the parent node of this node in the PSyIR.
3678 :type parent: :py:class:`psyclone.psyir.nodes.Node`
3680 :raises GenerationError: if distributed memory is not enabled.
3681 :raises InternalError: if the supplied argument is not a scalar.
3682 :raises GenerationError: if the scalar is not of "real" intrinsic type.
3685 def __init__(self, scalar, parent=None):
3687 if not Config.get().distributed_memory:
3689 "It makes no sense to create a DynGlobalSum object when "
3690 "distributed memory is not enabled (dm=False).")
3692 if not scalar.is_scalar:
3694 f
"DynGlobalSum.init(): A global sum argument should be a "
3695 f
"scalar but found argument of type '{scalar.argument_type}'.")
3698 if scalar.intrinsic_type !=
"real":
3700 f
"DynGlobalSum currently only supports real scalars, but "
3701 f
"argument '{scalar.name}' in Kernel '{scalar.call.name}' has "
3702 f
"'{scalar.intrinsic_type}' intrinsic type.")
3704 super().__init__(scalar, parent=parent)
3708 Dynamo-specific code generation for this class.
3710 :param parent: f2pygen node to which to add AST nodes.
3711 :type parent: :py:class:`psyclone.f2pygen.SubroutineGen`
3714 name = self.
_scalar_scalar.name
3717 sum_name = self.ancestor(InvokeSchedule).symbol_table.\
3718 find_or_create_tag(
"global_sum").name
3719 sum_type = self.
_scalar_scalar.data_type
3720 sum_mod = self.
_scalar_scalar.module_name
3721 parent.add(
UseGen(parent, name=sum_mod, only=
True,
3722 funcnames=[sum_type]))
3724 entity_decls=[sum_name]))
3725 parent.add(
AssignGen(parent, lhs=sum_name+
"%value", rhs=name))
3726 parent.add(
AssignGen(parent, lhs=name, rhs=sum_name+
"%get_sum()"))
3729 def _create_depth_list(halo_info_list, sym_table):
3730 '''Halo exchanges may have more than one dependency. This method
3731 simplifies multiple dependencies to remove duplicates and any
3732 obvious redundancy. For example, if one dependency is for depth=1
3733 and another for depth=2 then we do not need the former as it is
3734 covered by the latter. Similarly, if we have a depth=extent+1 and
3735 another for depth=extent+2 then we do not need the former as it is
3736 covered by the latter. It also takes into account
3737 needs_clean_outer, which indicates whether the outermost halo
3738 needs to be clean (and therefore whether there is a dependence).
3740 :param halo_info_list: a list containing halo access information \
3741 derived from all read fields dependent on this halo exchange.
3742 :type: :func:`list` of :py:class:`psyclone.dynamo0p3.HaloReadAccess`
3743 :param sym_table: the symbol table of the enclosing InvokeSchedule.
3744 :type sym_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
3746 :returns: a list containing halo depth information derived from \
3747 the halo access information.
3748 :rtype: :func:`list` of :py:class:`psyclone.dynamo0p3.HaloDepth`
3752 depth_info_list = []
3756 for halo_info
in halo_info_list:
3757 if not (halo_info.annexed_only
or
3758 (halo_info.literal_depth == 1
3759 and not halo_info.needs_clean_outer)):
3764 annexed_only =
False
3768 depth_info.set_by_value(max_depth=
False, var_depth=
"",
3769 literal_depth=1, annexed_only=
True,
3775 max_depth_m1 =
False
3776 for halo_info
in halo_info_list:
3777 if halo_info.max_depth:
3778 if halo_info.needs_clean_outer:
3782 depth_info.set_by_value(max_depth=
True, var_depth=
"",
3783 literal_depth=0, annexed_only=
False,
3792 depth_info.set_by_value(max_depth=
False, var_depth=
"",
3793 literal_depth=0, annexed_only=
False,
3795 depth_info_list.append(depth_info)
3797 for halo_info
in halo_info_list:
3800 if halo_info.max_depth
and not halo_info.needs_clean_outer:
3802 var_depth = halo_info.var_depth
3803 literal_depth = halo_info.literal_depth
3804 if literal_depth
and not halo_info.needs_clean_outer:
3810 for depth_info
in depth_info_list:
3811 if depth_info.var_depth == var_depth
and not match:
3818 depth_info.literal_depth = max(
3819 depth_info.literal_depth, literal_depth)
3825 if var_depth
or literal_depth > 0:
3827 depth_info.set_by_value(max_depth=
False, var_depth=var_depth,
3828 literal_depth=literal_depth,
3829 annexed_only=
False, max_depth_m1=
False)
3830 depth_info_list.append(depth_info)
3831 return depth_info_list
3836 '''Dynamo specific halo exchange class which can be added to and
3837 manipulated in a schedule.
3839 :param field: the field that this halo exchange will act on
3840 :type field: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
3841 :param check_dirty: optional argument default True indicating \
3842 whether this halo exchange should be subject to a run-time check \
3843 for clean/dirty halos.
3844 :type check_dirty: bool
3845 :param vector_index: optional vector index (default None) to \
3846 identify which index of a vector field this halo exchange is \
3848 :type vector_index: int
3849 :param parent: optional PSyIRe parent node (default None) of this \
3851 :type parent: :py:class:`psyclone.psyir.nodes.Node`
3854 def __init__(self, field, check_dirty=True,
3855 vector_index=None, parent=None):
3856 HaloExchange.__init__(self, field, check_dirty=check_dirty,
3857 vector_index=vector_index, parent=parent)
3861 def _compute_stencil_type(self):
3862 '''Dynamically work out the type of stencil required for this halo
3863 exchange as it could change as transformations are applied to
3864 the schedule. If all stencil accesses are of the same type then we
3865 return that stencil, otherwise we return the "region" stencil
3866 type (as it is safe for all stencils).
3868 :return: the type of stencil required for this halo exchange
3876 trial_stencil = halo_info_list[0].stencil_type
3877 for halo_info
in halo_info_list:
3881 if halo_info.stencil_type != trial_stencil:
3883 return trial_stencil
3885 def _compute_halo_depth(self):
3886 '''Dynamically determine the depth of the halo for this halo exchange,
3887 as the depth can change as transformations are applied to the
3890 :return: the halo exchange depth as a Fortran string
3900 if len(depth_info_list) == 1:
3901 return str(depth_info_list[0])
3905 depth_str_list = [str(depth_info)
for depth_info
in
3907 return "max("+
",".join(depth_str_list)+
")"
3909 def _psyir_depth_expression(self):
3911 :returns: the PSyIR expression to compute the halo depth.
3912 :rtype: :py:class:`psyclone.psyir.nodes.Node`
3915 if len(depth_info_list) == 1:
3916 return depth_info_list[0].psyir_expression()
3918 return IntrinsicCall.create(
3919 IntrinsicCall.Intrinsic.MAX,
3920 [depth.psyir_expression()
for depth
in depth_info_list])
3922 def _compute_halo_read_depth_info(self, ignore_hex_dep=False):
3923 '''Take a list of `psyclone.dynamo0p3.HaloReadAccess` objects and
3924 create an equivalent list of `psyclone.dynamo0p3.HaloDepth`
3925 objects. Whilst doing this we simplify the
3926 `psyclone.dynamo0p3.HaloDepth` list to remove redundant depth
3927 information e.g. depth=1 is not required if we have a depth=2.
3928 If the optional ignore_hex_dep argument is set to True then
3929 any read accesses contained in halo exchange nodes are
3930 ignored. This option can therefore be used to filter out any
3931 halo exchange dependencies and only return non-halo exchange
3932 dependencies if and when required.
3934 :param bool ignore_hex_dep: if True then ignore any read \
3935 accesses contained in halo exchanges. This is an optional \
3936 argument that defaults to False.
3938 :return: a list containing halo depth information derived from \
3939 all fields dependent on this halo exchange.
3940 :rtype: :func:`list` of :py:class:`psyclone.dynamo0p3.HaloDepth`
3946 depth_info_list = _create_depth_list(halo_info_list,
3948 return depth_info_list
3950 def _compute_halo_read_info(self, ignore_hex_dep=False):
3951 '''Dynamically computes all halo read dependencies and returns the
3952 required halo information (i.e. halo depth and stencil type)
3953 in a list of HaloReadAccess objects. If the optional
3954 ignore_hex_dep argument is set to True then any read accesses
3955 contained in halo exchange nodes are ignored. This option can
3956 therefore be used to filter out any halo exchange dependencies
3957 and only return non-halo exchange dependencies if and when
3960 :param bool ignore_hex_dep: if True then ignore any read \
3961 accesses contained in halo exchanges. This is an optional \
3962 argument that defaults to False.
3964 :return: a list containing halo information for each read dependency.
3965 :rtype: :func:`list` of :py:class:`psyclone.dynamo0p3.HaloReadAccess`
3967 :raises InternalError: if there is more than one read \
3968 dependency associated with a halo exchange.
3969 :raises InternalError: if there is a read dependency \
3970 associated with a halo exchange and it is not the last \
3971 entry in the read dependency list.
3972 :raises GenerationError: if there is a read dependency \
3973 associated with an asynchronous halo exchange.
3974 :raises InternalError: if no read dependencies are found.
3977 read_dependencies = self.
fieldfield.forward_read_dependencies()
3978 hex_deps = [dep
for dep
in read_dependencies
3979 if isinstance(dep.call, LFRicHaloExchange)]
3984 if any(dep
for dep
in hex_deps
3985 if isinstance(dep.call, (LFRicHaloExchangeStart,
3986 LFRicHaloExchangeEnd))):
3988 "Please perform redundant computation transformations "
3989 "before asynchronous halo exchange transformations.")
3993 if len(hex_deps) != 1:
3995 f
"There should only ever be at most one read dependency "
3996 f
"associated with a halo exchange in the read dependency "
3997 f
"list, but found {len(hex_deps)} for field "
3998 f
"{self.field.name}.")
4001 if not isinstance(read_dependencies[-1].call, LFRicHaloExchange):
4003 "If there is a read dependency associated with a halo "
4004 "exchange in the list of read dependencies then it should "
4005 "be the last one in the list.")
4009 del read_dependencies[-1]
4011 if not read_dependencies:
4013 "Internal logic error. There should be at least one read "
4014 "dependence for a halo exchange.")
4016 read_dependency
in read_dependencies]
4018 def _compute_halo_write_info(self):
4019 '''Determines how much of the halo has been cleaned from any previous
4020 redundant computation
4022 :return: a HaloWriteAccess object containing the required \
4023 information, or None if no dependence information is found.
4024 :rtype: :py:class:`psyclone.dynamo0p3.HaloWriteAccess` or None
4025 :raises GenerationError: if more than one write dependence is \
4026 found for this halo exchange as this should not be possible
4029 write_dependencies = self.
fieldfield.backward_write_dependencies()
4030 if not write_dependencies:
4033 if len(write_dependencies) > 1:
4035 f
"Internal logic error. There should be at most one write "
4036 f
"dependence for a halo exchange. Found "
4037 f
"'{len(write_dependencies)}'")
4041 '''Determines whether this halo exchange is definitely required
4042 ``(True, True)``, might be required ``(True, False)`` or is definitely
4043 not required ``(False, *)``.
4045 If the optional ignore_hex_dep argument is set to True then
4046 any read accesses contained in halo exchange nodes are
4047 ignored. This option can therefore be used to filter out any
4048 halo exchange dependencies and only consider non-halo exchange
4049 dependencies if and when required.
4051 Whilst a halo exchange is generally only ever added if it is
4052 required, or if it may be required, this situation can change
4053 if redundant computation transformations are applied. The
4054 first argument can be used to remove such halo exchanges if
4057 When the first return value is True, the second return value
4058 can be used to see if we need to rely on the runtime
4059 (set_dirty and set_clean calls) and therefore add a
4060 check_dirty() call around the halo exchange or whether we
4061 definitely know that this halo exchange is required.
4063 This routine assumes that a stencil size provided via a
4064 variable may take the value 0. If a variables value is
4065 constrained to be 1, or more, then the logic for deciding
4066 whether a halo exchange is definitely required should be
4067 updated. Note, the routine would still be correct as is, it
4068 would just return more unknown results than it should).
4070 :param bool ignore_hex_dep: if True then ignore any read \
4071 accesses contained in halo exchanges. This is an optional \
4072 argument that defaults to False.
4074 :returns: (x, y) where x specifies whether this halo \
4075 exchange is (or might be) required - True, or is not \
4076 required - False. If the first tuple item is True then the \
4077 second argument specifies whether we definitely know that \
4078 we need the HaloExchange - True, or are not sure - False.
4079 :rtype: (bool, bool)
4093 if Config.get().api_conf(
"dynamo0.3").compute_annexed_dofs
and \
4094 len(required_clean_info) == 1
and \
4095 required_clean_info[0].annexed_only:
4103 return required, known
4111 return required, known
4113 if clean_info.max_depth:
4114 if not clean_info.dirty_outer:
4121 if required_clean_info[0].max_depth:
4132 return required, known
4136 if not clean_info.literal_depth:
4142 return required, known
4144 if clean_info.literal_depth == 1
and clean_info.dirty_outer:
4147 if len(required_clean_info) == 1
and \
4148 required_clean_info[0].annexed_only:
4159 return required, known
4165 clean_depth = clean_info.literal_depth
4166 if clean_info.dirty_outer:
4176 if len(required_clean_info) > 1:
4177 for required_clean
in required_clean_info:
4178 if required_clean.literal_depth > clean_depth:
4181 return required, known
4188 if len(required_clean_info) == 1:
4190 if required_clean_info[0].var_depth
or \
4191 required_clean_info[0].max_depth:
4197 required_clean_depth = required_clean_info[0].literal_depth
4198 if clean_depth < required_clean_depth:
4206 return required, known
4213 return required, known
4216 ''' Creates a text summary of this HaloExchange node.
4218 :param bool colour: whether or not to include control codes for colour.
4220 :returns: text summary of this node, optionally with control codes \
4221 for colour highlighting.
4226 runtime_check =
not known
4227 field_id = self.
_field_field.name
4229 field_id += f
"({self.vector_index})"
4230 return (f
"{self.coloured_name(colour)}[field='{field_id}', "
4231 f
"type='{self._compute_stencil_type()}', "
4232 f
"depth={self._compute_halo_depth()}, "
4233 f
"check_dirty={runtime_check}]")
4236 '''Dynamo specific code generation for this class.
4238 :param parent: an f2pygen object that will be the parent of \
4239 f2pygen objects created in this method
4240 :type parent: :py:class:`psyclone.f2pygen.BaseGen`
4247 :returns: this node lowered to language-level PSyIR.
4248 :rtype: :py:class:`psyclone.psyir.nodes.Node`
4250 symbol = DataSymbol(self.
_field_field.proxy_name, UnresolvedType())
4257 if_condition = Call.create(
4258 ArrayOfStructuresReference.create(symbol, [idx], [
'is_dirty']),
4259 [(
'depth', depth_expr)])
4260 if_body = Call.create(
4261 ArrayOfStructuresReference.create(
4262 symbol, [idx.copy()], [method]),
4263 [(
'depth', depth_expr.copy())])
4265 if_condition = Call.create(
4266 StructureReference.create(symbol, [
'is_dirty']),
4267 [(
'depth', depth_expr)])
4268 if_body = Call.create(
4269 StructureReference.create(symbol, [method]),
4270 [(
'depth', depth_expr.copy())])
4275 haloex = IfBlock.create(if_condition, [if_body])
4279 self.replace_with(haloex)
4284 '''The start of an asynchronous halo exchange. This is similar to a
4285 regular halo exchange except that the Fortran name of the call is
4286 different and the routine only reads the data being transferred
4287 (the associated field is specified as having a read access). As a
4288 result this class is not able to determine some important
4289 properties (such as whether the halo exchange is known to be
4290 required or not). This is solved by finding the corresponding
4291 asynchronous halo exchange end (a halo exchange start always has a
4292 corresponding halo exchange end and vice versa) and calling its
4293 methods (a halo exchange end is specified as having readwrite
4294 access to its associated field and therefore is able to determine
4295 the required properties).
4297 :param field: the field that this halo exchange will act on
4298 :type field: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
4299 :param check_dirty: optional argument (default True) indicating \
4300 whether this halo exchange should be subject to a run-time check \
4301 for clean/dirty halos.
4302 :type check_dirty: bool
4303 :param vector_index: optional vector index (default None) to \
4304 identify which component of a vector field this halo exchange is \
4306 :type vector_index: int
4307 :param parent: optional PSyIRe parent node (default None) of this \
4309 :type parent: :py:class:`psyclone.psyir.nodes.Node`
4313 _text_name =
"HaloExchangeStart"
4316 def __init__(self, field, check_dirty=True,
4317 vector_index=None, parent=None):
4318 LFRicHaloExchange.__init__(self, field, check_dirty=check_dirty,
4319 vector_index=vector_index, parent=parent)
4323 self.
_field_field.access = AccessType.READ
4327 def _compute_stencil_type(self):
4328 '''Call the required method in the corresponding halo exchange end
4329 object. This is done as the field in halo exchange start is
4330 only read and the dependence analysis beneath this call
4331 requires the field to be modified.
4333 :return: Return the type of stencil required for this pair of \
4339 return self.
_get_hex_end_get_hex_end()._compute_stencil_type()
4341 def _compute_halo_depth(self):
4342 '''Call the required method in the corresponding halo exchange end
4343 object. This is done as the field in halo exchange start is
4344 only read and the dependence analysis beneath this call
4345 requires the field to be modified.
4347 :return: Return the halo exchange depth as a Fortran string
4352 return self.
_get_hex_end_get_hex_end()._compute_halo_depth()
4354 def _psyir_depth_expression(self):
4356 Call the required method in the corresponding halo exchange end
4357 object. This is done as the field in halo exchange start is
4358 only read and the dependence analysis beneath this call
4359 requires the field to be modified.
4361 :returns: the PSyIR expression to compute the halo depth.
4362 :rtype: :py:class:`psyclone.psyir.nodes.Node`
4364 return self.
_get_hex_end_get_hex_end()._psyir_depth_expression()
4367 '''Call the required method in the corresponding halo exchange end
4368 object. This is done as the field in halo exchange start is
4369 only read and the dependence analysis beneath this call
4370 requires the field to be modified.
4372 :returns: (x, y) where x specifies whether this halo exchange \
4373 is (or might be) required - True, or is not required \
4374 - False. If the first tuple item is True then the second \
4375 argument specifies whether we definitely know that we need \
4376 the HaloExchange - True, or are not sure - False.
4377 :rtype: (bool, bool)
4382 def _get_hex_end(self):
4383 '''An internal helper routine for this class which finds the halo
4384 exchange end object corresponding to this halo exchange start
4385 object or raises an exception if one is not found.
4387 :return: The corresponding halo exchange end object
4388 :rtype: :py:class:`psyclone.dynamo0p3.LFRicHaloExchangeEnd`
4389 :raises GenerationError: If no matching HaloExchangeEnd is \
4390 found, or if the first matching haloexchange that is found is \
4391 not a HaloExchangeEnd
4396 for node
in self.following():
4397 if self.sameParent(node)
and isinstance(node, LFRicHaloExchange):
4403 if access.overlaps(node.field):
4404 if isinstance(node, LFRicHaloExchangeEnd):
4407 f
"Halo exchange start for field '{self.field.name}' "
4408 f
"should match with a halo exchange end, but found "
4414 f
"Halo exchange start for field '{self.field.name}' has no "
4415 f
"matching halo exchange end")
4419 '''The end of an asynchronous halo exchange. This is similar to a
4420 regular halo exchange except that the Fortran name of the call is
4421 different and the routine only writes to the data being
4424 :param field: the field that this halo exchange will act on
4425 :type field: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
4426 :param check_dirty: optional argument (default True) indicating \
4427 whether this halo exchange should be subject to a run-time check \
4428 for clean/dirty halos.
4429 :type check_dirty: bool
4430 :param vector_index: optional vector index (default None) to \
4431 identify which index of a vector field this halo exchange is \
4433 :type vector_index: int
4434 :param parent: optional PSyIRe parent node (default None) of this \
4436 :type parent: :py:class:`psyclone.psyir.nodes.Node`
4440 _text_name =
"HaloExchangeEnd"
4443 def __init__(self, field, check_dirty=True,
4444 vector_index=None, parent=None):
4445 LFRicHaloExchange.__init__(self, field, check_dirty=check_dirty,
4446 vector_index=vector_index, parent=parent)
4451 self.
_field_field.access = AccessType.READWRITE
4457 '''Determines how much of the halo a read to a field accesses (the
4460 :param sym_table: the symbol table of the enclosing InvokeSchedule.
4461 :type sym_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
4464 def __init__(self, sym_table):
4498 :returns: True if only annexed dofs are accessed in the halo and \
4507 :returns: True if the read to the field is known to access all \
4508 of the halo and False otherwise.
4515 '''Returns whether the read to the field is known to access all of the
4516 halo except the outermost level or not.
4518 :returns: True if the read to the field is known to access all \
4519 of the halo except the outermost and False otherwise.
4527 '''Returns the name of the variable specifying the depth of halo
4528 access if one is provided. Note, a variable will only be provided for
4529 stencil accesses. Also note, this depth should be added to the
4530 literal_depth to find the total depth.
4532 :returns: a variable name specifying the halo access depth \
4533 if one exists, and None if not.
4541 '''Returns the known fixed (literal) depth of halo access. Note, this
4542 depth should be added to the var_depth to find the total depth.
4544 :returns: the known fixed (literal) halo access depth.
4550 @literal_depth.setter
4552 ''' Set the known fixed (literal) depth of halo access.
4554 :param int value: Set the known fixed (literal) halo access depth.
4562 '''Set halo depth information directly
4564 :param bool max_depth: True if the field accesses all of the \
4565 halo and False otherwise
4566 :param str var_depth: A variable name specifying the halo \
4567 access depth, if one exists, and None if not
4568 :param int literal_depth: The known fixed (literal) halo \
4570 :param bool annexed_only: True if only the halo's annexed dofs \
4571 are accessed and False otherwise
4572 :param bool max_depth_m1: True if the field accesses all of \
4573 the halo but does not require the outermost halo to be correct \
4584 '''return the depth of a halo dependency
4588 max_depth = self.
_symbol_table_symbol_table.lookup_with_tag(
4589 "max_halo_depth_mesh")
4590 depth_str += max_depth.name
4592 max_depth = self.
_symbol_table_symbol_table.lookup_with_tag(
4593 "max_halo_depth_mesh")
4594 depth_str += f
"{max_depth.name}-1"
4600 depth_str += f
"+{self.literal_depth}"
4608 :returns: the PSyIR expression representing this HaloDepth.
4609 :rtype: :py:class:`psyclone.psyir.nodes.Node`
4612 max_depth = self.
_symbol_table_symbol_table.lookup_with_tag(
4613 "max_halo_depth_mesh")
4614 return Reference(max_depth)
4616 max_depth = self.
_symbol_table_symbol_table.lookup_with_tag(
4617 "max_halo_depth_mesh")
4618 return BinaryOperation.create(
4619 BinaryOperation.Operator.SUB,
4620 Reference(max_depth),
4621 Literal(
'1', INTEGER_TYPE))
4625 return BinaryOperation.create(
4626 BinaryOperation.Operator.ADD,
4628 Literal(f
"{self.literal_depth}", INTEGER_TYPE))
4634 def halo_check_arg(field, access_types):
4636 Support function which performs checks to ensure the first argument
4637 is a field, that the field is contained within Kernel or Builtin
4638 call and that the field is accessed in one of the ways specified
4639 by the second argument. If no error is reported it returns the
4640 call object containing this argument.
4642 :param field: the argument object we are checking
4643 :type field: :py:class:`psyclone.dynamo0p3.DynArgument`
4644 :param access_types: List of allowed access types.
4645 :type access_types: List of :py:class:`psyclone.psyGen.AccessType`.
4646 :return: the call containing the argument object
4647 :rtype: sub-class of :py:class:`psyclone.psyGen.Kern`
4649 :raises GenerationError: if the first argument to this function is \
4651 :raises GenerationError: if the first argument is not accessed in one of \
4652 the ways specified by the second argument to the function.
4653 :raises GenerationError: if the first argument is not contained \
4654 within a call object.
4660 except AttributeError
as err:
4662 f
"HaloInfo class expects an argument of type DynArgument, or "
4663 f
"equivalent, on initialisation, but found, "
4664 f
"'{type(field)}'")
from err
4666 if field.access
not in access_types:
4667 api_strings = [access.api_specific_name()
for access
in access_types]
4669 f
"In HaloInfo class, field '{field.name}' should be one of "
4670 f
"{api_strings}, but found '{field.access.api_specific_name()}'")
4671 if not isinstance(call, (LFRicBuiltIn, LFRicKern)):
4673 f
"In HaloInfo class, field '{field.name}' should be from a call "
4674 f
"but found {type(call)}")
4679 '''Determines how much of a field's halo is written to (the halo depth)
4680 when a field is accessed in a particular kernel within a
4681 particular loop nest.
4683 :param field: the field that we are concerned with.
4684 :type field: :py:class:`psyclone.dynamo0p3.DynArgument`
4685 :param sym_table: the symbol table associated with the scoping region \
4686 that contains this halo access.
4687 :type sym_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
4690 def __init__(self, field, sym_table):
4691 HaloDepth.__init__(self, sym_table)
4696 '''Returns True if the writer is continuous and accesses the halo and
4697 False otherwise. It indicates that the outer level of halo that has
4698 been written to is actually dirty (well to be precise it is a partial
4701 :returns: True if the outer layer of halo that is written \
4702 to remains dirty and False otherwise.
4708 def _compute_from_field(self, field):
4709 '''Internal method to compute what parts of a field's halo are written
4710 to in a certain kernel and loop. The information computed is
4711 the depth of access and validity of the data after
4712 writing. The depth of access can be the maximum halo depth or
4713 a literal depth and the outer halo layer that is written to
4714 may be dirty or clean.
4716 :param field: the field that we are concerned with.
4717 :type field: :py:class:`psyclone.dynamo0p3.DynArgument`
4722 call = halo_check_arg(field, AccessType.all_write_accesses())
4725 loop = call.parent.parent
4730 not field.discontinuous
and
4731 loop.iteration_space ==
"cell_column" and
4732 loop.upper_bound_name
in const.HALO_ACCESS_LOOP_BOUNDS)
4735 if loop.upper_bound_name
in const.HALO_ACCESS_LOOP_BOUNDS:
4737 if loop.upper_bound_halo_depth:
4739 depth = loop.upper_bound_halo_depth
4746 if call.is_intergrid
and field.mesh ==
"gh_fine":
4757 HaloDepth.set_by_value(self, max_depth,
None, depth,
False,
False)
4761 '''Determines how much of a field's halo is read (the halo depth) and
4762 additionally the access pattern (the stencil) when a field is
4763 accessed in a particular kernel within a particular loop nest.
4765 :param field: the field for which we want information.
4766 :type field: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
4767 :param sym_table: the symbol table associated with the scoping region \
4768 that contains this halo access.
4769 :type sym_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
4772 def __init__(self, field, sym_table):
4773 HaloDepth.__init__(self, sym_table)
4780 '''Returns False if the reader has a gh_inc access and accesses the
4781 halo. Otherwise returns True. Indicates that the outer level
4782 of halo that has been read does not need to be clean (although
4783 any annexed dofs do).
4785 :return: Returns False if the outer layer of halo that is read \
4786 does not need to be clean and True otherwise.
4794 '''Returns the type of stencil access used by the field(s) in the halo
4795 if one exists. If redundant computation (accessing the full
4796 halo) is combined with a stencil access (potentially accessing
4797 a subset of the halo) then the access is assumed to be full
4798 access (region) for all depths.
4800 :returns: the type of stencil access used or None if there is no \
4807 def _compute_from_field(self, field):
4808 '''Internal method to compute which parts of a field's halo are read
4809 in a certain kernel and loop. The information computed is the
4810 depth of access and the access pattern. The depth of access
4811 can be the maximum halo depth, a variable specifying the depth
4812 and/or a literal depth. The access pattern will only be
4813 specified if the kernel code performs a stencil access on the
4816 :param field: the field that we are concerned with
4817 :type field: :py:class:`psyclone.dynamo0p3.DynArgument`
4824 call = halo_check_arg(field, AccessType.all_read_accesses())
4826 loop = call.ancestor(LFRicLoop)
4840 not (field.access == AccessType.INC
4841 and loop.upper_bound_name
in [
"cell_halo",
4845 if loop.upper_bound_name
in const.HALO_ACCESS_LOOP_BOUNDS:
4847 if loop.upper_bound_halo_depth:
4853 elif loop.upper_bound_name ==
"ncolour":
4856 elif loop.upper_bound_name
in [
"ncells",
"nannexed"]:
4857 if field.descriptor.stencil:
4864 if (field.discontinuous
or call.iterates_over ==
"dof" or
4865 call.all_updates_are_writes):
4878 elif loop.upper_bound_name ==
"ndofs":
4884 f
"Internal error in HaloReadAccess._compute_from_field. Found "
4885 f
"unexpected loop upper bound name '{loop.upper_bound_name}'")
4897 if field.descriptor.stencil:
4901 "redundant computation to max depth with a stencil is "
4903 self.
_stencil_type_stencil_type = field.descriptor.stencil[
'type']
4907 stencil_depth = field.descriptor.stencil[
'extent']
4916 if field.stencil.extent_arg.is_literal():
4918 value_str = field.stencil.extent_arg.text
4925 if call.is_intergrid
and field.mesh ==
"gh_fine":
4933 ''' Provides information about a particular function space used by
4934 a meta-funcs entry in the kernel metadata. '''
4936 def __init__(self, descriptor):
4941 ''' Returns True if a basis function is associated with this
4942 function space, otherwise it returns False. '''
4943 return "gh_basis" in self.
_descriptor_descriptor.operator_names
4947 ''' Returns True if a differential basis function is
4948 associated with this function space, otherwise it returns
4950 return "gh_diff_basis" in self.
_descriptor_descriptor.operator_names
4954 ''' Returns the raw metadata value of this function space. '''
4955 return self.
_descriptor_descriptor.function_space_name
4959 ''' Contains a collection of FSDescriptor objects and methods
4960 that provide information across these objects. We have one
4961 FSDescriptor for each meta-funcs entry in the kernel
4963 # TODO #274 this should actually be named something like
4964 BasisFuncDescriptors as it holds information describing the
4965 basis/diff-basis functions required by a kernel.
4967 :param descriptors: list of objects describing the basis/diff-basis \
4968 functions required by a kernel, as obtained from \
4970 :type descriptors: list of :py:class:`psyclone.DynFuncDescriptor03`.
4973 def __init__(self, descriptors):
4976 for descriptor
in descriptors:
4980 ''' Return True if a descriptor with the specified function
4981 space exists, otherwise return False. '''
4986 if descriptor.fs_name == fspace.orig_name:
4991 ''' Return the descriptor with the specified function space
4992 name. If it does not exist raise an error.'''
4994 if descriptor.fs_name == fspace.orig_name:
4997 f
"FSDescriptors:get_descriptor: there is no descriptor for "
4998 f
"function space {fspace.orig_name}")
5003 :return: the list of Descriptors, one for each of the meta-funcs
5004 entries in the kernel metadata.
5005 :rtype: List of :py:class:`psyclone.dynamo0p3.FSDescriptor`
5010 def check_args(call):
5012 Checks that the kernel arguments provided via the invoke call are
5013 consistent with the information expected, as specified by the
5016 :param call: the object produced by the parser that describes the
5017 kernel call to be checked.
5018 :type call: :py:class:`psyclone.parse.algorithm.KernelCall`
5019 :raises: GenerationError if the kernel arguments in the Algorithm layer
5020 do not match up with the kernel metadata
5023 stencil_arg_count = 0
5024 for arg_descriptor
in call.ktype.arg_descriptors:
5025 if arg_descriptor.stencil:
5026 if not arg_descriptor.stencil[
'extent']:
5028 stencil_arg_count += 1
5029 if arg_descriptor.stencil[
'type'] ==
'xory1d':
5031 stencil_arg_count += 1
5036 qr_arg_count = len(set(call.ktype.eval_shapes).intersection(
5037 set(const.VALID_QUADRATURE_SHAPES)))
5039 expected_arg_count = len(call.ktype.arg_descriptors) + \
5040 stencil_arg_count + qr_arg_count
5042 if expected_arg_count != len(call.args):
5044 f
"error: expected '{expected_arg_count}' arguments in the "
5045 f
"algorithm layer but found '{len(call.args)}'. Expected "
5046 f
"'{len(call.ktype.arg_descriptors)}' standard arguments, "
5047 f
"'{stencil_arg_count}' tencil arguments and '{qr_arg_count}' "
5051 @dataclass(frozen=True)
5054 Provides stencil information about an LFRic kernel argument.
5055 LFRicArgStencil can provide the extent, algorithm argument for the extent,
5056 and the direction argument of a stencil or set any of these properties.
5058 :param name: the name of the stencil.
5059 :param extent: the extent of the stencil if it is known. It will
5060 be known if it is specified in the metadata.
5061 :param extent_arg: the algorithm argument associated with the extent
5062 value if extent was not found in the metadata.
5063 :param direction_arg: the direction argument associated with the
5064 direction of the stencil if the direction of the
5065 stencil is not known.
5069 extent_arg: Any =
None
5070 direction_arg: Any =
None
5075 Provides information about Dynamo kernel call arguments
5076 collectively, as specified by the kernel argument metadata.
5078 :param call: the kernel metadata for which to extract argument info.
5079 :type call: :py:class:`psyclone.parse.KernelCall`
5080 :param parent_call: the kernel-call object.
5081 :type parent_call: :py:class:`psyclone.domain.lfric.LFRicKern`
5082 :param bool check: whether to check for consistency between the \
5083 kernel metadata and the algorithm layer. Defaults to True.
5085 :raises GenerationError: if the kernel metadata specifies stencil extent.
5087 def __init__(self, call, parent_call, check=True):
5093 Arguments.__init__(self, parent_call)
5103 for arg
in call.ktype.arg_descriptors:
5107 if dyn_argument.descriptor.stencil:
5108 if dyn_argument.descriptor.stencil[
'extent']:
5114 stencil_extent_arg = call.args[idx]
5116 if dyn_argument.descriptor.stencil[
'type'] ==
'xory1d':
5119 name=dyn_argument.descriptor.stencil[
'type'],
5120 extent_arg=stencil_extent_arg,
5121 direction_arg=call.args[idx]
5128 name=dyn_argument.descriptor.stencil[
'type'],
5129 extent_arg=stencil_extent_arg
5131 dyn_argument.stencil = stencil
5138 inv_sched = self.
_parent_call_parent_call.ancestor(InvokeSchedule)
5139 if hasattr(inv_sched,
"symbol_table"):
5140 symtab = inv_sched.symbol_table
5150 if not arg.descriptor.stencil:
5152 if not arg.stencil.extent_arg.is_literal():
5153 if arg.stencil.extent_arg.varname:
5156 tag =
"AlgArgs_" + arg.stencil.extent_arg.text
5157 root = arg.stencil.extent_arg.varname
5158 new_name = symtab.find_or_create_tag(tag, root).name
5159 arg.stencil.extent_arg.varname = new_name
5160 if arg.descriptor.stencil[
'type'] ==
'xory1d':
5162 if arg.stencil.direction_arg.varname
and \
5163 arg.stencil.direction_arg.varname
not in \
5164 const.VALID_STENCIL_DIRECTIONS:
5167 tag =
"AlgArgs_" + arg.stencil.direction_arg.text
5168 root = arg.stencil.direction_arg.varname
5169 new_name = symtab.find_or_create_integer_symbol(
5171 arg.stencil.direction_arg.varname = new_name
5173 self.
_dofs_dofs = []
5182 for function_space
in arg.function_spaces:
5186 if function_space
and \
5193 Returns the first argument (field or operator) found that is on
5194 the named function space, as specified in the kernel metadata. Also
5195 returns the associated FunctionSpace object.
5197 :param str func_space_name: Name of the function space (as specified \
5198 in kernel metadata) for which to \
5200 :return: the first kernel argument that is on the named function \
5201 space and the associated FunctionSpace object.
5202 :rtype: (:py:class:`psyclone.dynamo0p3.DynKernelArgument`,
5203 :py:class:`psyclone.domain.lfric.FunctionSpace`)
5204 :raises: FieldNotFoundError if no field or operator argument is found \
5205 for the named function space.
5208 for function_space
in arg.function_spaces:
5210 if func_space_name == function_space.orig_name:
5211 return arg, function_space
5213 f
"there is no field or operator with "
5214 f
"function space {func_space_name}")
5218 Returns the first argument (field or operator) found that is on
5219 the specified function space. The mangled name of the supplied
5220 function space is used for comparison.
5222 :param func_space: The function space for which to find an argument.
5223 :type func_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
5224 :return: the first kernel argument that is on the supplied function
5226 :rtype: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
5227 :raises: FieldNotFoundError if no field or operator argument is found
5228 for the specified function space.
5231 for function_space
in arg.function_spaces:
5233 if func_space.mangled_name == function_space.mangled_name:
5237 f
"is no field or operator with function space"
5238 f
" {func_space.orig_name} (mangled name = "
5239 f
"'{func_space.mangled_name}')")
5242 ''' Returns true if at least one of the arguments is an operator
5243 of type op_type (either gh_operator [LMA] or gh_columnwise_operator
5244 [CMA]). If op_type is None then searches for *any* valid operator
5247 if op_type
and op_type
not in const.VALID_OPERATOR_NAMES:
5249 f
"If supplied, 'op_type' must be a valid operator type (one "
5250 f
"of {const.VALID_OPERATOR_NAMES}) but got '{op_type}'")
5253 op_list = const.VALID_OPERATOR_NAMES
5257 if arg.argument_type
in op_list:
5263 ''' Returns a unique list of function space objects used by the
5264 arguments of this kernel '''
5269 ''' Return the list of unique function space names used by the
5270 arguments of this kernel. The names are unmangled (i.e. as
5271 specified in the kernel metadata) '''
5276 Returns an argument we can use to dereference the iteration
5277 space. This can be a field or operator that is modified or
5278 alternatively a field that is read if one or more scalars
5279 are modified. If a kernel writes to more than one argument then
5280 that requiring the largest iteration space is selected.
5282 :return: Kernel argument from which to obtain iteration space
5283 :rtype: :py:class:`psyclone.dynamo0p3.DynKernelArgument`
5288 write_accesses = AccessType.all_write_accesses()
5290 op_args = psyGen.args_filter(
5292 arg_types=const.VALID_OPERATOR_NAMES,
5293 arg_accesses=write_accesses)
5302 fld_args = psyGen.args_filter(
5304 arg_types=const.VALID_FIELD_NAMES,
5305 arg_meshes=[
"gh_coarse"])
5318 write_accesses = AccessType.all_write_accesses()
5319 fld_args = psyGen.args_filter(
5321 arg_types=const.VALID_FIELD_NAMES,
5322 arg_accesses=write_accesses)
5324 for spaces
in [const.CONTINUOUS_FUNCTION_SPACES,
5325 const.VALID_ANY_SPACE_NAMES,
5326 const.VALID_DISCONTINUOUS_NAMES]:
5327 for arg
in fld_args:
5328 if arg.function_space.orig_name
in spaces:
5332 fld_args = psyGen.args_filter(
5334 arg_types=const.VALID_FIELD_NAMES)
5340 "iteration_space_arg(). The dynamo0.3 api must have a modified "
5341 "field, a modified operator, or an unmodified field (in the case "
5342 "of a modified scalar). None of these were found.")
5346 ''' Currently required for Invoke base class although this
5347 makes no sense for Dynamo. Need to refactor the Invoke base class
5348 and remove the need for this property (#279). '''
5349 return self.
_dofs_dofs
5353 :returns: the PSyIR expressions representing this Argument list.
5354 :rtype: list of :py:class:`psyclone.psyir.nodes.Node`
5358 create_arg_list.generate()
5359 return create_arg_list.psyir_arglist
5364 :returns: the list of quantities that must be available on an \
5365 OpenACC device before the associated kernel can be launched.
5370 create_acc_arg_list.generate()
5371 return create_acc_arg_list.arglist
5376 Provides the list of names of scalar arguments required by the
5377 kernel associated with this Arguments object. If there are none
5378 then the returned list is empty.
5380 :returns: A list of the names of scalar arguments in this object.
5391 This class provides information about individual LFRic kernel call
5392 arguments as specified by the kernel argument metadata and the
5393 kernel invocation in the Algorithm layer.
5395 :param kernel_args: object encapsulating all arguments to the \
5397 :type kernel_args: :py:class:`psyclone.dynamo0p3.DynKernelArguments`
5398 :param arg_meta_data: information obtained from the metadata for \
5399 this kernel argument.
5400 :type arg_meta_data: :py:class:`psyclone.domain.lfric.LFRicArgDescriptor`
5401 :param arg_info: information on how this argument is specified in \
5402 the Algorithm layer.
5403 :type arg_info: :py:class:`psyclone.parse.algorithm.Arg`
5404 :param call: the kernel object with which this argument is associated.
5405 :type call: :py:class:`psyclone.domain.lfric.LFRicKern`
5406 :param bool check: whether to check for consistency between the \
5407 kernel metadata and the algorithm layer. Defaults to True.
5409 :raises InternalError: for an unsupported metadata in the argument \
5410 descriptor data type.
5414 def __init__(self, kernel_args, arg_meta_data, arg_info, call, check=True):
5419 self.
_vector_size_vector_size = arg_meta_data.vector_size
5422 if arg_meta_data.mesh:
5423 self.
_mesh_mesh = arg_meta_data.mesh.lower()
5425 self.
_mesh_mesh =
None
5442 if arg_meta_data.function_space:
5453 arg_meta_data.data_type]
5454 except KeyError
as err:
5456 f
"DynKernelArgument.__init__(): Found unsupported data "
5457 f
"type '{arg_meta_data.data_type}' in the kernel argument "
5458 f
"descriptor '{arg_meta_data}'.")
from err
5465 KernelArgument.__init__(self, arg_meta_data, arg_info, call)
5478 Returns the name used to dereference this type of argument (depends
5479 on whether it is a field or operator and, if the latter, whether it
5480 is the to- or from-space that is specified).
5482 :param function_space: the function space of this argument
5483 :type function_space: :py:class:`psyclone.domain.lfric.FunctionSpace`
5485 :returns: the name used to dereference this argument.
5488 :raises GenerationError: if the supplied function space is not one \
5489 of the function spaces associated with \
5491 :raises GenerationError: if the supplied function space is not being \
5492 returned by either 'function_space_from' or \
5493 'function_space_to'.
5494 :raises GenerationError: if the argument type is not supported.
5498 if not function_space:
5509 if fspace
and fspace.orig_name == function_space.orig_name:
5514 f
"DynKernelArgument.ref_name(fs): The supplied function "
5515 f
"space (fs='{function_space.orig_name}') is not one of "
5516 f
"the function spaces associated with this argument "
5517 f
"(fss={self.function_space_names}).")
5521 if function_space.orig_name == self.
descriptordescriptor.function_space_from:
5523 if function_space.orig_name == self.
descriptordescriptor.function_space_to:
5526 f
"DynKernelArgument.ref_name(fs): Function space "
5527 f
"'{function_space.orig_name}' is one of the 'gh_operator' "
5528 f
"function spaces '{self.function_spaces}' but is not being "
5529 f
"returned by either function_space_from "
5530 f
"'{self.descriptor.function_space_from}' or "
5531 f
"function_space_to '{self.descriptor.function_space_to}'.")
5533 f
"DynKernelArgument.ref_name(fs): Found unsupported argument "
5534 f
"type '{self._argument_type}'.")
5536 def _init_data_type_properties(self, arg_info, check=True):
5537 '''Set up kernel argument information from LFRicConstants: precision,
5538 data type, proxy data type and module name. This is currently
5539 supported for scalar, field and operator arguments.
5541 :param arg_info: information on how this argument is specified \
5542 in the Algorithm layer.
5543 :type arg_info: :py:class:`psyclone.parse.algorithm.Arg`
5544 :param bool check: whether to use the algorithm \
5545 information. Optional argument that defaults to True.
5548 alg_datatype_info =
None
5550 alg_datatype_info = arg_info._datatype
5552 alg_precision =
None
5553 if alg_datatype_info:
5554 alg_datatype, alg_precision = alg_datatype_info
5557 if arg_info
and arg_info.form ==
"collection":
5559 alg_datatype = const.FIELD_VECTOR_TO_FIELD_MAP[alg_datatype]
5573 f
"Supported argument types are scalar, field and operator, "
5574 f
"but the argument '{self.name}' in kernel "
5575 f
"'{self._call.name}' is none of these.")
5577 def _init_scalar_properties(
5578 self, alg_datatype, alg_precision, check=True):
5579 '''Set up the properties of this scalar using algorithm datatype
5580 information if it is available.
5582 :param alg_datatype: the datatype of this argument as \
5583 specified in the algorithm layer or None if it is not \
5585 :type alg_datatype: str or NoneType
5586 :param alg_precision: the precision of this argument as \
5587 specified in the algorithm layer or None if it is not \
5589 :type alg_precision: str or NoneType
5590 :param bool check: whether to use the algorithm \
5591 information. Optional argument that defaults to True.
5593 :raises InternalError: if the intrinsic type of the scalar is \
5595 :raises GenerationError: if the datatype specified in the \
5596 algorithm layer is inconsistent with the kernel metadata.
5597 :raises GenerationError: if the datatype for a gh_scalar \
5598 could not be found in the algorithm layer.
5599 :raises NotImplementedError: if the scalar is a reduction and \
5600 its intrinsic type is not real.
5601 :raises GenerationError: if the scalar is a reduction and is \
5602 not declared with default precision.
5609 f
"Expected one of {const.VALID_INTRINSIC_TYPES} intrinsic "
5610 f
"types for a scalar argument but found "
5611 f
"'{self.intrinsic_type}' in the metadata of kernel "
5612 f
"{self._call.name} for argument {self.name}.")
5616 if check
and alg_datatype
and \
5619 f
"The kernel metadata for argument '{self.name}' in "
5620 f
"kernel '{self._call.name}' specifies this argument "
5621 f
"should be a scalar of type '{self.intrinsic_type}' but "
5622 f
"in the algorithm layer it is defined as a "
5623 f
"'{alg_datatype}'.")
5628 if check
and alg_datatype
and not alg_precision
and \
5631 f
"LFRic coding standards require scalars to have "
5632 f
"their precision defined in the algorithm layer but "
5633 f
"'{self.name}' in '{self._call.name}' does not.")
5644 raise NotImplementedError(
5645 "Reductions for datatypes other than real are not yet "
5646 "supported in PSyclone.")
5648 expected_precision = const.DATA_TYPE_MAP[
"reduction"][
"kind"]
5653 if check
and alg_precision
and \
5654 alg_precision != expected_precision:
5656 f
"This scalar is a reduction which assumes precision "
5657 f
"of type '{expected_precision}' but the algorithm "
5658 f
"declares this scalar with precision "
5659 f
"'{alg_precision}'.")
5665 "reduction"][
"proxy_type"]
5670 if check
and alg_precision:
5681 def _init_field_properties(self, alg_datatype, check=True):
5682 '''Set up the properties of this field using algorithm datatype
5683 information if it is available.
5685 :param alg_datatype: the datatype of this argument as \
5686 specified in the algorithm layer or None if it is not \
5688 :type alg_datatype: str or NoneType
5689 :param bool check: whether to use the algorithm \
5690 information. Optional argument that defaults to True.
5692 :raises GenerationError: if the datatype for a gh_field \
5693 could not be found in the algorithm layer.
5694 :raises GenerationError: if the datatype specified in the \
5695 algorithm layer is inconsistent with the kernel metadata.
5696 :raises InternalError: if the intrinsic type of the field is \
5697 not supported (i.e. is not real or integer).
5704 if check
and not alg_datatype:
5706 f
"It was not possible to determine the field type from "
5707 f
"the algorithm layer for argument '{self.name}' in "
5708 f
"kernel '{self._call.name}'.")
5717 elif alg_datatype ==
"field_type":
5719 elif alg_datatype ==
"r_bl_field_type":
5720 argtype =
"r_bl_field"
5721 elif alg_datatype ==
"r_phys_field_type":
5722 argtype =
"r_phys_field"
5723 elif alg_datatype ==
"r_solver_field_type":
5724 argtype =
"r_solver_field"
5725 elif alg_datatype ==
"r_tran_field_type":
5726 argtype =
"r_tran_field"
5729 f
"The metadata for argument '{self.name}' in kernel "
5730 f
"'{self._call.name}' specifies that this is a real "
5731 f
"field, however it is declared as a "
5732 f
"'{alg_datatype}' in the algorithm code.")
5735 if check
and alg_datatype !=
"integer_field_type":
5737 f
"The metadata for argument '{self.name}' in kernel "
5738 f
"'{self._call.name}' specifies that this is an "
5739 f
"integer field, however it is declared as a "
5740 f
"'{alg_datatype}' in the algorithm code.")
5741 argtype =
"integer_field"
5744 f
"Expected one of {const.VALID_FIELD_INTRINSIC_TYPES} "
5745 f
"intrinsic types for a field argument but found "
5746 f
"'{self.intrinsic_type}'.")
5749 self.
_proxy_data_type_proxy_data_type = const.DATA_TYPE_MAP[argtype][
"proxy_type"]
5752 def _init_operator_properties(self, alg_datatype, check=True):
5753 '''Set up the properties of this operator using algorithm datatype
5754 information if it is available.
5756 :param alg_datatype: the datatype of this argument as \
5757 specified in the algorithm layer or None if it is not \
5759 :type alg_datatype: str or NoneType
5760 :param bool check: whether to use the algorithm \
5761 information. Optional argument that defaults to True.
5762 :raises GenerationError: if the datatype for a gh_operator \
5763 could not be found in the algorithm layer (and check is \
5765 :raises GenerationError: if the datatype specified in the \
5766 algorithm layer is inconsistent with the kernel metadata.
5767 :raises InternalError: if this argument is not an operator.
5775 argtype =
"operator"
5776 elif not alg_datatype:
5781 f
"It was not possible to determine the operator type "
5782 f
"from the algorithm layer for argument '{self.name}' "
5783 f
"in kernel '{self._call.name}'.")
5784 elif alg_datatype ==
"operator_type":
5785 argtype =
"operator"
5786 elif alg_datatype ==
"r_solver_operator_type":
5787 argtype =
"r_solver_operator"
5788 elif alg_datatype ==
"r_tran_operator_type":
5789 argtype =
"r_tran_operator"
5792 f
"The metadata for argument '{self.name}' in kernel "
5793 f
"'{self._call.name}' specifies that this is an "
5794 f
"operator, however it is declared as a "
5795 f
"'{alg_datatype}' in the algorithm code.")
5797 if check
and alg_datatype
and \
5798 alg_datatype !=
"columnwise_operator_type":
5800 f
"The metadata for argument '{self.name}' in kernel "
5801 f
"'{self._call.name}' specifies that this is a "
5802 f
"columnwise operator, however it is declared as a "
5803 f
"'{alg_datatype}' in the algorithm code.")
5804 argtype =
"columnwise_operator"
5807 f
"Expected 'gh_operator' or 'gh_columnwise_operator' "
5808 f
"argument type but found '{self.argument_type}'.")
5811 self.
_proxy_data_type_proxy_data_type = const.DATA_TYPE_MAP[argtype][
"proxy_type"]
5817 :returns: True if this kernel argument represents a scalar, \
5822 return self.
_argument_type_argument_type
in const.VALID_SCALAR_NAMES
5827 :returns: True if this kernel argument represents a field, \
5832 return self.
_argument_type_argument_type
in const.VALID_FIELD_NAMES
5837 :returns: True if this kernel argument represents an operator, \
5842 return self.
_argument_type_argument_type
in const.VALID_OPERATOR_NAMES
5847 :returns: a descriptor object which contains Kernel metadata \
5848 about this argument.
5849 :rtype: :py:class:`psyclone.domain.lfric.LFRicArgDescriptor`
5851 return self.
_arg_arg
5854 def argument_type(self):
5856 :returns: the API type of this argument, as specified in \
5863 def intrinsic_type(self):
5865 :returns: the intrinsic Fortran type of this argument for scalars \
5866 or of the argument's data for fields and operators.
5874 :returns: mesh associated with argument ('GH_FINE' or 'GH_COARSE').
5877 return self.
_mesh_mesh
5882 :returns: the vector size of this argument as specified in \
5883 the Kernel metadata.
5891 :returns: the name for this argument with an additional index \
5892 which accesses the first element for a vector argument.
5896 return self.
_name_name+
"(1)"
5897 return self.
_name_name
5901 Looks up or creates a reference to a suitable Symbol for this kernel
5902 argument. If the argument is a scalar that has been provided as a
5903 literal (in the Algorithm layer) then the PSyIR of the expression
5906 :returns: the PSyIR for this kernel argument.
5907 :rtype: :py:class:`psyclone.psyir.nodes.Node`
5909 :raises InternalError: if this argument is a literal but we fail to \
5910 construct PSyIR that is consistent with this.
5911 :raises NotImplementedError: if this argument is not a literal, scalar
5915 symbol_table = self.
_call_call.scope.symbol_table
5921 symbol_table.add_lfric_precision_symbol(self.
precisionprecision)
5922 lit = reader.psyir_from_expression(self.
namename, symbol_table)
5925 if lit.walk(Reference):
5927 f
"Expected argument '{self.name}' to kernel "
5928 f
"'{self.call.name}' to be a literal but the created "
5929 f
"PSyIR contains one or more References.")
5934 scalar_sym = symbol_table.lookup(self.
namename)
5938 scalar_sym = symbol_table.new_symbol(
5939 self.
namename, symbol_type=DataSymbol,
5941 return Reference(scalar_sym)
5946 tag_name = f
"{self.name}:{suffix}"
5947 sym = symbol_table.lookup_with_tag(tag_name)
5948 return Reference(sym)
5950 except KeyError
as err:
5951 raise NotImplementedError(
5952 f
"Unsupported kernel argument type: '{self.name}' is of type "
5953 f
"'{self.argument_type}' which is not recognised as being a "
5954 f
"literal, scalar or field.")
from err
5959 :returns: the name for this argument with the array dimensions \
5965 return self.
_name_name
5970 :returns: the proxy name for this argument.
5973 return self.
_name_name+
"_proxy"
5978 :returns: the proxy name for this argument with an additional \
5979 index which accesses the first element for a vector \
5984 return self.
_name_name+
"_proxy(1)"
5985 return self.
_name_name+
"_proxy"
5990 :returns: the proxy name for this argument with the array \
5991 dimensions added if required.
6001 :returns: the type of this argument's proxy (if it exists) as \
6002 defined in LFRic infrastructure.
6003 :rtype: str or NoneType
6011 Returns the expected finite element function space for a kernel
6012 argument as specified by the kernel argument metadata: a single
6013 function space for a field and function_space_from for an operator.
6015 :returns: function space for this argument.
6016 :rtype: :py:class:`psyclone.domain.lfric.FunctionSpace`
6026 :returns: the 'to' function space of an operator.
6034 :returns: the 'from' function space of an operator.
6042 Returns the expected finite element function space for a kernel
6043 argument as specified by the kernel argument metadata: a single
6044 function space for a field and a list containing
6045 function_space_to and function_space_from for an operator.
6047 :returns: function space(s) for this argument.
6048 :rtype: list of :py:class:`psyclone.domain.lfric.FunctionSpace`
6056 Returns a list of the names of the function spaces associated
6057 with this argument. We have more than one function space when
6058 dealing with operators.
6060 :returns: list of function space names for this argument.
6067 fs_names.append(fspace.orig_name)
6073 Returns the Fortran intent of this argument as defined by the
6074 valid access types for this API
6076 :returns: the expected Fortran intent for this argument as \
6077 specified by the kernel argument metadata
6081 write_accesses = AccessType.all_write_accesses()
6088 valid_accesses = [AccessType.READ.api_specific_name()] + \
6089 [access.api_specific_name()
for access
in write_accesses]
6091 f
"In the LFRic API the argument access must be one of "
6092 f
"{valid_accesses}, but found '{self.access}'.")
6097 Returns True if this argument is known to be on a discontinuous
6098 function space including any_discontinuous_space, otherwise
6101 :returns: whether the argument is discontinuous.
6107 const.VALID_DISCONTINUOUS_NAMES:
6110 const.VALID_ANY_SPACE_NAMES:
6119 :returns: stencil information for this argument if it exists.
6120 :rtype: :py:class:`psyclone.dynamo0p3.LFRicArgStencil`
6127 Sets stencil information for this kernel argument.
6129 :param value: stencil information for this argument.
6130 :type value: :py:class:`psyclone.dynamo0p3.LFRicArgStencil`
6137 Infer the datatype of this kernel argument in the PSy layer using
6138 the LFRic API rules. If any LFRic infrastructure modules are required
6139 but are not already present then suitable ContainerSymbols are added
6140 to the outermost symbol table. Similarly, DataTypeSymbols are added for
6141 any required LFRic derived types that are not already in the symbol
6144 TODO #1258 - ultimately this routine should not have to create any
6145 DataTypeSymbols as that should already have been done.
6147 :param bool proxy: whether or not we want the type of the proxy \
6148 object for this kernel argument. Defaults to False (i.e.
6149 return the type rather than the proxy type).
6151 :returns: the datatype of this argument.
6152 :rtype: :py:class:`psyclone.psyir.symbols.DataType`
6154 :raises NotImplementedError: if an unsupported argument type is found.
6159 symbol_table = self.
_call_call.scope.symbol_table
6160 root_table = symbol_table
6161 while root_table.parent_symbol_table():
6162 root_table = root_table.parent_symbol_table()
6164 def _find_or_create_type(mod_name, type_name):
6166 Utility to find or create a DataTypeSymbol with the supplied name,
6167 imported from the named module.
6169 :param str mod_name: the name of the module from which the \
6170 DataTypeSymbol should be imported.
6171 :param str type_name: the name of the derived type for which to \
6172 create a DataTypeSymbol.
6174 :returns: the symbol for the requested type.
6175 :rtype: :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
6178 return root_table.find_or_create(
6180 symbol_type=DataTypeSymbol,
6181 datatype=UnresolvedType(),
6182 interface=ImportInterface(root_table.find_or_create(
6184 symbol_type=ContainerSymbol)
6190 prim_type = ScalarType.Intrinsic.REAL
6192 prim_type = ScalarType.Intrinsic.INTEGER
6194 prim_type = ScalarType.Intrinsic.BOOLEAN
6196 raise NotImplementedError(
6197 f
"Unsupported scalar type '{self.intrinsic_type}'")
6201 kind_symbol = symbol_table.lookup(kind_name)
6204 const_mod = mod_map[
"constants"][
"module"]
6206 constants_container = symbol_table.lookup(const_mod)
6212 root_table.add(constants_container)
6213 kind_symbol = DataSymbol(
6214 kind_name, INTEGER_TYPE,
6215 interface=ImportInterface(constants_container))
6216 root_table.add(kind_symbol)
6217 return ScalarType(prim_type, kind_symbol)
6227 return _find_or_create_type(mod_name, type_name)
6229 raise NotImplementedError(
6230 f
"'{str(self)}' is not a scalar, field or operator argument")
6235 Sub-classes ACCEnterDataDirective to provide an API-specific implementation
6236 of data_on_device().
6241 Provide a hook to be able to add information about data being on a
6242 device (or not). This is currently not used in dynamo0p3.
6252 'DynFuncDescriptor03',
6254 'DynFunctionSpaces',
6261 'DynBasisFunctions',
6262 'DynBoundaryConditions',
6263 'DynInvokeSchedule',
6265 'LFRicHaloExchange',
6266 'LFRicHaloExchangeStart',
6267 'LFRicHaloExchangeEnd',
6274 'DynKernelArguments',
6275 'DynKernelArgument',
6276 'DynACCEnterDataDirective']
def data_on_device(self, _)
def basis_first_dim_value(function_space)
def _compute_basis_fns(self, parent)
def _initialise_xoyoz_qr(self, parent)
def initialise(self, parent)
def _basis_fn_declns(self)
def diff_basis_first_dim_name(function_space)
def deallocate(self, parent)
def _setup_basis_fns_for_call(self, call)
def basis_first_dim_name(function_space)
def diff_basis_first_dim_value(function_space)
def _initialise_xyz_qr(self, parent)
def _initialise_face_or_edge_qr(self, parent, qr_type)
def _initialise_xyoz_qr(self, parent)
def initialise(self, parent)
def initialise(self, parent)
def initialise(self, parent)
def function_space_name(self)
def initialise(self, parent)
def gen_code(self, parent)
def last_cell_var_symbol(self)
def ncolours_var_symbol(self)
def set_colour_info(self, colour_map, ncolours, last_cell)
def colourmap_symbol(self)
def node_str(self, colour=True)
def function_spaces(self)
def proxy_declaration_name(self)
def _init_data_type_properties(self, arg_info, check=True)
def _init_scalar_properties(self, alg_datatype, alg_precision, check=True)
def proxy_data_type(self)
def function_space_from(self)
def infer_datatype(self, proxy=False)
def declaration_name(self)
def _init_operator_properties(self, alg_datatype, check=True)
def proxy_name_indexed(self)
def function_space_to(self)
def ref_name(self, function_space=None)
def _init_field_properties(self, alg_datatype, check=True)
def psyir_expression(self)
def function_space_names(self)
def iteration_space_arg(self)
def get_arg_on_space(self, func_space)
def get_arg_on_space_name(self, func_space_name)
def psyir_expressions(self)
def has_operator(self, op_type=None)
def unique_fs_names(self)
def initialise(self, parent)
def intergrid_kernels(self)
def _colourmap_init(self)
def _add_mesh_symbols(self, mesh_tags)
def declarations(self, parent)
def _add_symbol(self, name, tag, intrinsic_type, arg, rank)
def initialise(self, parent)
def initialise(self, parent)
_vert_face_out_normals_symbol
def kern_args_symbols(self)
_horiz_face_normals_symbol
_horiz_face_out_normals_symbol
_vert_face_normals_symbol
def infrastructure_modules(self)
def requires_diff_basis(self)
def get_descriptor(self, fspace)
def psyir_expression(self)
def set_by_value(self, max_depth, var_depth, literal_depth, annexed_only, max_depth_m1)
def literal_depth(self, value)
def _compute_from_field(self, field)
def needs_clean_outer(self)
def _compute_from_field(self, field)
def _compute_halo_read_info(self, ignore_hex_dep=False)
def node_str(self, colour=True)
def _psyir_depth_expression(self)
def gen_code(self, parent)
def _compute_halo_read_depth_info(self, ignore_hex_dep=False)
def lower_to_language_level(self)
def required(self, ignore_hex_dep=False)
def _compute_halo_write_info(self)
def initialise(self, parent)
def kern_args(self, stub=False, var_accesses=None, kern_call_arg_list=None)
def _complete_init(self, arg_info)
def append(self, name, argument_type)
def invoke(self, my_invoke)