From ac19160ba67801a5f89060dace219810939bc954 Mon Sep 17 00:00:00 2001 From: "oakley.brunt" Date: Mon, 8 Jan 2024 14:03:40 +0000 Subject: [PATCH] #2461 Moved LFRicPSy into LFRic domain --- examples/lfric/eg14/acc_parallel.py | 4 +- examples/lfric/eg2/loop_fuse_trans.py | 4 +- examples/lfric/eg2/module_inline_trans.py | 4 +- examples/lfric/eg2/print_psyir_trans.py | 4 +- src/psyclone/domain/lfric/__init__.py | 2 + src/psyclone/domain/lfric/lfric_invokes.py | 2 +- src/psyclone/domain/lfric/lfric_psy.py | 176 ++++++++++++++++++ src/psyclone/dynamo0p3.py | 127 ------------- src/psyclone/psyGen.py | 2 +- ...ic_dynamopsy_test.py => lfric_psy_test.py} | 44 ++--- .../tests/test_files/dynamo0p3/alg_script.py | 4 +- 11 files changed, 212 insertions(+), 161 deletions(-) create mode 100644 src/psyclone/domain/lfric/lfric_psy.py rename src/psyclone/tests/domain/lfric/{lfric_dynamopsy_test.py => lfric_psy_test.py} (86%) diff --git a/examples/lfric/eg14/acc_parallel.py b/examples/lfric/eg14/acc_parallel.py index af06d323fb..914ce49bad 100644 --- a/examples/lfric/eg14/acc_parallel.py +++ b/examples/lfric/eg14/acc_parallel.py @@ -52,10 +52,10 @@ def trans(psy): transformed through the addition of a routine directive. :param psy: the PSy object containing the invokes to transform. - :type psy: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :type psy: :py:class:`psyclone.domain.lfric.LFRicPSy` :returns: the transformed PSy object. - :rtype: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :rtype: :py:class:`psyclone.domain.lfric.LFRicPSy` ''' const = LFRicConstants() diff --git a/examples/lfric/eg2/loop_fuse_trans.py b/examples/lfric/eg2/loop_fuse_trans.py index ff6621ddd5..f836f2abf2 100644 --- a/examples/lfric/eg2/loop_fuse_trans.py +++ b/examples/lfric/eg2/loop_fuse_trans.py @@ -50,10 +50,10 @@ def trans(psy): :param psy: the PSy object that PSyclone has constructed for the \ 'invoke'(s) found in the Algorithm file. - :type psy: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :type psy: :py:class:`psyclone.domain.lfric.LFRicPSy` :returns: the transformed PSy object. - :rtype: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :rtype: :py:class:`psyclone.domain.lfric.LFRicPSy` ''' print(psy.gen) diff --git a/examples/lfric/eg2/module_inline_trans.py b/examples/lfric/eg2/module_inline_trans.py index a5be56ae7f..aee11c1ef7 100644 --- a/examples/lfric/eg2/module_inline_trans.py +++ b/examples/lfric/eg2/module_inline_trans.py @@ -48,10 +48,10 @@ def trans(psy): :param psy: the PSy object that PSyclone has constructed for the \ 'invoke'(s) found in the Algorithm file. - :type psy: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :type psy: :py:class:`psyclone.domain.lfric.LFRicPSy` :returns: the transformed PSy object. - :rtype: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :rtype: :py:class:`psyclone.domain.lfric.LFRicPSy` ''' invokes = psy.invokes diff --git a/examples/lfric/eg2/print_psyir_trans.py b/examples/lfric/eg2/print_psyir_trans.py index 920921810a..61ca8c63f2 100644 --- a/examples/lfric/eg2/print_psyir_trans.py +++ b/examples/lfric/eg2/print_psyir_trans.py @@ -46,10 +46,10 @@ def trans(psy): :param psy: the PSy object that PSyclone has constructed for the \ 'invoke'(s) found in the Algorithm file. - :type psy: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :type psy: :py:class:`psyclone.domain.lfric.LFRicPSy` :returns: the supplied PSy object unmodified. - :rtype: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :rtype: :py:class:`psyclone.domain.lfric.LFRicPSy` ''' print("Supplied code has Invokes: ", psy.invokes.names) diff --git a/src/psyclone/domain/lfric/__init__.py b/src/psyclone/domain/lfric/__init__.py index 74ed7468b8..fc9831a714 100644 --- a/src/psyclone/domain/lfric/__init__.py +++ b/src/psyclone/domain/lfric/__init__.py @@ -71,6 +71,7 @@ from psyclone.domain.lfric.lfric_scalar_args import LFRicScalarArgs from psyclone.domain.lfric.lfric_loop_bounds import LFRicLoopBounds from psyclone.domain.lfric.lfric_kern_metadata import LFRicKernMetadata +from psyclone.domain.lfric.lfric_psy import LFRicPSy __all__ = [ @@ -92,6 +93,7 @@ 'LFRicKernMetadata', 'LFRicLoop', 'LFRicLoopBounds', + 'LFRicPSy', 'LFRicRunTimeChecks', 'LFRicScalarArgs', 'LFRicSymbolTable'] diff --git a/src/psyclone/domain/lfric/lfric_invokes.py b/src/psyclone/domain/lfric/lfric_invokes.py index 54391c6803..e77075850d 100644 --- a/src/psyclone/domain/lfric/lfric_invokes.py +++ b/src/psyclone/domain/lfric/lfric_invokes.py @@ -53,7 +53,7 @@ class LFRicInvokes(Invokes): information. :type alg_calls: List[:py:class:`psyclone.parse.algorithm.InvokeCall`] :param psy: The PSy object containing this LFRicInvokes object. - :type psy: :py:class:`psyclone.dynamo0p3.LFRicPSy` + :type psy: :py:class:`psyclone.domain.lfric.LFRicPSy` ''' def __init__(self, alg_calls, psy): diff --git a/src/psyclone/domain/lfric/lfric_psy.py b/src/psyclone/domain/lfric/lfric_psy.py new file mode 100644 index 0000000000..cf9129956a --- /dev/null +++ b/src/psyclone/domain/lfric/lfric_psy.py @@ -0,0 +1,176 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-2023, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Authors R. W. Ford, A. R. Porter and S. Siso, STFC Daresbury Lab +# Modified I. Kavcic, A. Coughtrie, L. Turner and O. Brunt, Met Office +# Modified J. Henrichs, Bureau of Meteorology +# Modified A. B. G. Chalk and N. Nobre, STFC Daresbury Lab + +''' This module creates an LFRic-specific Invokes object which controls all + the required invocation calls. It also overrides the PSy gen method so + that LFRic-specific PSy module code is generated. + ''' + +from collections import OrderedDict + +from psyclone.configuration import Config +from psyclone.domain.lfric import LFRicConstants, LFRicSymbolTable, LFRicInvokes +from psyclone.f2pygen import ModuleGen, UseGen, PSyIRGen +from psyclone.psyGen import PSy, InvokeSchedule +from psyclone.psyir.nodes import ScopingNode + + +class LFRicPSy(PSy): + ''' + The LFRic-specific PSy class. This creates an LFRic-specific + Invokes object (which controls all the required invocation calls). + It also overrides the PSy gen method so that we generate + LFRic-specific PSy module code. + + :param invoke_info: object containing the required invocation information \ + for code optimisation and generation. + :type invoke_info: :py:class:`psyclone.parse.algorithm.FileInfo` + + ''' + def __init__(self, invoke_info): + # Make sure the scoping node creates LFRicSymbolTables + # TODO #1954: Remove the protected access using a factory + ScopingNode._symbol_table_class = LFRicSymbolTable + PSy.__init__(self, invoke_info) + self._invokes = LFRicInvokes(invoke_info.calls, self) + # Initialise the dictionary that holds the names of the required + # LFRic constants, data structures and data structure proxies for + # the "use" statements in modules that contain PSy-layer routines. + const = LFRicConstants() + const_mod = const.UTILITIES_MOD_MAP["constants"]["module"] + infmod_list = [const_mod] + # Add all field and operator modules that might be used in the + # algorithm layer. These do not appear in the code unless a + # variable is added to the "only" part of the + # '_infrastructure_modules' map. + for data_type_info in const.DATA_TYPE_MAP.values(): + infmod_list.append(data_type_info["module"]) + + # This also removes any duplicates from infmod_list + self._infrastructure_modules = OrderedDict( + (k, set()) for k in infmod_list) + + kind_names = set() + + # The infrastructure declares integer types with default + # precision so always add this. + api_config = Config.get().api_conf("dynamo0.3") + kind_names.add(api_config.default_kind["integer"]) + + # Datatypes declare precision information themselves. However, + # that is not the case for literals. Therefore deal + # with these separately here. + for invoke in self.invokes.invoke_list: + schedule = invoke.schedule + for kernel in schedule.kernels(): + for arg in kernel.args: + if arg.is_literal: + kind_names.add(arg.precision) + # Add precision names to the dictionary storing the required + # LFRic constants. + self._infrastructure_modules[const_mod] = kind_names + + @property + def name(self): + ''' + :returns: a name for the PSy layer. This is used as the PSy module \ + name. We override the default value as the Met Office \ + prefer "_psy" to be appended, rather than prepended. + :rtype: str + + ''' + return self._name + "_psy" + + @property + def orig_name(self): + ''' + :returns: the unmodified PSy-layer name. + :rtype: str + + ''' + return self._name + + @property + def infrastructure_modules(self): + ''' + :returns: the dictionary that holds the names of the required \ + LFRic infrastructure modules to create "use" \ + statements in the PSy-layer modules. + :rtype: dict of set + + ''' + return self._infrastructure_modules + + @property + def gen(self): + ''' + Generate PSy code for the LFRic (Dynamo0.3) API. + + :returns: root node of generated Fortran AST. + :rtype: :py:class:`psyir.nodes.Node` + + ''' + # Create an empty PSy layer module + psy_module = ModuleGen(self.name) + + # If the container has a Routine that is not an InvokeSchedule + # it should also be added to the generated module. + for routine in self.container.children: + if not isinstance(routine, InvokeSchedule): + psy_module.add(PSyIRGen(psy_module, routine)) + + # Add all invoke-specific information + self.invokes.gen_code(psy_module) + + # Include required constants and infrastructure modules. The sets of + # required LFRic data structures and their proxies are updated in + # the relevant field and operator subclasses of LFRicCollection. + # Here we sort the inputs in reverse order to have "_type" before + # "_proxy_type" and "operator_" before "columnwise_operator_". + # We also iterate through the dictionary in reverse order so the + # "use" statements for field types are before the "use" statements + # for operator types. + for infmod in reversed(self._infrastructure_modules): + if self._infrastructure_modules[infmod]: + infmod_types = sorted( + list(self._infrastructure_modules[infmod]), reverse=True) + psy_module.add(UseGen(psy_module, name=infmod, + only=True, funcnames=infmod_types)) + + # Return the root node of the generated code + return psy_module.root diff --git a/src/psyclone/dynamo0p3.py b/src/psyclone/dynamo0p3.py index d6ce1bacc1..33092d767e 100644 --- a/src/psyclone/dynamo0p3.py +++ b/src/psyclone/dynamo0p3.py @@ -367,132 +367,6 @@ def __init__(self, kernel_name, type_declns): # ---------- Classes -------------------------------------------------------- # -class LFRicPSy(PSy): - ''' - The LFRic-specific PSy class. This creates an LFRic-specific - Invokes object (which controls all the required invocation calls). - It also overrides the PSy gen method so that we generate - LFRic-specific PSy module code. - - :param invoke_info: object containing the required invocation information \ - for code optimisation and generation. - :type invoke_info: :py:class:`psyclone.parse.algorithm.FileInfo` - - ''' - def __init__(self, invoke_info): - # Make sure the scoping node creates LFRicSymbolTables - # TODO #1954: Remove the protected access using a factory - ScopingNode._symbol_table_class = LFRicSymbolTable - PSy.__init__(self, invoke_info) - self._invokes = LFRicInvokes(invoke_info.calls, self) - # Initialise the dictionary that holds the names of the required - # LFRic constants, data structures and data structure proxies for - # the "use" statements in modules that contain PSy-layer routines. - const = LFRicConstants() - const_mod = const.UTILITIES_MOD_MAP["constants"]["module"] - infmod_list = [const_mod] - # Add all field and operator modules that might be used in the - # algorithm layer. These do not appear in the code unless a - # variable is added to the "only" part of the - # '_infrastructure_modules' map. - for data_type_info in const.DATA_TYPE_MAP.values(): - infmod_list.append(data_type_info["module"]) - - # This also removes any duplicates from infmod_list - self._infrastructure_modules = OrderedDict( - (k, set()) for k in infmod_list) - - kind_names = set() - - # The infrastructure declares integer types with default - # precision so always add this. - api_config = Config.get().api_conf("dynamo0.3") - kind_names.add(api_config.default_kind["integer"]) - - # Datatypes declare precision information themselves. However, - # that is not the case for literals. Therefore deal - # with these separately here. - for invoke in self.invokes.invoke_list: - schedule = invoke.schedule - for kernel in schedule.kernels(): - for arg in kernel.args: - if arg.is_literal: - kind_names.add(arg.precision) - # Add precision names to the dictionary storing the required - # LFRic constants. - self._infrastructure_modules[const_mod] = kind_names - - @property - def name(self): - ''' - :returns: a name for the PSy layer. This is used as the PSy module \ - name. We override the default value as the Met Office \ - prefer "_psy" to be appended, rather than prepended. - :rtype: str - - ''' - return self._name + "_psy" - - @property - def orig_name(self): - ''' - :returns: the unmodified PSy-layer name. - :rtype: str - - ''' - return self._name - - @property - def infrastructure_modules(self): - ''' - :returns: the dictionary that holds the names of the required \ - LFRic infrastructure modules to create "use" \ - statements in the PSy-layer modules. - :rtype: dict of set - - ''' - return self._infrastructure_modules - - @property - def gen(self): - ''' - Generate PSy code for the LFRic (Dynamo0.3) API. - - :returns: root node of generated Fortran AST. - :rtype: :py:class:`psyir.nodes.Node` - - ''' - # Create an empty PSy layer module - psy_module = ModuleGen(self.name) - - # If the container has a Routine that is not an InvokeSchedule - # it should also be added to the generated module. - for routine in self.container.children: - if not isinstance(routine, InvokeSchedule): - psy_module.add(PSyIRGen(psy_module, routine)) - - # Add all invoke-specific information - self.invokes.gen_code(psy_module) - - # Include required constants and infrastructure modules. The sets of - # required LFRic data structures and their proxies are updated in - # the relevant field and operator subclasses of LFRicCollection. - # Here we sort the inputs in reverse order to have "_type" before - # "_proxy_type" and "operator_" before "columnwise_operator_". - # We also iterate through the dictionary in reverse order so the - # "use" statements for field types are before the "use" statements - # for operator types. - for infmod in reversed(self._infrastructure_modules): - if self._infrastructure_modules[infmod]: - infmod_types = sorted( - list(self._infrastructure_modules[infmod]), reverse=True) - psy_module.add(UseGen(psy_module, name=infmod, - only=True, funcnames=infmod_types)) - - # Return the root node of the generated code - return psy_module.root - - class DynStencils(LFRicCollection): ''' Stencil information and code generation associated with a PSy-layer @@ -6998,7 +6872,6 @@ def data_on_device(self, _): # documentation for. (See https://psyclone-ref.readthedocs.io) __all__ = [ 'DynFuncDescriptor03', - 'LFRicPSy', 'DynStencils', 'DynDofmaps', 'DynFunctionSpaces', diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 6c619bc7e0..8807eeab8a 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -215,7 +215,7 @@ def create(self, invoke_info): # implementation. # pylint: disable=import-outside-toplevel if self._type == "dynamo0.3": - from psyclone.dynamo0p3 import LFRicPSy as PSyClass + from psyclone.domain.lfric import LFRicPSy as PSyClass elif self._type == "gocean1.0": from psyclone.gocean1p0 import GOPSy as PSyClass elif self._type == "nemo": diff --git a/src/psyclone/tests/domain/lfric/lfric_dynamopsy_test.py b/src/psyclone/tests/domain/lfric/lfric_psy_test.py similarity index 86% rename from src/psyclone/tests/domain/lfric/lfric_dynamopsy_test.py rename to src/psyclone/tests/domain/lfric/lfric_psy_test.py index fdd1deb58b..bd0e642e82 100644 --- a/src/psyclone/tests/domain/lfric/lfric_dynamopsy_test.py +++ b/src/psyclone/tests/domain/lfric/lfric_psy_test.py @@ -35,15 +35,15 @@ # Modifications: A. R. Porter, STFC Daresbury Lab -'''This module tests the LFRicPSy class, currently located within the -dynamo0.3.py file.''' +'''This module tests the LFRicPSy class found in the LFRic domain. +''' from collections import OrderedDict import os from psyclone.configuration import Config -from psyclone.domain.lfric import LFRicConstants -from psyclone.dynamo0p3 import LFRicPSy, LFRicInvokes +from psyclone.domain.lfric import LFRicPSy, LFRicConstants +from psyclone.dynamo0p3 import LFRicInvokes from psyclone.parse.algorithm import parse from psyclone.psyGen import PSy @@ -68,11 +68,11 @@ def test_dynamopsy(): ''' Check that an instance of LFRicPSy can be created successfully.''' invoke_info = DummyInvokeInfo() - dynamo_psy = LFRicPSy(invoke_info) - assert isinstance(dynamo_psy, LFRicPSy) + lfric_psy = LFRicPSy(invoke_info) + assert isinstance(lfric_psy, LFRicPSy) assert issubclass(LFRicPSy, PSy) - assert isinstance(dynamo_psy._invokes, LFRicInvokes) - infrastructure_modules = dynamo_psy._infrastructure_modules + assert isinstance(lfric_psy._invokes, LFRicInvokes) + infrastructure_modules = lfric_psy._infrastructure_modules assert isinstance(infrastructure_modules, OrderedDict) assert list(infrastructure_modules["constants_mod"]) == ["i_def"] const = LFRicConstants() @@ -91,16 +91,16 @@ def test_dynamopsy_kind(): # is not required). _, invoke_info = parse(os.path.join( BASE_PATH, "15.12.3_single_pointwise_builtin.f90"), api="dynamo0.3") - dynamo_psy = LFRicPSy(invoke_info) - result = str(dynamo_psy.gen) + lfric_psy = LFRicPSy(invoke_info) + result = str(lfric_psy.gen) assert "USE constants_mod, ONLY: r_def, i_def" in result assert "f1_data(df) = 0.0\n" in result # 2: Literal kind value is declared (trying with two cases to check) for kind_name in ["r_solver", "r_tran"]: invoke_info.calls[0].kcalls[0].args[1]._text = f"0.0_{kind_name}" invoke_info.calls[0].kcalls[0].args[1]._datatype = ("real", kind_name) - dynamo_psy = LFRicPSy(invoke_info) - result = str(dynamo_psy.gen).lower() + lfric_psy = LFRicPSy(invoke_info) + result = str(lfric_psy.gen).lower() assert f"use constants_mod, only: {kind_name}, r_def, i_def" in result assert f"f1_data(df) = 0.0_{kind_name}" in result @@ -112,9 +112,9 @@ def test_dynamopsy_names(): ''' supplied_name = "hello" invoke_info = DummyInvokeInfo(name=supplied_name) - dynamo_psy = LFRicPSy(invoke_info) - assert dynamo_psy.name == supplied_name + "_psy" - assert dynamo_psy.orig_name == supplied_name + lfric_psy = LFRicPSy(invoke_info) + assert lfric_psy.name == supplied_name + "_psy" + assert lfric_psy.orig_name == supplied_name def test_dynamopsy_inf_modules(): @@ -124,9 +124,9 @@ def test_dynamopsy_inf_modules(): an instance of LFRicPSy. ''' - dynamo_psy = LFRicPSy(DummyInvokeInfo()) - assert (dynamo_psy.infrastructure_modules is - dynamo_psy._infrastructure_modules) + lfric_psy = LFRicPSy(DummyInvokeInfo()) + assert (lfric_psy.infrastructure_modules is + lfric_psy._infrastructure_modules) def test_dynamopsy_gen_no_invoke(): @@ -141,8 +141,8 @@ def test_dynamopsy_gen_no_invoke(): " IMPLICIT NONE\n" " CONTAINS\n" " END MODULE hello_psy") - dynamo_psy = LFRicPSy(DummyInvokeInfo(name="hello")) - result = dynamo_psy.gen + lfric_psy = LFRicPSy(DummyInvokeInfo(name="hello")) + result = lfric_psy.gen assert str(result) == expected_result @@ -168,8 +168,8 @@ def test_dynamopsy_gen(monkeypatch): # to be false. config = Config.get() config.distributed_memory = True - dynamo_psy = LFRicPSy(invoke_info) - result = str(dynamo_psy.gen) + lfric_psy = LFRicPSy(invoke_info) + result = str(lfric_psy.gen) assert ( " DO cell=loop0_start,loop0_stop\n" " !\n" diff --git a/src/psyclone/tests/test_files/dynamo0p3/alg_script.py b/src/psyclone/tests/test_files/dynamo0p3/alg_script.py index cda9ae36e4..8709247ba0 100644 --- a/src/psyclone/tests/test_files/dynamo0p3/alg_script.py +++ b/src/psyclone/tests/test_files/dynamo0p3/alg_script.py @@ -58,10 +58,10 @@ def trans(psy): designed to be called by the psyclone script. :param psy: PSyclone's representation of the PSy-layer code. - :type psy: :class:py:`psyclone.dynamo0p3.LFRicPSy` + :type psy: :class:py:`psyclone.domain.lfric.LFRicPSy` :returns: modified algorithm-layer code. - :rtype: :class:py:`psyclone.dynamo0p3.LFRicPSy` + :rtype: :class:py:`psyclone.domain.lfric.LFRicPSy` ''' return psy