diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bc4c65ab2a..a52b51ad7a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -70,10 +70,9 @@ jobs: - run: python -m pip install --upgrade pip - run: pip install .[doc] # Now we can check for warnings and broken links + - run: cd doc; make doctest - run: cd doc; make html SPHINXOPTS="-W --keep-going" - run: cd doc; make linkcheck - # TODO #2936: There are many doctest issues, so commenting out for now - # - run: cd doc; make doctest build: if: ${{ github.repository != 'stfc/PSyclone-mirror' }} runs-on: ubuntu-latest @@ -127,12 +126,12 @@ jobs: if: ${{ !(matrix.python-version == '3.9') }} run: | locale - pytest -n auto --cov=psyclone --cov-report=xml src/psyclone/tests + pytest -n auto --doctest-modules --cov=psyclone --cov-report=xml src/psyclone - name: Test with pytest and C Locale if: ${{ matrix.python-version == '3.9' }} run: | locale - pytest -n auto --cov=psyclone --cov-report=xml src/psyclone/tests + pytest -n auto --doctest-modules --cov=psyclone --cov-report=xml src/psyclone env: LC_ALL: C LANG: C diff --git a/doc/developer_guide/dependency.rst b/doc/developer_guide/dependency.rst index 782a3a18e1..b9e80e75b5 100644 --- a/doc/developer_guide/dependency.rst +++ b/doc/developer_guide/dependency.rst @@ -461,7 +461,7 @@ thread-private. Note that this code does not handle the usage of # the access information as well as from the symbol table # into account. access_sequence = var_accesses[signature] - if symbol.is_array_access(access_info=access_info): + if symbol.is_array_access(access_info=access_sequence): # It's not a scalar variable, so it will not be private continue @@ -504,7 +504,7 @@ until we find accesses that would prevent parallelisation: for next_statement in statements: # Add the variable accesses of the next statement to # the existing accesses: - next_statement.reference_accesses(accesses) + accesses = next_statement.reference_accesses() # Stop when the next statement can not be parallelised # together with the previous accesses: if not can_be_parallelised(accesses): @@ -581,7 +581,7 @@ can be parallelised: .. testoutput:: :hide: - Error: The write access to 'a(i,i)' and the read access to 'a(i + 1,i + 1)' are dependent and cannot be parallelised. Variable: 'a'. + Error: The write access to 'a(i,i)' in 'a(i,i) = j + k' and the read access to 'a(i + 1,i + 1)' in 'a(i,i) = a(i + 1,i + 1)' are dependent and cannot be parallelised. Variable: 'a'. .. _defusechain: diff --git a/doc/developer_guide/module_manager.rst b/doc/developer_guide/module_manager.rst index a9f8fc62b6..cde0308af2 100644 --- a/doc/developer_guide/module_manager.rst +++ b/doc/developer_guide/module_manager.rst @@ -115,34 +115,6 @@ However, it also provides methods (``get_used_module_names``, ``get_used_symbols_from_modules``) for interrogating the parse tree which can be useful if it is not possible to represent this in PSyIR. -An example usage of the ``ModuleManager`` and ``ModuleInfo`` objects, -which prints the filenames of all modules used in ``tl_testkern_mod``: - -.. testcode :: - - mod_manager = ModuleManager.get() - # Add the path to the PSyclone LFRic example codes: - mod_manager.add_search_path("../src/psyclone/tests/test_files/" - "lfric") - - testkern_info = mod_manager.get_module_info("tl_testkern_mod") - - used_mods = testkern_info.get_used_module_names() - # Sort the modules so we get a reproducible output ordering - used_mods_list = sorted(list(used_mods)) - for module_name in used_mods_list: - mod_info = mod_manager.get_module_info(module_name) - print("Module:", module_name, os.path.basename(mod_info.filename)) - -.. testoutput:: - - Module: argument_mod argument_mod.f90 - Module: constants_mod constants_mod.f90 - Module: fs_continuity_mod fs_continuity_mod.f90 - Module: kernel_mod kernel_mod.f90 - - - FileInfo ======== diff --git a/doc/developer_guide/psyir_symbols.rst b/doc/developer_guide/psyir_symbols.rst index c5b04f9f8f..abb3ebb0d9 100644 --- a/doc/developer_guide/psyir_symbols.rst +++ b/doc/developer_guide/psyir_symbols.rst @@ -337,7 +337,7 @@ specialisations are possible: >>> # The following statement would fail because the initial_value doesn't >>> # match the datatype of the symbol: >>> # sym2.specialise(DataSymbol, datatype=ScalarType.integer_type(), - ... initial_value=3.14) + >>> # initial_value=3.14) >>> # The following statement is valid and initial_value is set to 3 >>> # (and is_constant will default to False): >>> sym2.specialise(DataSymbol, datatype=ScalarType.integer_type(), initial_value=3) diff --git a/doc/developer_guide/transformations.rst b/doc/developer_guide/transformations.rst index 19b31333ea..766e150c34 100644 --- a/doc/developer_guide/transformations.rst +++ b/doc/developer_guide/transformations.rst @@ -33,14 +33,6 @@ .. ----------------------------------------------------------------------------- .. Written by R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab -.. testsetup:: - - # Define GOCEAN_SOURCE_FILE to point to an existing gocean 1.0 file. - GOCEAN_SOURCE_FILE = ("../src/psyclone/tests/test_files/" - "gocean1p0/test11_different_iterates_over_one_invoke.f90") - # Define NEMO_SOURCE_FILE to point to an existing nemo file. - NEMO_SOURCE_FILE = ("../examples/nemo/code/tra_adv.F90") - Transformations ############### diff --git a/doc/user_guide/psyir.rst b/doc/user_guide/psyir.rst index 9f385d7f63..caa82d3e87 100644 --- a/doc/user_guide/psyir.rst +++ b/doc/user_guide/psyir.rst @@ -40,7 +40,7 @@ .. testsetup:: from psyclone.psyir.symbols import DataSymbol, ScalarType, ArrayType - from psyclone.psyir.nodes import Reference + from psyclone.psyir.nodes import Reference .. _psyir-ug: @@ -268,7 +268,7 @@ example: ... ScalarType.Precision.SINGLE) >>> bool_type = ScalarType(ScalarType.Intrinsic.BOOLEAN, 4) >>> symbol = DataSymbol("rdef", int_type, initial_value=4) - >>> scalar_type = ScalarType(ScalarType.Intrinsic.REAL, symbol) + >>> scalar_type = ScalarType(ScalarType.Intrinsic.REAL, Reference(symbol)) For convenience ScalarType has static methods to create a number of common scalar datatypes: diff --git a/src/psyclone/alg_gen.py b/src/psyclone/alg_gen.py index fd290e6ca8..2781e03fe7 100644 --- a/src/psyclone/alg_gen.py +++ b/src/psyclone/alg_gen.py @@ -72,26 +72,16 @@ class Alg: latter allows consistent names to be generated between the algorithm (calling) and psy (callee) layers. - For example: - - >>> from psyclone.algorithm.parse import parse - >>> parse_tree, info = parse("argspec.F90") - >>> from psyclone.psyGen import PSy - >>> psy = PSy(info) - >>> from psyclone.alg_gen import Alg - >>> alg = Alg(parse_tree, psy) - >>> print(alg.gen) - - :param parse_tree: an object containing a parse tree of the \ - algorithm specification which was produced by the function \ - :func:`psyclone.parse.algorithm.parse`. Assumes the algorithm \ - will be parsed by fparser2 and expects a valid program unit, \ + :param parse_tree: an object containing a parse tree of the + algorithm specification which was produced by the function + :func:`psyclone.parse.algorithm.parse`. Assumes the algorithm + will be parsed by fparser2 and expects a valid program unit, program, module, subroutine or function. :type parse_tree: :py:class:`fparser.two.utils.Base` :param psy: an object containing information about the PSy layer. :type psy: :py:class:`psyclone.psyGen.PSy` - :param str invoke_name: the name that the algorithm layer uses to \ - indicate an invoke call. This is an optional argument that \ + :param str invoke_name: the name that the algorithm layer uses to + indicate an invoke call. This is an optional argument that defaults to the name "invoke". ''' diff --git a/src/psyclone/core/symbolic_maths.py b/src/psyclone/core/symbolic_maths.py index 00ef2a3910..62854baecd 100644 --- a/src/psyclone/core/symbolic_maths.py +++ b/src/psyclone/core/symbolic_maths.py @@ -50,10 +50,13 @@ class SymbolicMaths: provides convenience functions for PSyclone. It has a Singleton access, e.g.: + >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.core import SymbolicMaths >>> sympy = SymbolicMaths.get() - >>> # Assume lhs is the PSyIR of 'i+j', and rhs is 'j+i' + >>> reader = FortranReader() + >>> lhs = reader.psyir_from_expression('i+j') + >>> rhs = reader.psyir_from_expression('j+i') >>> if sympy.equal(lhs, rhs): ... writer = FortranWriter() ... print(f"'{writer(lhs)}' and '{writer(rhs)}' are equal.") diff --git a/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py b/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py index 3a01823b7b..ad097948f9 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py @@ -70,22 +70,15 @@ class GOConstLoopBoundsTrans(Transformation): In practice, the application of the constant loop bounds transformation looks something like, e.g.: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> import os - >>> TEST_API = "gocean" - >>> _, info = parse(os.path.join("tests", "test_files", "gocean1p0", - ... "single_invoke.f90"), - ... api=TEST_API) - >>> psy = PSyFactory(TEST_API).create(info) - >>> invoke = psy.invokes.get('invoke_0_compute_cu') - >>> schedule = invoke.schedule + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "single_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> - >>> from psyclone.transformations import GOConstLoopBoundsTrans + >>> from psyclone.domain.gocean.transformations import \ + GOConstLoopBoundsTrans >>> clbtrans = GOConstLoopBoundsTrans() >>> >>> clbtrans.apply(schedule) - >>> print(schedule.view()) ''' diff --git a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py index e14e364988..47b97d0fb0 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py @@ -46,24 +46,19 @@ class GOceanExtractTrans(ExtractTrans): - ''' GOcean1.0 API application of ExtractTrans transformation \ + ''' GOcean1.0 API application of ExtractTrans transformation to extract code into a stand-alone program. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> - >>> API = "gocean" - >>> FILENAME = "shallow_alg.f90" - >>> ast, invokeInfo = parse(FILENAME, api=API) - >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) - >>> schedule = psy.invokes.get('invoke_0').schedule + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "shallow/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.domain.gocean.transformations import GOceanExtractTrans >>> etrans = GOceanExtractTrans() >>> >>> # Apply GOceanExtractTrans transformation to selected Nodes >>> etrans.apply(schedule.children[0]) - >>> print(schedule.view()) + ''' # ------------------------------------------------------------------------ diff --git a/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py index ed50422aaa..35ce13bcfb 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py @@ -49,17 +49,15 @@ class GOceanLoopFuseTrans(LoopFuseTrans): in order to fuse two GOcean loops after performing validity checks (e.g. that the loops are over the same grid-point type). For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("shallow_alg.f90") - >>> psy = PSyFactory("gocean").create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> print(schedule.view()) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "shallow/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> - >>> from psyclone.transformations import GOceanLoopFuseTrans + >>> from psyclone.domain.gocean.transformations import GOceanLoopFuseTrans >>> ftrans = GOceanLoopFuseTrans() - >>> ftrans.apply(schedule[0], schedule[1]) - >>> print(schedule.view()) + + # FIXME + # >>> ftrans.apply(schedule[0], schedule[1]) ''' def __str__(self): diff --git a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py index a4faaa9f6b..a9ad2b7406 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -64,16 +64,18 @@ class GOOpenCLTrans(Transformation): InvokeSchedule. Additionally, it will generate OpenCL kernels for each of the kernels referenced by the Invoke. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> API = "gocean" - >>> FILENAME = "shallow_alg.f90" # examples/gocean/eg1 - >>> ast, invoke_info = parse(FILENAME, api=API) - >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) - >>> schedule = psy.invokes.get('invoke_0').schedule + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "shallow/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> + >>> from psyclone.domain.gocean.transformations import ( + ... GOMoveIterationBoundariesInsideKernelTrans, + ... GOOpenCLTrans) + >>> move_trans = GOMoveIterationBoundariesInsideKernelTrans() >>> ocl_trans = GOOpenCLTrans() - >>> ocl_trans.apply(schedule) - >>> print(schedule.view()) + + # FIXME: Needs GOMoveIterationBoundariesInsideKernelTrans + # >>> ocl_trans.apply(schedule) ''' # Specify which OpenCL command queue to use for management operations like diff --git a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py index 3e2dd5d292..ff79af0941 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py @@ -49,22 +49,6 @@ class LFRicExtractTrans(ExtractTrans): ''' LFRic API application of ExtractTrans transformation to extract code into a stand-alone program. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> - >>> API = "lfric" - >>> FILENAME = "solver_alg.x90" - >>> ast, invokeInfo = parse(FILENAME, api=API) - >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> - >>> from psyclone.domain.lfric.transformations import LFRicExtractTrans - >>> etrans = LFRicExtractTrans() - >>> - >>> # Apply LFRicExtractTrans transformation to selected Nodes - >>> etrans.apply(schedule.children[0:3]) - >>> print(schedule.view()) - ''' def __init__(self): diff --git a/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py b/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py index 488e64f2ee..c13cb384d3 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py @@ -56,20 +56,23 @@ class LFRicLoopFuseTrans(LoopFuseTrans): >>> from psyclone.psyGen import PSyFactory >>> >>> API = "lfric" - >>> FILENAME = "alg.x90" - >>> ast, invokeInfo = parse(FILENAME, api=API) - >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> - >>> from psyclone.domain.lfric.transformations import LFRicLoopFuseTrans - >>> ftrans = LFRicLoopFuseTrans() - >>> - >>> ftrans.apply(schedule[0], schedule[1]) - >>> print(schedule.view()) + + # FIXME: Which alg.f90? + # >>> FILENAME = "alg.x90" + # >>> ast, invokeInfo = parse(FILENAME, api=API) + # >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) + # >>> schedule = psy.invokes.get('invoke_0').schedule + # >>> + # >>> from psyclone.domain.lfric.transformations import LFRicLoopFuseTrans + # >>> ftrans = LFRicLoopFuseTrans() + # >>> + # >>> ftrans.apply(schedule[0], schedule[1]) + + # FIXME: doctest compare output code The optional argument `same_space` can be set as - >>> ftrans.apply(schedule[0], schedule[1], {"same_space": True}) + # >>> ftrans.apply(schedule[0], schedule[1], {"same_space": True}) when applying the transformation. diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index f0005709a2..6487904b75 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -239,15 +239,6 @@ def generate(filename: str, :raises IOError: if the filename or search path do not exist. :raises NoInvokesError: if no invokes are found in the algorithm file. - For example: - - >>> from psyclone.generator import generate - >>> alg, psy = generate("algspec.f90") - >>> alg, psy = generate("algspec.f90", kernel_paths=["src/kernels"]) - >>> alg, psy = generate("algspec.f90", script_name="optimise.py") - >>> alg, psy = generate("algspec.f90", line_length=True) - >>> alg, psy = generate("algspec.f90", distributed_memory=False) - ''' logger = logging.getLogger(__name__) diff --git a/src/psyclone/gocean1p0.py b/src/psyclone/gocean1p0.py index 2a90c4270c..8ca4bcee8b 100644 --- a/src/psyclone/gocean1p0.py +++ b/src/psyclone/gocean1p0.py @@ -566,12 +566,16 @@ def add_bounds(bound_info): the loop boundaries for the outer and inner loop. The format is a ":" separated tuple: - >>> bound_info = offset-type:field-type:iteration-space:outer-start: + .. code-block:: + + bound_info = offset-type:field-type:iteration-space:outer-start: outer-stop:inner-start:inner-stop Example: - >>> bound_info = go_offset_ne:go_ct:go_all_pts: + .. code-block:: + + bound_info = go_offset_ne:go_ct:go_all_pts: {start}-1:{stop}+1:{start}:{stop} The expressions {start} and {stop} will be replaced with the loop diff --git a/src/psyclone/parse/algorithm.py b/src/psyclone/parse/algorithm.py index 7cf57ee4a8..61f5ee40b0 100644 --- a/src/psyclone/parse/algorithm.py +++ b/src/psyclone/parse/algorithm.py @@ -96,11 +96,6 @@ def parse(alg_filename, api="", invoke_name="invoke", kernel_paths=None, :rtype: (:py:class:`fparser.two.Fortran2003.Program`, \ :py:class:`psyclone.parse.FileInfo`) - For example: - - >>> from psyclone.parse.algorithm import parse - >>> ast, info = parse(SOURCE_FILE) - ''' if kernel_paths is None: kernel_paths = [] @@ -129,12 +124,6 @@ class Parser(): that it conforms and an error raised if not. The default is \ False. - For example: - - >>> from psyclone.parse.algorithm import Parser - >>> parser = Parser(api="gocean") - >>> ast, info = parser.parse(SOURCE_FILE) - ''' def __init__(self, api="", invoke_name="invoke", kernel_paths=None, diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index d49056a9b8..4e370390eb 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -204,15 +204,6 @@ class PSy(): by the function :func:`parse.algorithm.parse`. :type invoke_info: :py:class:`psyclone.parse.algorithm.FileInfo` - For example: - - >>> from psyclone.parse.algorithm import parse - >>> ast, info = parse("argspec.F90") - >>> from psyclone.psyGen import PSyFactory - >>> api = "..." - >>> psy = PSyFactory(api).create(info) - >>> print(psy.gen) - ''' def __init__(self, invoke_info): @@ -630,21 +621,10 @@ class InvokeSchedule(Routine): Stores schedule information for an invocation call. Schedules can be optimised using transformations. - >>> from psyclone.parse.algorithm import parse - >>> ast, info = parse("algorithm.f90") - >>> from psyclone.psyGen import PSyFactory - >>> api = "..." - >>> psy = PSyFactory(api).create(info) - >>> invokes = psy.invokes - >>> invokes.names - >>> invoke = invokes.get("name") - >>> schedule = invoke.schedule - >>> print(schedule.view()) - :param symbol: RoutineSymbol representing the invoke. :type symbol: :py:class:`psyclone.psyir.symbols.RoutineSymbol` - :param type KernFactory: class instance of the factory to use when \ - creating Kernels. e.g. \ + :param type KernFactory: class instance of the factory to use when + creating Kernels. e.g. \ :py:class:`psyclone.domain.lfric.LFRicKernCallFactory`. :param type BuiltInFactory: class instance of the factory to use when \ creating built-ins. e.g. \ @@ -2209,18 +2189,6 @@ class TransInfo(): This utility will not find Transformations under the new file structure (TODO #620) and is deprecated. - For example: - - >>> from psyclone.psyGen import TransInfo - >>> t = TransInfo() - >>> print(t.list) - There is 1 transformation available: - 1: SwapTrans, A test transformation - >>> # accessing a transformation by index - >>> trans = t.get_trans_num(1) - >>> # accessing a transformation by name - >>> trans = t.get_trans_name("SwapTrans") - ''' def __init__(self, module=None, base_class=None): diff --git a/src/psyclone/psyir/nodes/node.py b/src/psyclone/psyir/nodes/node.py index acc90484d4..15943fb7e9 100644 --- a/src/psyclone/psyir/nodes/node.py +++ b/src/psyclone/psyir/nodes/node.py @@ -1808,10 +1808,14 @@ def path_from(self, ancestor): The result of this method can be used to find the node from its ancestor for example by: - >>> index_list = node.path_from(ancestor) - >>> cursor = ancestor + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.nodes import Literal + >>> psyir = FortranReader().psyir_from_statement("a = b + c + 1") + >>> node = psyir.walk(Literal)[0] + >>> index_list = node.path_from(psyir) + >>> cursor = psyir >>> for index in index_list: - >>> cursor = cursor.children[index] + ... cursor = cursor.children[index] >>> assert cursor is node :param ancestor: an ancestor node of self to find the path from. diff --git a/src/psyclone/psyir/nodes/structure_member.py b/src/psyclone/psyir/nodes/structure_member.py index 0e09e44db8..f8efc6b202 100644 --- a/src/psyclone/psyir/nodes/structure_member.py +++ b/src/psyclone/psyir/nodes/structure_member.py @@ -67,6 +67,7 @@ def create(member_name, inner_member): the 'xstart' component of it. We would therefore create the `StructureMember` for this by calling: + >>> from psyclone.psyir.nodes import StructureMember, Member >>> smem = StructureMember.create("subdomain", Member("xstart")) :param str member_name: name of the structure member. diff --git a/src/psyclone/psyir/symbols/symbol.py b/src/psyclone/psyir/symbols/symbol.py index a80c74dae8..a0b865e190 100644 --- a/src/psyclone/psyir/symbols/symbol.py +++ b/src/psyclone/psyir/symbols/symbol.py @@ -482,8 +482,11 @@ def is_array_access(self, index_variable=None, access_info=None): If a `loop_variable` is specified, a variable access will only be considered an array access if the specified variable is used in at least one of the indices. For example: - >>> do i=1, n - >>> a(j) = 2 + + .. code-block:: fortran + + do i=1, n + a(j) = 2 the access to `a` is not considered an array if `loop_variable` is set to `i`. If `loop_variable` is specified, `access_information` diff --git a/src/psyclone/psyir/transformations/acc_kernels_trans.py b/src/psyclone/psyir/transformations/acc_kernels_trans.py index 3473c5dc5b..bf191d9cdd 100644 --- a/src/psyclone/psyir/transformations/acc_kernels_trans.py +++ b/src/psyclone/psyir/transformations/acc_kernels_trans.py @@ -67,16 +67,16 @@ class ACCKernelsTrans(RegionTrans): For example: - >>> from psyclone.psyir.frontend import FortranReader - >>> psyir = FortranReader().psyir_from_source(NEMO_SOURCE_FILE) + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.tests.utilities import get_file_path + >>> filename = get_file_path("code/tra_adv.F90") + >>> psyir = FortranReader().psyir_from_file(filename) >>> >>> from psyclone.psyir.transformations import ACCKernelsTrans >>> ktrans = ACCKernelsTrans() >>> >>> schedule = psyir.children[0] - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> kernels = schedule.children[9] + >>> kernels = schedule.children[26] >>> # Transform the kernel >>> ktrans.apply(kernels) diff --git a/src/psyclone/psyir/transformations/acc_loop_trans.py b/src/psyclone/psyir/transformations/acc_loop_trans.py index 86e2036f5f..5c981514aa 100644 --- a/src/psyclone/psyir/transformations/acc_loop_trans.py +++ b/src/psyclone/psyir/transformations/acc_loop_trans.py @@ -52,30 +52,22 @@ class ACCLoopTrans(ParallelLoopTrans): For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.parse.utils import ParseError - >>> from psyclone.psyGen import PSyFactory - >>> from psyclone.errors import GenerationError + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> >>> from psyclone.psyir.transformations import ACCLoopTrans >>> from psyclone.transformations import ACCParallelTrans - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) >>> >>> ltrans = ACCLoopTrans() >>> rtrans = ACCParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Apply the OpenACC Loop transformation to *every* loop in the schedule >>> for child in schedule.children[:]: ... ltrans.apply(child) >>> >>> # Enclose all of these loops within a single OpenACC parallel region >>> rtrans.apply(schedule) - >>> ''' # The types of node that must be excluded from the section of PSyIR diff --git a/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py b/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py index 783cca9399..c3eac09626 100644 --- a/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py +++ b/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py @@ -74,10 +74,10 @@ class ArrayAccess2LoopTrans(Transformation): >>> print(FortranWriter()(psyir)) program example real, dimension(10) :: a - integer :: ji + integer :: idx - do ji = 1, 1, 1 - a(ji) = 0.0 + do idx = 1, 1, 1 + a(idx) = 0.0 enddo end program example diff --git a/src/psyclone/psyir/transformations/datanode_to_temp_trans.py b/src/psyclone/psyir/transformations/datanode_to_temp_trans.py index 78505d31ef..9f29a926a0 100644 --- a/src/psyclone/psyir/transformations/datanode_to_temp_trans.py +++ b/src/psyclone/psyir/transformations/datanode_to_temp_trans.py @@ -87,13 +87,12 @@ class DataNodeToTempTrans(Transformation): >>> DataNodeToTempTrans().apply(assign.rhs, storage_name="temp") >>> print(FortranWriter()(psyir)) subroutine my_subroutine() - integer, dimension(10,10) :: a integer :: i integer :: j integer :: temp - temp = j * 2 - i = temp + temp = j * 2 + i = temp end subroutine my_subroutine diff --git a/src/psyclone/psyir/transformations/debug_checksum_trans.py b/src/psyclone/psyir/transformations/debug_checksum_trans.py index f33b0a0d92..929b21ffd9 100644 --- a/src/psyclone/psyir/transformations/debug_checksum_trans.py +++ b/src/psyclone/psyir/transformations/debug_checksum_trans.py @@ -69,8 +69,7 @@ class DebugChecksumTrans(RegionTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.transformations import DebugChecksumTrans - + >>> from psyclone.psyir.transformations import DebugChecksumTrans >>> psyir = FortranReader().psyir_from_source(""" ... subroutine mysubroutine() ... integer, dimension(10,10) :: A @@ -78,14 +77,14 @@ class DebugChecksumTrans(RegionTrans): ... integer :: j ... do i = 1, 10 ... do j = 1, 10 - ... A(i,j) = A(i,k) + i-j + ... A(i,j) = A(i,j) + i-j ... end do ... end do ... end subroutine ... """) - ... loop = psyir.children[0].children[0] - ... DebugChecksumTrans().apply(loop) - ... print(FortranWriter()(psyir)) + >>> loop = psyir.children[0].children[0] + >>> DebugChecksumTrans().apply(loop) + >>> print(FortranWriter()(psyir)) subroutine mysubroutine() integer, dimension(10,10) :: a integer :: i @@ -98,9 +97,11 @@ class DebugChecksumTrans(RegionTrans): enddo enddo PSYCLONE_INTERNAL_line_ = __LINE__ + + ! PSyclone DebugChecksumTrans-generated checksums PRINT *, "PSyclone checksums from mysubroutine at line:", \ PSYCLONE_INTERNAL_line_ + 1 - PRINT *, "a checksum", SUM(a) + PRINT *, "a checksum", SUM(a(:, :)) end subroutine mysubroutine diff --git a/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py index 9533e2ab95..9ed14d3fe9 100644 --- a/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py +++ b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py @@ -61,7 +61,7 @@ class HoistLocalArraysTrans(Transformation): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.psyir.nodes import Assignment + >>> from psyclone.psyir.nodes import Assignment, Routine >>> from psyclone.psyir.transformations import HoistLocalArraysTrans >>> code = ("module test_mod\\n" ... "contains\\n" @@ -84,22 +84,20 @@ class HoistLocalArraysTrans(Transformation): implicit none real, allocatable, dimension(:,:), private :: a public - - public :: test_sub contains subroutine test_sub(n) integer :: n integer :: i integer :: j - real :: value = 1.0 + real, save :: value = 1.0 - if (.not.allocated(a) .or. ubound(a, 1) /= n .or. ubound(a, 2) /= n) \ -then + if (.not.allocated(a) .or. ubound(a, dim=1) /= n .or. \ +ubound(a, dim=2) /= n) then if (allocated(a)) then deallocate(a) end if - allocate(a(1 : n, 1 : n)) + allocate(a(1:n,1:n)) end if do i = 1, n, 1 do j = 1, n, 1 diff --git a/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py b/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py index 4e1c72f061..deeadf8952 100644 --- a/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py +++ b/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py @@ -62,7 +62,7 @@ class HoistLoopBoundExprTrans(LoopTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.nodes import Loop - >>> from psyclone.psyir.transformations import HoistTrans + >>> from psyclone.psyir.transformations import HoistLoopBoundExprTrans >>> code = ("program test\\n" ... " use mymod, only: mytype\\n" ... " integer :: i,j,n\\n" @@ -81,12 +81,12 @@ class HoistLoopBoundExprTrans(LoopTrans): integer :: j integer :: n real, dimension(n) :: a - integer :: loop_bound - integer :: loop_bound_1 + integer :: loop_start + integer :: loop_stop - loop_bound_1 = UBOUND(a, 1) - loop_bound = mytype%start - do i = loop_bound, loop_bound_1, 1 + loop_stop = UBOUND(a, dim=1) + loop_start = mytype%start + do i = loop_start, loop_stop, 1 a(i) = 1.0 enddo diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py index 2e01075fd3..d44d3efd0b 100644 --- a/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py @@ -126,13 +126,15 @@ class Maxval2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = -HUGE(result) + reduction_var = -HUGE(reduction_var) do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = MAX(result, array(idx_1,idx)) + reduction_var = MAX(reduction_var, array(idx_1,idx)) enddo enddo + result = reduction_var end subroutine maxval_test diff --git a/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py index 70620f8b50..70e5b2e5fe 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py @@ -126,13 +126,15 @@ class Minval2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = HUGE(result) + reduction_var = HUGE(reduction_var) do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = MIN(result, array(idx_1,idx)) + reduction_var = MIN(reduction_var, array(idx_1,idx)) enddo enddo + result = reduction_var end subroutine minval_test diff --git a/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py index 1484558ed2..a261d96c2e 100644 --- a/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py @@ -127,13 +127,15 @@ class Product2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = 1.0 + reduction_var = 1.0 do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = result * array(idx_1,idx) + reduction_var = reduction_var * array(idx_1,idx) enddo enddo + result = reduction_var end subroutine product_test diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py index f2cfb4c9e6..884e2feaab 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py @@ -131,13 +131,15 @@ class Sum2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = 0.0 + reduction_var = 0.0 do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = result + array(idx_1,idx) + reduction_var = reduction_var + array(idx_1,idx) enddo enddo + result = reduction_var end subroutine sum_test diff --git a/src/psyclone/psyir/transformations/loop_swap_trans.py b/src/psyclone/psyir/transformations/loop_swap_trans.py index 59f72e3d78..8f098e51d5 100644 --- a/src/psyclone/psyir/transformations/loop_swap_trans.py +++ b/src/psyclone/psyir/transformations/loop_swap_trans.py @@ -63,19 +63,14 @@ class LoopSwapTrans(LoopTrans): This transform is used as follows: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("shallow_alg.f90") - >>> psy = PSyFactory("gocean").create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> - >>> from psyclone.transformations import LoopSwapTrans - >>> swap = LoopSwapTrans() - >>> swap.apply(schedule.children[0]) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "shallow/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> + >>> from psyclone.psyir.transformations import LoopSwapTrans + >>> from psyclone.psyir.nodes import Loop + >>> swap = LoopSwapTrans() + >>> swap.apply(schedule.walk(Loop)[0]) ''' diff --git a/src/psyclone/psyir/transformations/move_trans.py b/src/psyclone/psyir/transformations/move_trans.py index 44aabfeed8..76189ee2ae 100644 --- a/src/psyclone/psyir/transformations/move_trans.py +++ b/src/psyclone/psyir/transformations/move_trans.py @@ -57,18 +57,20 @@ class MoveTrans(Transformation): >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory - >>> ast,invokeInfo=parse("lfric.F90") - >>> psy=PSyFactory("lfric").create(invokeInfo) - >>> schedule=psy.invokes.get('invoke_v3_kernel_type').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> - >>> from psyclone.transformations import MoveTrans - >>> trans=MoveTrans() - >>> trans.apply(schedule.children[0], schedule.children[2], - ... options = {"position":"after") - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + + # FIXME: File lfric.F90 does not exist + # >>> ast,invokeInfo=parse("lfric.F90", api="lfric") + # >>> psy=PSyFactory("lfric").create(invokeInfo) + # >>> schedule=psy.invokes.get('invoke_v3_kernel_type').schedule + # >>> # Uncomment the following line to see a text view of the schedule + # >>> # print(schedule.view()) + # >>> + # >>> from psyclone.transformations import MoveTrans + # >>> trans=MoveTrans() + # >>> trans.apply(schedule.children[0], schedule.children[2], + # ... options = {"position":"after") + # >>> # Uncomment the following line to see a text view of the schedule + # >>> # print(schedule.view()) Nodes may only be moved to a new location with the same parent and must not break any dependencies otherwise an exception is diff --git a/src/psyclone/psyir/transformations/omp_declare_target_trans.py b/src/psyclone/psyir/transformations/omp_declare_target_trans.py index f1a48bfb2b..31c9a26d16 100644 --- a/src/psyclone/psyir/transformations/omp_declare_target_trans.py +++ b/src/psyclone/psyir/transformations/omp_declare_target_trans.py @@ -59,8 +59,9 @@ class OMPDeclareTargetTrans(Transformation, For example: >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.nodes import Loop - >>> from psyclone.transformations import OMPDeclareTargetTrans + >>> from psyclone.psyir.transformations import OMPDeclareTargetTrans >>> >>> tree = FortranReader().psyir_from_source(""" ... subroutine my_subroutine(A) @@ -73,25 +74,24 @@ class OMPDeclareTargetTrans(Transformation, ... end do ... end do ... end subroutine - ... """ + ... """) >>> omptargettrans = OMPDeclareTargetTrans() >>> omptargettrans.apply(tree.walk(Routine)[0]) - - will generate: - - .. code-block:: fortran - - subroutine my_subroutine(A) - integer, dimension(10, 10), intent(inout) :: A - integer :: i - integer :: j - !$omp declare target - do i = 1, 10 - do j = 1, 10 - A(i, j) = 0 - end do - end do - end subroutine + >>> print(FortranWriter()(tree)) + subroutine my_subroutine(a) + integer, dimension(10,10), intent(inout) :: a + integer :: i + integer :: j + + !$omp declare target + do i = 1, 10, 1 + do j = 1, 10, 1 + a(i,j) = 0 + enddo + enddo + + end subroutine my_subroutine + ''' def apply(self, diff --git a/src/psyclone/psyir/transformations/omp_loop_trans.py b/src/psyclone/psyir/transformations/omp_loop_trans.py index 694c6293be..ff4629a625 100644 --- a/src/psyclone/psyir/transformations/omp_loop_trans.py +++ b/src/psyclone/psyir/transformations/omp_loop_trans.py @@ -117,7 +117,7 @@ class OMPLoopTrans(ParallelLoopTrans): integer :: i integer :: j - !$omp parallel do default(shared), private(i,j), schedule(dynamic) + !$omp parallel do default(shared) private(i,j) schedule(dynamic) do i = 1, 10, 1 do j = 1, 10, 1 a(i,j) = 0 @@ -129,6 +129,7 @@ class OMPLoopTrans(ParallelLoopTrans): ''' + def __init__(self, omp_directive="do", omp_schedule="auto"): super().__init__() # Whether or not to generate code for (run-to-run on n threads) diff --git a/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py b/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py index f046881fe6..7b51b3d21d 100644 --- a/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py +++ b/src/psyclone/psyir/transformations/omp_minimise_sync_trans.py @@ -124,7 +124,7 @@ class OMPMinimiseSyncTrans(Transformation, AsyncTransMixin): integer, dimension(100) :: b integer :: i - !$omp parallel default(shared), private(i) + !$omp parallel default(shared) private(i) !$omp do schedule(auto) do i = 1, 100, 1 a(i) = i @@ -146,13 +146,13 @@ class OMPMinimiseSyncTrans(Transformation, AsyncTransMixin): a(i) = a(i) + 1 enddo !$omp end do nowait - !$omp barrier !$omp end parallel end subroutine test ''' + def __str__(self) -> str: '''Returns the string representation of this OMPMinimiseSyncTrans object.''' @@ -192,12 +192,14 @@ def _eliminate_adjacent_barriers(self, routine: Routine, ''' Removes excess adjacent bar_type barriers from the input routine, i.e: - >>> !$omp taskwait - >>> !$omp taskwait + .. code-block:: fortran + !$omp taskwait + !$omp taskwait will be simplified to just: - >>> !$omp taskwait + .. code-block:: fortran + !$omp taskwait :param routine: the routine to remove duplicate barriers from. :param bar_type: the barrier type to remove. diff --git a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py index b1505b4c75..38133ad505 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py @@ -49,23 +49,8 @@ class OMPParallelLoopTrans(OMPLoopTrans): ''' Adds an OpenMP PARALLEL DO directive to a loop. - For example: - - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("lfric.F90") - >>> psy = PSyFactory("lfric").create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_v3_kernel_type').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> - >>> from psyclone.transformations import OMPParallelLoopTrans - >>> trans = OMPParallelLoopTrans() - >>> trans.apply(schedule.children[0]) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - ''' + def __str__(self): return "Add an 'OpenMP PARALLEL DO' directive" diff --git a/src/psyclone/psyir/transformations/omp_parallel_trans.py b/src/psyclone/psyir/transformations/omp_parallel_trans.py index c6fe3843d7..cb76927a26 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_trans.py @@ -62,33 +62,24 @@ class OMPParallelTrans(ParallelRegionTrans): Create an OpenMP PARALLEL region by inserting directives. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.parse.utils import ParseError - >>> from psyclone.psyGen import PSyFactory - >>> from psyclone.errors import GenerationError - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.psyGen import TransInfo >>> t = TransInfo() >>> ltrans = t.get_trans_name('GOceanOMPLoopTrans') - >>> rtrans = t.get_trans_name('OMPParallelTrans') - >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + >>> from psyclone.psyir.transformations import OMPParallelTrans + >>> rtrans = OMPParallelTrans() >>> >>> # Apply the OpenMP Loop transformation to *every* loop >>> # in the schedule >>> for child in schedule.children: - >>> ltrans.apply(child) + ... ltrans.apply(child) >>> >>> # Enclose all of these loops within a single OpenMP >>> # PARALLEL region >>> rtrans.apply(schedule.children) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) ''' # The types of node that this transformation cannot enclose diff --git a/src/psyclone/psyir/transformations/omp_taskloop_trans.py b/src/psyclone/psyir/transformations/omp_taskloop_trans.py index 05bd2e3b20..e206a87e7e 100644 --- a/src/psyclone/psyir/transformations/omp_taskloop_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskloop_trans.py @@ -47,31 +47,25 @@ class OMPTaskloopTrans(ParallelLoopTrans): For example: - >>> from pysclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPSingleTrans >>> from psyclone.psyir.transformations import OMPParallelTrans - >>> from psyclone.transformations import OMPTaskloopTrans + >>> from psyclone.psyir.transformations import OMPTaskloopTrans >>> from psyclone.psyir.transformations import OMPTaskwaitTrans >>> singletrans = OMPSingleTrans() >>> paralleltrans = OMPParallelTrans() >>> tasklooptrans = OMPTaskloopTrans() >>> taskwaittrans = OMPTaskwaitTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Apply the OpenMP Taskloop transformation to *every* loop >>> # in the schedule. >>> # This ignores loop dependencies. These can be handled >>> # by the OMPTaskwaitTrans >>> for child in schedule.children: - >>> tasklooptrans.apply(child) + ... tasklooptrans.apply(child) >>> # Enclose all of these loops within a single OpenMP >>> # SINGLE region >>> singletrans.apply(schedule.children) @@ -79,11 +73,12 @@ class OMPTaskloopTrans(ParallelLoopTrans): >>> # PARALLEL region >>> paralleltrans.apply(schedule.children) >>> # Ensure loop dependencies are satisfied - >>> taskwaittrans.apply(schedule.children) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + + # FIXME + # >>> taskwaittrans.apply(schedule.children) ''' + def __init__(self, grainsize=None, num_tasks=None, nogroup=False): self._grainsize = None self._num_tasks = None diff --git a/src/psyclone/psyir/transformations/omp_taskwait_trans.py b/src/psyclone/psyir/transformations/omp_taskwait_trans.py index 13d2858693..4440ce70fc 100644 --- a/src/psyclone/psyir/transformations/omp_taskwait_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskwait_trans.py @@ -66,39 +66,34 @@ class OMPTaskwaitTrans(Transformation): For example: - >>> from pysclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> filename = "nemolite2d_alg.f90" - >>> ast, invokeInfo = parse(filename, api=api, invoke_name="invoke") - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPSingleTrans >>> from psyclone.psyir.transformations import OMPParallelTrans - >>> from psyclone.transformations import OMPTaskloopTrans + >>> from psyclone.psyir.transformations import OMPTaskloopTrans >>> from psyclone.psyir.transformations import OMPTaskwaitTrans >>> singletrans = OMPSingleTrans() >>> paralleltrans = OMPParallelTrans() >>> tasklooptrans = OMPTaskloopTrans() >>> taskwaittrans = OMPTaskwaitTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> print(schedule.view()) - >>> >>> # Apply the OpenMP Taskloop transformation to *every* loop >>> # in the schedule. >>> # This ignores loop dependencies. These are handled by the >>> # taskwait transformation. >>> for child in schedule.children: - >>> tasklooptrans.apply(child, nogroup = true) + ... tasklooptrans.apply(child, nogroup = True) >>> # Enclose all of these loops within a single OpenMP >>> # SINGLE region >>> singletrans.apply(schedule.children) >>> # Enclose all of these loops within a single OpenMP >>> # PARALLEL region >>> paralleltrans.apply(schedule.children) - >>> taskwaittrans.apply(schedule.children) - >>> print(schedule.view()) + + # FIXME + # >>> taskwaittrans.apply(schedule.children) ''' def __str__(self): diff --git a/src/psyclone/psyir/transformations/profile_trans.py b/src/psyclone/psyir/transformations/profile_trans.py index d5e6900a23..addc72ecaf 100644 --- a/src/psyclone/psyir/transformations/profile_trans.py +++ b/src/psyclone/psyir/transformations/profile_trans.py @@ -48,23 +48,15 @@ class ProfileTrans(PSyDataTrans): ''' Create a profile region around a list of statements. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.parse.utils import ParseError - >>> from psyclone.psyGen import PSyFactory, GenerationError - >>> from psyclone.psyir.transformations import ProfileTrans - >>> api = "gocean" - >>> filename = "nemolite2d_alg.f90" - >>> ast, invokeInfo = parse(filename, api=api, invoke_name="invoke") - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> + >>> from psyclone.psyir.transformations import ProfileTrans >>> p_trans = ProfileTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> print(schedule.view()) - >>> >>> # Enclose all children within a single profile region >>> p_trans.apply(schedule.children) - >>> print(schedule.view()) This implementation relies completely on the base class PSyDataTrans for the actual work, it only adjusts the name etc, and the list diff --git a/src/psyclone/psyir/transformations/psy_data_trans.py b/src/psyclone/psyir/transformations/psy_data_trans.py index d7cfe6742d..71d8757b70 100644 --- a/src/psyclone/psyir/transformations/psy_data_trans.py +++ b/src/psyclone/psyir/transformations/psy_data_trans.py @@ -55,16 +55,14 @@ class PSyDataTrans(RegionTrans): >>> from psyclone.parse.utils import ParseError >>> from psyclone.psyGen import PSyFactory >>> api = "gocean" - >>> ast, invoke_info = parse(SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invoke_info) - >>> + + # FIXME: What file? + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> from psyclone.psyir.transformations import PSyDataTrans >>> data_trans = PSyDataTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Enclose all children within a single PSyData region >>> data_trans.apply(schedule.children) >>> # Uncomment the following line to see a text view of the schedule diff --git a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py index a345d58ea4..5373390e73 100644 --- a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py +++ b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py @@ -77,17 +77,15 @@ class Reference2ArrayRangeTrans(Transformation): >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.nodes import Reference >>> from psyclone.psyir.transformations import TransformationError - >>> CODE = ("program example\\n" - ... "real :: a(:)\\n" - ... "a = 0.0\\n" - ... "end program\\n") + >>> from psyclone.psyir.transformations import Reference2ArrayRangeTrans >>> trans = Reference2ArrayRangeTrans() - >>> psyir = FortranReader().psyir_from_source(CODE) + >>> psyir = FortranReader().psyir_from_source(""" + ... program example + ... real :: a(:) + ... a = 0.0 + ... end program""") >>> for reference in psyir.walk(Reference): - ... try: - ... trans.apply(reference) - ... except TransformationError: - ... pass + ... trans.apply(reference) >>> print(FortranWriter()(psyir)) program example real, dimension(:) :: a diff --git a/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py b/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py index fe4b9420ff..fc5c051796 100644 --- a/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py +++ b/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py @@ -68,10 +68,12 @@ class ReplaceReferenceByLiteralTrans(Transformation): For example: + >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.symbols import ScalarType + >>> from psyclone.psyir.nodes import Routine >>> from psyclone.psyir.transformations import ( - ReplaceReferenceByLiteralTrans) + ... ReplaceReferenceByLiteralTrans) >>> source = """program test ... use mymod ... type(my_type):: t1, t2, t3, t4 @@ -115,7 +117,7 @@ class ReplaceReferenceByLiteralTrans(Transformation): integer :: ic2 integer :: ic3 real, dimension(10) :: a - + invariant = 1 do i = 1, 10, 1 t1%a = 13 @@ -124,11 +126,12 @@ class ReplaceReferenceByLiteralTrans(Transformation): a(ic3) = 3 + (ic3 + 13) * ic3 a(t1%a) = 4 + (t1%a + 4 * 13) * t1%a enddo - + end program test ''' + def __init__(self) -> None: super().__init__() # Dictionary with Literal values of the corresponding symbol diff --git a/src/psyclone/psyir/transformations/scalarisation_trans.py b/src/psyclone/psyir/transformations/scalarisation_trans.py index 496d1dd786..ee076cea14 100644 --- a/src/psyclone/psyir/transformations/scalarisation_trans.py +++ b/src/psyclone/psyir/transformations/scalarisation_trans.py @@ -201,10 +201,12 @@ def _get_index_values_from_indices( ''' Compute a list of index values for a given node. Looks at loop bounds and range declarations to attempt to convert loop variables to an - explicit range, i.e. an access like + explicit range, i.e. an access like: + .. code-block:: fortran + do i = 1, 100 - array(i) = ... + array(i) = ... end do the returned list would contain a range object for [1:100]. diff --git a/src/psyclone/tests/exceptions_test.py b/src/psyclone/tests/exceptions_test.py index 7e19d1400e..8985c11bf9 100644 --- a/src/psyclone/tests/exceptions_test.py +++ b/src/psyclone/tests/exceptions_test.py @@ -92,8 +92,10 @@ def test_exception_repr(): _ = importlib.import_module(mod) all_exceptions = all_sub_exceptions(Exception) - psy_excepts = [exc for exc in all_exceptions if "psyclone." in str(exc)] - psy_excepts.append(DummyPSycloneError) + psy_excepts = set(exc for exc in all_exceptions + if "psyclone." in str(exc) and + "PSycloneError" not in str(exc)) + psy_excepts.add(DummyPSycloneError) # Different versions of pytest behave differently with respect to their # handling of an exception's representation. This can lead to some tests @@ -112,8 +114,11 @@ def test_exception_repr(): for psy_except in psy_excepts: - # Check if the exception inherits PSycloneError - assert issubclass(psy_except, PSycloneError) + # Check if the exception inherits PSycloneError (we don't use + # issubclass because the import_modules used in this test re-imports + # already loaded modules and create two super classes PSycloneError) + assert 'PSycloneError' in [str(cls.__name__) for cls in + psy_except.__bases__] # Ensure there are __str__ & __repr__ methods implemented which are not # inherited from the parent Exception class diff --git a/src/psyclone/tests/nemo/test_files/code/tra_adv.F90 b/src/psyclone/tests/nemo/test_files/code/tra_adv.F90 new file mode 100755 index 0000000000..a70fa2b7ae --- /dev/null +++ b/src/psyclone/tests/nemo/test_files/code/tra_adv.F90 @@ -0,0 +1,273 @@ + !!===================================================================================== + !! *** traadv kernel extracted from the NEMO software (http://www.nemo-ocean.eu ) *** + !! *** governed by the CeCILL licence (http://www.cecill.info) *** + !! + !! *** IS-ENES2 - CMCC/STFC *** + !!===================================================================================== +PROGRAM tra_adv + USE iso_c_binding, only: C_INT64_T + ! The below should be e.g. wp = KIND(1.0d0) but PSyclone does not support + ! the KIND intrinsic yet: TODO #585. + INTEGER, PARAMETER :: wp = 8 + REAL(wp), ALLOCATABLE, SAVE, DIMENSION(:,:,:,:) :: t3sn, t3ns, t3ew, t3we + REAL(wp), ALLOCATABLE, SAVE, DIMENSION(:,:,:) :: tsn + REAL(wp), ALLOCATABLE, SAVE, DIMENSION(:,:,:) :: pun, pvn, pwn + REAL(wp), ALLOCATABLE, SAVE, DIMENSION(:,:,:) :: mydomain, zslpx, zslpy, zwx, zwy, umask, vmask, tmask, zind + REAL(wp), ALLOCATABLE, SAVE, DIMENSION(:,:) :: ztfreez, rnfmsk, upsmsk + REAL(wp), ALLOCATABLE, SAVE, DIMENSION(:) :: rnfmsk_z + REAL(wp) :: zice, zu, z0u, zzwx, zv, z0v, zzwy, ztra, zbtr, zdt, zalpha + REAL(wp) :: r + REAL(wp) :: zw, z0w + INTEGER :: jpi, jpj, jpk, ji, jj, jk, jt + ! TODO #588 it would be more natural to do INTEGER*8 here but PSyclone does + ! not yet support such syntax. + INTEGER(KIND=C_INT64_T) :: it + CHARACTER(len=10) :: env + + CALL get_environment_variable("JPI", env) + READ ( env, '(i10)' ) jpi + CALL get_environment_variable("JPJ", env) + READ ( env, '(i10)' ) jpj + CALL get_environment_variable("JPK", env) + READ ( env, '(i10)' ) jpk + CALL get_environment_variable("IT", env) + READ ( env, '(i10)' ) it + + ! Initialisation + + ALLOCATE( mydomain (jpi,jpj,jpk)) + ALLOCATE( zwx (jpi,jpj,jpk)) + ALLOCATE( zwy (jpi,jpj,jpk)) + ALLOCATE( zslpx (jpi,jpj,jpk)) + ALLOCATE( zslpy (jpi,jpj,jpk)) + ALLOCATE( pun (jpi,jpj,jpk)) + ALLOCATE( pvn (jpi,jpj,jpk)) + ALLOCATE( pwn (jpi,jpj,jpk)) + ALLOCATE( umask (jpi,jpj,jpk)) + ALLOCATE( vmask (jpi,jpj,jpk)) + ALLOCATE( tmask (jpi,jpj,jpk)) + ALLOCATE( zind (jpi,jpj,jpk)) + ALLOCATE( ztfreez (jpi,jpj)) + ALLOCATE( rnfmsk (jpi,jpj)) + ALLOCATE( upsmsk (jpi,jpj)) + ALLOCATE( rnfmsk_z (jpk)) + ALLOCATE( tsn(jpi,jpj,jpk)) + +! arrays initialization + + r = jpi*jpj*jpk + + ! the following three lines can be uncommented to randomize arrays initialization + !call random_seed() + !call random_number(r) + !r = r*jpi*jpj*jpk + + DO jk = 1, jpk + DO jj = 1, jpj + DO ji = 1, jpi + umask(ji,jj,jk) = ji*jj*jk/r + mydomain(ji,jj,jk) =ji*jj*jk/r + pun(ji,jj,jk) =ji*jj*jk/r + pvn(ji,jj,jk) =ji*jj*jk/r + pwn(ji,jj,jk) =ji*jj*jk/r + vmask(ji,jj,jk)= ji*jj*jk/r + tsn(ji,jj,jk)= ji*jj*jk/r + tmask(ji,jj,jk)= ji*jj*jk/r + END DO + END DO + END DO + + r = jpi*jpj + DO jj=1, jpj + DO ji=1, jpi + ztfreez(ji,jj) = ji*jj/r + upsmsk(ji,jj) = ji*jj/r + rnfmsk(ji,jj) = ji*jj/r + END DO + END DO + + DO jk=1, jpk + rnfmsk_z(jk)=jk/jpk + END DO + +!*********************** +!* Start of the symphony +!*********************** + DO jt = 1, it + DO jk = 1, jpk + DO jj = 1, jpj + DO ji = 1, jpi + IF( tsn(ji,jj,jk) <= ztfreez(ji,jj) + 0.1d0 ) THEN ; zice = 1.d0 + ELSE ; zice = 0.d0 + ENDIF + zind(ji,jj,jk) = MAX ( & + rnfmsk(ji,jj) * rnfmsk_z(jk), & + upsmsk(ji,jj) , & + zice & + & ) * tmask(ji,jj,jk) + zind(ji,jj,jk) = 1 - zind(ji,jj,jk) + END DO + END DO + END DO + + zwx(:,:,jpk) = 0.e0 ; zwy(:,:,jpk) = 0.e0 + + DO jk = 1, jpk-1 + DO jj = 1, jpj-1 + DO ji = 1, jpi-1 + zwx(ji,jj,jk) = umask(ji,jj,jk) * ( mydomain(ji+1,jj,jk) - mydomain(ji,jj,jk) ) + zwy(ji,jj,jk) = vmask(ji,jj,jk) * ( mydomain(ji,jj+1,jk) - mydomain(ji,jj,jk) ) + END DO + END DO + END DO + + zslpx(:,:,jpk) = 0.e0 ; zslpy(:,:,jpk) = 0.e0 + + DO jk = 1, jpk-1 + DO jj = 2, jpj + DO ji = 2, jpi + zslpx(ji,jj,jk) = ( zwx(ji,jj,jk) + zwx(ji-1,jj ,jk) ) & + & * ( 0.25d0 + SIGN( 0.25d0, zwx(ji,jj,jk) * zwx(ji-1,jj ,jk) ) ) + zslpy(ji,jj,jk) = ( zwy(ji,jj,jk) + zwy(ji ,jj-1,jk) ) & + & * ( 0.25d0 + SIGN( 0.25d0, zwy(ji,jj,jk) * zwy(ji ,jj-1,jk) ) ) + END DO + END DO + END DO + + DO jk = 1, jpk-1 + DO jj = 2, jpj + DO ji = 2, jpi + zslpx(ji,jj,jk) = SIGN( 1.d0, zslpx(ji,jj,jk) ) * MIN( ABS( zslpx(ji ,jj,jk) ), & + & 2.d0*ABS( zwx (ji-1,jj,jk) ), & + & 2.d0*ABS( zwx (ji ,jj,jk) ) ) + zslpy(ji,jj,jk) = SIGN( 1.d0, zslpy(ji,jj,jk) ) * MIN( ABS( zslpy(ji,jj ,jk) ), & + & 2.d0*ABS( zwy (ji,jj-1,jk) ), & + & 2.d0*ABS( zwy (ji,jj ,jk) ) ) + END DO + END DO + END DO + + DO jk = 1, jpk-1 + zdt = 1 + DO jj = 2, jpj-1 + DO ji = 2, jpi-1 + z0u = SIGN( 0.5d0, pun(ji,jj,jk) ) + zalpha = 0.5d0 - z0u + zu = z0u - 0.5d0 * pun(ji,jj,jk) * zdt + + zzwx = mydomain(ji+1,jj,jk) + zind(ji,jj,jk) * (zu * zslpx(ji+1,jj,jk)) + zzwy = mydomain(ji ,jj,jk) + zind(ji,jj,jk) * (zu * zslpx(ji ,jj,jk)) + + zwx(ji,jj,jk) = pun(ji,jj,jk) * ( zalpha * zzwx + (1.-zalpha) * zzwy ) + + z0v = SIGN( 0.5d0, pvn(ji,jj,jk) ) + zalpha = 0.5d0 - z0v + zv = z0v - 0.5d0 * pvn(ji,jj,jk) * zdt + + zzwx = mydomain(ji,jj+1,jk) + zind(ji,jj,jk) * (zv * zslpy(ji,jj+1,jk)) + zzwy = mydomain(ji,jj ,jk) + zind(ji,jj,jk) * (zv * zslpy(ji,jj ,jk)) + + zwy(ji,jj,jk) = pvn(ji,jj,jk) * ( zalpha * zzwx + (1.d0-zalpha) * zzwy ) + END DO + END DO + END DO + + DO jk = 1, jpk-1 + DO jj = 2, jpj-1 + DO ji = 2, jpi-1 + zbtr = 1. + ztra = - zbtr * ( zwx(ji,jj,jk) - zwx(ji-1,jj ,jk ) & + & + zwy(ji,jj,jk) - zwy(ji ,jj-1,jk ) ) + mydomain(ji,jj,jk) = mydomain(ji,jj,jk) + ztra + END DO + END DO + END DO + + zwx (:,:, 1 ) = 0.e0 ; zwx (:,:,jpk) = 0.e0 + + DO jk = 2, jpk-1 + zwx(:,:,jk) = tmask(:,:,jk) * ( mydomain(:,:,jk-1) - mydomain(:,:,jk) ) + END DO + + zslpx(:,:,1) = 0.e0 + + DO jk = 2, jpk-1 + DO jj = 1, jpj + DO ji = 1, jpi + zslpx(ji,jj,jk) = ( zwx(ji,jj,jk) + zwx(ji,jj,jk+1) ) & + & * ( 0.25d0 + SIGN( 0.25d0, zwx(ji,jj,jk) * zwx(ji,jj,jk+1) ) ) + END DO + END DO + END DO + + DO jk = 2, jpk-1 + DO jj = 1, jpj + DO ji = 1, jpi + zslpx(ji,jj,jk) = SIGN( 1.d0, zslpx(ji,jj,jk) ) * MIN( ABS( zslpx(ji,jj,jk ) ), & + & 2.d0*ABS( zwx (ji,jj,jk+1) ), & + & 2.d0*ABS( zwx (ji,jj,jk ) ) ) + END DO + END DO + END DO + + zwx(:,:, 1 ) = pwn(:,:,1) * mydomain(:,:,1) + + zdt = 1 + zbtr = 1. + DO jk = 1, jpk-1 + DO jj = 2, jpj-1 + DO ji = 2, jpi-1 + z0w = SIGN( 0.5d0, pwn(ji,jj,jk+1) ) + zalpha = 0.5d0 + z0w + zw = z0w - 0.5d0 * pwn(ji,jj,jk+1) * zdt * zbtr + + zzwx = mydomain(ji,jj,jk+1) + zind(ji,jj,jk) * (zw * zslpx(ji,jj,jk+1)) + zzwy = mydomain(ji,jj,jk ) + zind(ji,jj,jk) * (zw * zslpx(ji,jj,jk )) + + zwx(ji,jj,jk+1) = pwn(ji,jj,jk+1) * ( zalpha * zzwx + (1.-zalpha) * zzwy ) + END DO + END DO + END DO + + zbtr = 1. + DO jk = 1, jpk-1 + DO jj = 2, jpj-1 + DO ji = 2, jpi-1 + ztra = - zbtr * ( zwx(ji,jj,jk) - zwx(ji,jj,jk+1) ) + mydomain(ji,jj,jk) = ztra + END DO + END DO + END DO + END DO + + OPEN(unit = 4, file = 'output.dat', form='formatted') + + DO jk = 1, jpk-1 + DO jj = 2, jpj-1 + DO ji = 2, jpi-1 + write(4,*) mydomain(ji,jj,jk) + END DO + END DO + END DO + + CLOSE(4) + + DEALLOCATE( mydomain ) + DEALLOCATE( zwx ) + DEALLOCATE( zwy ) + DEALLOCATE( zslpx ) + DEALLOCATE( zslpy ) + DEALLOCATE( pun ) + DEALLOCATE( pvn ) + DEALLOCATE( pwn ) + DEALLOCATE( umask) + DEALLOCATE( vmask) + DEALLOCATE( tmask) + DEALLOCATE( zind ) + DEALLOCATE( ztfreez ) + DEALLOCATE( rnfmsk) + DEALLOCATE( upsmsk) + DEALLOCATE( rnfmsk_z) + DEALLOCATE( tsn) + +END PROGRAM tra_adv diff --git a/src/psyclone/tests/nemo/test_files/code/traldf_iso.F90 b/src/psyclone/tests/nemo/test_files/code/traldf_iso.F90 new file mode 100644 index 0000000000..210fcf843b --- /dev/null +++ b/src/psyclone/tests/nemo/test_files/code/traldf_iso.F90 @@ -0,0 +1,385 @@ +MODULE traldf_iso + !!====================================================================== + !! *** MODULE traldf_iso *** + !! Ocean tracers: horizontal component of the lateral tracer mixing trend + !!====================================================================== + !! History : OPA ! 1994-08 (G. Madec, M. Imbard) + !! 8.0 ! 1997-05 (G. Madec) split into traldf and trazdf + !! NEMO ! 2002-08 (G. Madec) Free form, F90 + !! 1.0 ! 2005-11 (G. Madec) merge traldf and trazdf :-) + !! 3.3 ! 2010-09 (C. Ethe, G. Madec) Merge TRA-TRC + !! 3.7 ! 2014-01 (G. Madec, S. Masson) restructuration/simplification of aht/aeiv specification + !! - ! 2014-02 (F. Lemarie, G. Madec) triad operator (Griffies) + Method of Stabilizing Correction + !!---------------------------------------------------------------------- + + !!---------------------------------------------------------------------- + !! tra_ldf_iso : update the tracer trend with the horizontal component of a iso-neutral laplacian operator + !! and with the vertical part of the isopycnal or geopotential s-coord. operator + !!---------------------------------------------------------------------- + USE oce ! ocean dynamics and active tracers + USE dom_oce ! ocean space and time domain + USE trc_oce ! share passive tracers/Ocean variables + USE zdf_oce ! ocean vertical physics + USE ldftra ! lateral diffusion: tracer eddy coefficients + USE ldfslp ! iso-neutral slopes + USE diaptr ! poleward transport diagnostics + USE diaar5 ! AR5 diagnostics + ! + USE in_out_manager ! I/O manager + USE iom ! I/O library + USE phycst ! physical constants + USE lbclnk ! ocean lateral boundary conditions (or mpp link) + + IMPLICIT NONE + PRIVATE + + PUBLIC tra_ldf_iso ! routine called by step.F90 + + LOGICAL :: l_ptr ! flag to compute poleward transport + LOGICAL :: l_hst ! flag to compute heat transport + + !! * Substitutions +!# include "vectopt_loop_substitute.h90" + !!---------------------------------------------------------------------- + !! NEMO/OPA 3.7 , NEMO Consortium (2015) + !! $Id: traldf_iso.F90 9124 2017-12-19 08:26:25Z gm $ + !! Software governed by the CeCILL licence (NEMOGCM/NEMO_CeCILL.txt) + !!---------------------------------------------------------------------- +CONTAINS + + SUBROUTINE tra_ldf_iso( kt, kit000, cdtype, pahu, pahv, pgu , pgv , & + & pgui, pgvi, & + & ptb , ptbb, pta , kjpt, kpass ) + !!---------------------------------------------------------------------- + !! *** ROUTINE tra_ldf_iso *** + !! + !! ** Purpose : Compute the before horizontal tracer (t & s) diffusive + !! trend for a laplacian tensor (ezxcept the dz[ dz[.] ] term) and + !! add it to the general trend of tracer equation. + !! + !! ** Method : The horizontal component of the lateral diffusive trends + !! is provided by a 2nd order operator rotated along neural or geopo- + !! tential surfaces to which an eddy induced advection can be added + !! It is computed using before fields (forward in time) and isopyc- + !! nal or geopotential slopes computed in routine ldfslp. + !! + !! 1st part : masked horizontal derivative of T ( di[ t ] ) + !! ======== with partial cell update if ln_zps=T + !! with top cell update if ln_isfcav + !! + !! 2nd part : horizontal fluxes of the lateral mixing operator + !! ======== + !! zftu = pahu e2u*e3u/e1u di[ tb ] + !! - pahu e2u*uslp dk[ mi(mk(tb)) ] + !! zftv = pahv e1v*e3v/e2v dj[ tb ] + !! - pahv e2u*vslp dk[ mj(mk(tb)) ] + !! take the horizontal divergence of the fluxes: + !! difft = 1/(e1e2t*e3t) { di-1[ zftu ] + dj-1[ zftv ] } + !! Add this trend to the general trend (ta,sa): + !! ta = ta + difft + !! + !! 3rd part: vertical trends of the lateral mixing operator + !! ======== (excluding the vertical flux proportional to dk[t] ) + !! vertical fluxes associated with the rotated lateral mixing: + !! zftw = - { mi(mk(pahu)) * e2t*wslpi di[ mi(mk(tb)) ] + !! + mj(mk(pahv)) * e1t*wslpj dj[ mj(mk(tb)) ] } + !! take the horizontal divergence of the fluxes: + !! difft = 1/(e1e2t*e3t) dk[ zftw ] + !! Add this trend to the general trend (ta,sa): + !! pta = pta + difft + !! + !! ** Action : Update pta arrays with the before rotated diffusion + !!---------------------------------------------------------------------- + INTEGER , INTENT(in ) :: kt ! ocean time-step index + INTEGER , INTENT(in ) :: kit000 ! first time step index + CHARACTER(len=3) , INTENT(in ) :: cdtype ! =TRA or TRC (tracer indicator) + INTEGER , INTENT(in ) :: kjpt ! number of tracers + INTEGER , INTENT(in ) :: kpass ! =1/2 first or second passage + REAL(wp), DIMENSION(jpi,jpj,jpk) , INTENT(in ) :: pahu, pahv ! eddy diffusivity at u- and v-points [m2/s] + REAL(wp), DIMENSION(jpi,jpj ,kjpt), INTENT(in ) :: pgu, pgv ! tracer gradient at pstep levels + REAL(wp), DIMENSION(jpi,jpj, kjpt), INTENT(in ) :: pgui, pgvi ! tracer gradient at top levels + REAL(wp), DIMENSION(jpi,jpj,jpk,kjpt), INTENT(in ) :: ptb ! tracer (kpass=1) or laplacian of tracer (kpass=2) + REAL(wp), DIMENSION(jpi,jpj,jpk,kjpt), INTENT(in ) :: ptbb ! tracer (only used in kpass=2) + REAL(wp), DIMENSION(jpi,jpj,jpk,kjpt), INTENT(inout) :: pta ! tracer trend + ! + INTEGER :: ji, jj, jk, jn ! dummy loop indices + INTEGER :: ikt + INTEGER :: ierr ! local integer + REAL(wp) :: zmsku, zahu_w, zabe1, zcof1, zcoef3 ! local scalars + REAL(wp) :: zmskv, zahv_w, zabe2, zcof2, zcoef4 ! - - + REAL(wp) :: zcoef0, ze3w_2, zsign, z2dt, z1_2dt ! - - + REAL(wp), DIMENSION(jpi,jpj) :: zdkt, zdk1t, z2d + REAL(wp), DIMENSION(jpi,jpj,jpk) :: zdit, zdjt, zftu, zftv, ztfw + !!---------------------------------------------------------------------- + ! + IF( kt == kit000 ) THEN + IF(lwp) WRITE(numout,*) + IF(lwp) WRITE(numout,*) 'tra_ldf_iso : rotated laplacian diffusion operator on ', cdtype + IF(lwp) WRITE(numout,*) '~~~~~~~~~~~' + ! + akz (:,:,:) = 0._wp + ah_wslp2(:,:,:) = 0._wp + ENDIF + ! + l_hst = .FALSE. + l_ptr = .FALSE. + IF( cdtype == 'TRA' .AND. ln_diaptr ) l_ptr = .TRUE. + IF( cdtype == 'TRA' .AND. ( iom_use("uadv_heattr") .OR. iom_use("vadv_heattr") .OR. & + & iom_use("uadv_salttr") .OR. iom_use("vadv_salttr") ) ) l_hst = .TRUE. + ! + ! ! set time step size (Euler/Leapfrog) + IF( neuler == 0 .AND. kt == nit000 ) THEN ; z2dt = rdt ! at nit000 (Euler) + ELSE ; z2dt = 2.* rdt ! (Leapfrog) + ENDIF + z1_2dt = 1._wp / z2dt + ! + IF( kpass == 1 ) THEN ; zsign = 1._wp ! bilaplacian operator require a minus sign (eddy diffusivity >0) + ELSE ; zsign = -1._wp + ENDIF + + !!---------------------------------------------------------------------- + !! 0 - calculate ah_wslp2 and akz + !!---------------------------------------------------------------------- + ! + IF( kpass == 1 ) THEN !== first pass only ==! + ! + DO jk = 2, jpkm1 + DO jj = 2, jpjm1 + DO ji = fs_2, fs_jpim1 ! vector opt. + ! + zmsku = wmask(ji,jj,jk) / MAX( umask(ji ,jj,jk-1) + umask(ji-1,jj,jk) & + & + umask(ji-1,jj,jk-1) + umask(ji ,jj,jk) , 1._wp ) + zmskv = wmask(ji,jj,jk) / MAX( vmask(ji,jj ,jk-1) + vmask(ji,jj-1,jk) & + & + vmask(ji,jj-1,jk-1) + vmask(ji,jj ,jk) , 1._wp ) + ! + zahu_w = ( pahu(ji ,jj,jk-1) + pahu(ji-1,jj,jk) & + & + pahu(ji-1,jj,jk-1) + pahu(ji ,jj,jk) ) * zmsku + zahv_w = ( pahv(ji,jj ,jk-1) + pahv(ji,jj-1,jk) & + & + pahv(ji,jj-1,jk-1) + pahv(ji,jj ,jk) ) * zmskv + ! + ah_wslp2(ji,jj,jk) = zahu_w * wslpi(ji,jj,jk) * wslpi(ji,jj,jk) & + & + zahv_w * wslpj(ji,jj,jk) * wslpj(ji,jj,jk) + END DO + END DO + END DO + ! + IF( ln_traldf_msc ) THEN ! stabilizing vertical diffusivity coefficient + DO jk = 2, jpkm1 + DO jj = 2, jpjm1 + DO ji = fs_2, fs_jpim1 + akz(ji,jj,jk) = 0.25_wp * ( & + & ( pahu(ji ,jj,jk) + pahu(ji ,jj,jk-1) ) / ( e1u(ji ,jj) * e1u(ji ,jj) ) & + & + ( pahu(ji-1,jj,jk) + pahu(ji-1,jj,jk-1) ) / ( e1u(ji-1,jj) * e1u(ji-1,jj) ) & + & + ( pahv(ji,jj ,jk) + pahv(ji,jj ,jk-1) ) / ( e2v(ji,jj ) * e2v(ji,jj ) ) & + & + ( pahv(ji,jj-1,jk) + pahv(ji,jj-1,jk-1) ) / ( e2v(ji,jj-1) * e2v(ji,jj-1) ) ) + END DO + END DO + END DO + ! + IF( ln_traldf_blp ) THEN ! bilaplacian operator + DO jk = 2, jpkm1 + DO jj = 1, jpjm1 + DO ji = 1, fs_jpim1 + akz(ji,jj,jk) = 16._wp * ah_wslp2(ji,jj,jk) & + & * ( akz(ji,jj,jk) + ah_wslp2(ji,jj,jk) / ( e3w_n(ji,jj,jk) * e3w_n(ji,jj,jk) ) ) + END DO + END DO + END DO + ELSEIF( ln_traldf_lap ) THEN ! laplacian operator + DO jk = 2, jpkm1 + DO jj = 1, jpjm1 + DO ji = 1, fs_jpim1 + ze3w_2 = e3w_n(ji,jj,jk) * e3w_n(ji,jj,jk) + zcoef0 = z2dt * ( akz(ji,jj,jk) + ah_wslp2(ji,jj,jk) / ze3w_2 ) + akz(ji,jj,jk) = MAX( zcoef0 - 0.5_wp , 0._wp ) * ze3w_2 * z1_2dt + END DO + END DO + END DO + ENDIF + ! + ELSE ! 33 flux set to zero with akz=ah_wslp2 ==>> computed in full implicit + akz(:,:,:) = ah_wslp2(:,:,:) + ENDIF + ENDIF + ! + ! ! =========== + DO jn = 1, kjpt ! tracer loop + ! ! =========== + ! + !!---------------------------------------------------------------------- + !! I - masked horizontal derivative + !!---------------------------------------------------------------------- +!!gm : bug.... why (x,:,:)? (1,jpj,:) and (jpi,1,:) should be sufficient.... + zdit (1,:,:) = 0._wp ; zdit (jpi,:,:) = 0._wp + zdjt (1,:,:) = 0._wp ; zdjt (jpi,:,:) = 0._wp + !!end + + ! Horizontal tracer gradient + DO jk = 1, jpkm1 + DO jj = 1, jpjm1 + DO ji = 1, fs_jpim1 ! vector opt. + zdit(ji,jj,jk) = ( ptb(ji+1,jj ,jk,jn) - ptb(ji,jj,jk,jn) ) * umask(ji,jj,jk) + zdjt(ji,jj,jk) = ( ptb(ji ,jj+1,jk,jn) - ptb(ji,jj,jk,jn) ) * vmask(ji,jj,jk) + END DO + END DO + END DO + IF( ln_zps ) THEN ! bottom and surface ocean correction of the horizontal gradient + DO jj = 1, jpjm1 ! bottom correction (partial bottom cell) + DO ji = 1, fs_jpim1 ! vector opt. + zdit(ji,jj,mbku(ji,jj)) = pgu(ji,jj,jn) + zdjt(ji,jj,mbkv(ji,jj)) = pgv(ji,jj,jn) + END DO + END DO + IF( ln_isfcav ) THEN ! first wet level beneath a cavity + DO jj = 1, jpjm1 + DO ji = 1, fs_jpim1 ! vector opt. + IF( miku(ji,jj) > 1 ) zdit(ji,jj,miku(ji,jj)) = pgui(ji,jj,jn) + IF( mikv(ji,jj) > 1 ) zdjt(ji,jj,mikv(ji,jj)) = pgvi(ji,jj,jn) + END DO + END DO + ENDIF + ENDIF + ! + !!---------------------------------------------------------------------- + !! II - horizontal trend (full) + !!---------------------------------------------------------------------- + ! + DO jk = 1, jpkm1 ! Horizontal slab + ! + ! !== Vertical tracer gradient + zdk1t(:,:) = ( ptb(:,:,jk,jn) - ptb(:,:,jk+1,jn) ) * wmask(:,:,jk+1) ! level jk+1 + ! + IF( jk == 1 ) THEN ; zdkt(:,:) = zdk1t(:,:) ! surface: zdkt(jk=1)=zdkt(jk=2) + ELSE ; zdkt(:,:) = ( ptb(:,:,jk-1,jn) - ptb(:,:,jk,jn) ) * wmask(:,:,jk) + ENDIF + DO jj = 1 , jpjm1 !== Horizontal fluxes + DO ji = 1, fs_jpim1 ! vector opt. + zabe1 = pahu(ji,jj,jk) * e2_e1u(ji,jj) * e3u_n(ji,jj,jk) + zabe2 = pahv(ji,jj,jk) * e1_e2v(ji,jj) * e3v_n(ji,jj,jk) + ! + zmsku = 1. / MAX( wmask(ji+1,jj,jk ) + wmask(ji,jj,jk+1) & + & + wmask(ji+1,jj,jk+1) + wmask(ji,jj,jk ), 1. ) + ! + zmskv = 1. / MAX( wmask(ji,jj+1,jk ) + wmask(ji,jj,jk+1) & + & + wmask(ji,jj+1,jk+1) + wmask(ji,jj,jk ), 1. ) + ! + zcof1 = - pahu(ji,jj,jk) * e2u(ji,jj) * uslp(ji,jj,jk) * zmsku + zcof2 = - pahv(ji,jj,jk) * e1v(ji,jj) * vslp(ji,jj,jk) * zmskv + ! + zftu(ji,jj,jk ) = ( zabe1 * zdit(ji,jj,jk) & + & + zcof1 * ( zdkt (ji+1,jj) + zdk1t(ji,jj) & + & + zdk1t(ji+1,jj) + zdkt (ji,jj) ) ) * umask(ji,jj,jk) + zftv(ji,jj,jk) = ( zabe2 * zdjt(ji,jj,jk) & + & + zcof2 * ( zdkt (ji,jj+1) + zdk1t(ji,jj) & + & + zdk1t(ji,jj+1) + zdkt (ji,jj) ) ) * vmask(ji,jj,jk) + END DO + END DO + ! + DO jj = 2 , jpjm1 !== horizontal divergence and add to pta + DO ji = fs_2, fs_jpim1 ! vector opt. + pta(ji,jj,jk,jn) = pta(ji,jj,jk,jn) + zsign * ( zftu(ji,jj,jk) - zftu(ji-1,jj,jk) & + & + zftv(ji,jj,jk) - zftv(ji,jj-1,jk) ) & + & * r1_e1e2t(ji,jj) / e3t_n(ji,jj,jk) + END DO + END DO + END DO ! End of slab + + !!---------------------------------------------------------------------- + !! III - vertical trend (full) + !!---------------------------------------------------------------------- + ! + ztfw(1,:,:) = 0._wp ; ztfw(jpi,:,:) = 0._wp + ! + ! Vertical fluxes + ! --------------- + ! ! Surface and bottom vertical fluxes set to zero + ztfw(:,:, 1 ) = 0._wp ; ztfw(:,:,jpk) = 0._wp + + DO jk = 2, jpkm1 ! interior (2= \brief Compute the mass flux in the x direction, cu +!! \detail Given the current pressure and velocity fields, +!! computes the mass flux in the x direction. +module compute_cu_mod + use kind_params_mod + use kernel_mod + use argument_mod + use field_mod + use grid_mod + implicit none + + private + + public invoke_compute_cu + public compute_cu, compute_cu_code + + type, extends(kernel_type) :: compute_cu + type(go_arg), dimension(3) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CU, GO_POINTWISE), & ! cu + go_arg(GO_READ, GO_CT, GO_STENCIL(000,110,000)), & ! p + go_arg(GO_READ, GO_CU, GO_POINTWISE) & ! u + /) + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_cu_code + end type compute_cu + +contains + + !=================================================== + + !> Manual implementation of the code needed to invoke + !! compute_cu_code(). + subroutine invoke_compute_cu(cufld, pfld, ufld) + implicit none + type(r2d_field), intent(inout) :: cufld + type(r2d_field), intent(in) :: pfld, ufld + ! Locals + integer :: I, J + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CU. + ! This loop writes to cu(2:M+1,1:N) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! o o o o + ! o x x x j=N + ! o x x x + ! o x x x j=1 + + ! Quantity CU is mass flux in x direction. + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! CU(I+1,J) = .5*(P(I+1,J)+P(I,J))*U(I+1,J) + ! END DO + ! END DO + + ! cu(i,j) depends upon: + ! p(i-1,j), p(i,j) : CT + ! => lateral CT neighbours of the CU pt being updated + ! u(i,j) : CU + ! => the horiz. vel. component at the CU pt being updated + + ! vi-1j+1--fij+1---vij+1---fi+1j+1 + ! | | | | + ! | | | | + ! Ti-1j----uij-----Tij-----ui+1j + ! | | | | + ! | | | | + ! vi-1j----fij-----vij-----fi+1j + ! | | | | + ! | | | | + ! Ti-1j-1--uij-1---Tij-1---ui+1j-1 + ! + + do J=cufld%internal%ystart, cufld%internal%ystop + do I=cufld%internal%xstart, cufld%internal%xstop + + call compute_cu_code(i, j, cufld%data, pfld%data, ufld%data) + end do + end do + + end subroutine invoke_compute_cu + + !=================================================== + + !> Compute the mass flux in the x direction at point (i,j) + subroutine compute_cu_code(i, j, cu, p, u) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(out), dimension(:,:) :: cu + real(go_wp), intent(in), dimension(:,:) :: p, u + + + CU(I,J) = 0.5d0*(P(i,J)+P(I-1,J))*U(I,J) + + !write (*,"('CU calc: ',I3,1x,I3,3(1x,E24.16))") & + ! i, j, p(i,j), p(i-1,j), u(i,j) + + end subroutine compute_cu_code + +end module compute_cu_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_cv_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_cv_mod.f90 new file mode 100644 index 0000000000..098c5f289d --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_cv_mod.f90 @@ -0,0 +1,116 @@ +!> \brief Compute the mass flux in the y direction, cv +!! \detail Given the current pressure and velocity fields, +!! computes the mass flux in the y direction. +module compute_cv_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_cv + public compute_cv, compute_cv_code + + type, extends(kernel_type) :: compute_cv + type(go_arg), dimension(3) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CV, GO_POINTWISE), & ! cv + go_arg(GO_READ, GO_CT, GO_STENCIL(000,010,010)), & ! p + go_arg(GO_READ, GO_CV, GO_POINTWISE) & ! v + /) + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_cv_code + end type compute_cv + +contains + + !=================================================== + + !> Manual implementation of the code needed to invoke + !! compute_cv_code(). + subroutine invoke_compute_cv(cvfld, pfld, vfld) + implicit none + type(r2d_field), intent(inout) :: cvfld + type(r2d_field), intent(in) :: pfld, vfld + ! Locals + integer :: I, J + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CV. + ! This loop writes to cv(1:M,2:N+1) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! x x x o + ! x x x o j=N + ! x x x o + ! o o o o j=1 + + ! Quantity CV is mass flux in y direction. + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! CV(I,J+1) = .5*(P(I,J+1)+P(I,J))*V(I,J+1) + ! END DO + ! END DO + + ! cv(i,j) depends upon: + ! p(i,j-1), p(i,j) : CT + ! => vertical CT neighbours of the CV pt being updated + ! v(i,j) : CV + ! => the velocity component at the CV pt being updated + + ! vi-1j+1--fij+1---vij+1---fi+1j+1 + ! | | | | + ! | | | | + ! Ti-1j----uij-----Tij-----ui+1j + ! | | | | + ! | | | | + ! vi-1j----fij-----vij-----fi+1j + ! | | | | + ! | | | | + ! Ti-1j-1--uij-1---Tij-1---ui+1j-1 + ! + + do J=cvfld%internal%ystart, cvfld%internal%ystop + do I=cvfld%internal%xstart, cvfld%internal%xstop + + call compute_cv_code(i, j, cvfld%data, pfld%data, vfld%data) + end do + end do + + end subroutine invoke_compute_cv + + !=================================================== + + !> Compute the mass flux in the y direction at point (i,j) + subroutine compute_cv_code(i, j, cv, p, v) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(out), dimension(:,:) :: cv + real(go_wp), intent(in), dimension(:,:) :: p, v + + CV(I,J) = .5d0*(P(I,J)+P(I,J-1))*V(I,J) + + end subroutine compute_cv_code + +end module compute_cv_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_h_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_h_mod.f90 new file mode 100644 index 0000000000..d75ef38c8e --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_h_mod.f90 @@ -0,0 +1,117 @@ +module compute_h_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_h + public compute_h, compute_h_code + + type, extends(kernel_type) :: compute_h + type(go_arg), dimension(4) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CT, GO_POINTWISE), & ! h + go_arg(GO_READ, GO_CT, GO_POINTWISE), & ! p + go_arg(GO_READ, GO_CU, GO_STENCIL(000,011,000)), & ! u + go_arg(GO_READ, GO_CV, GO_STENCIL(010,010,000)) & ! v + /) + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_h_code + end type compute_h + +contains + + !=================================================== + + subroutine invoke_compute_h(hfld, pfld, ufld, vfld) + implicit none + type(r2d_field), intent(inout) :: hfld + type(r2d_field), intent(in) :: pfld, ufld,vfld + ! Locals + integer :: I, J + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CT. + ! This loop writes to h(1:M,1:N) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! o o o o + ! x x x o j=N + ! x x x o + ! x x x o j=1 + + ! Quantity H is defined as: + ! H = P + 0.5(_x + _y) + ! where _x indicates average over field d in x direction. + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! H(I,J) = P(I,J)+.25*(U(I+1,J)*U(I+1,J)+U(I,J)*U(I,J) & + ! +V(I,J+1)*V(I,J+1)+V(I,J)*V(I,J)) + ! END DO + ! END DO + + ! h(i,j) depends upon: + ! p(i,j) : CT + ! u(i,j), u(i+1,j) : CU + ! => lateral CU neighbours of the CT pt being updated + ! v(i,j), v(i,j+1) : CV + ! => vertical CV neighbours of the CT pt being updated + + ! x-------vij+1---fi+1j+1 + ! | | | + ! | | | + ! uij-----Tij-----ui+1j + ! | | | + ! | | | + ! fij-----vij-----fi+1j + ! | | | + ! | | | + ! uij-1- -Tij-1---ui+1j-1 + ! + + DO J=hfld%internal%ystart, hfld%internal%ystop, 1 + DO I=hfld%internal%xstart, hfld%internal%xstop, 1 + + CALL compute_h_code(i, j, hfld%data, & + pfld%data, ufld%data, vfld%data) + END DO + END DO + + end subroutine invoke_compute_h + + !=================================================== + + SUBROUTINE compute_h_code(i, j, h, p, u, v) + IMPLICIT none + integer, intent(in) :: I, J + REAL(go_wp), INTENT(out), DIMENSION(:,:) :: h + REAL(go_wp), INTENT(in), DIMENSION(:,:) :: p, u, v + + H(I,J) = P(I,J)+.25d0*(U(I+1,J)*U(I+1,J)+U(I,J)*U(I,J) + & + V(I,J+1)*V(I,J+1)+V(I,J)*V(I,J)) + + END SUBROUTINE compute_h_code + +END MODULE compute_h_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_pnew_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_pnew_mod.f90 new file mode 100644 index 0000000000..d31c0acba9 --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_pnew_mod.f90 @@ -0,0 +1,136 @@ +module compute_pnew_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_pnew + public compute_pnew, compute_pnew_code + + type, extends(kernel_type) :: compute_pnew + type(go_arg), dimension(7) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CT, GO_POINTWISE), & ! pnew + go_arg(GO_READ, GO_CT, GO_POINTWISE), & ! pold + go_arg(GO_READ, GO_CU, GO_STENCIL(000,011,000)), & ! cu + go_arg(GO_READ, GO_CV, GO_STENCIL(010,010,000)), & ! cv + go_arg(GO_READ, GO_R_SCALAR, GO_POINTWISE), & ! tdt + go_arg(GO_READ, GO_GRID_DX_CONST), & ! dx + go_arg(GO_READ, GO_GRID_DY_CONST) & ! dy + /) + !> This kernel operates on fields that live on an + !! orthogonal, regular grid. + integer :: GRID_TYPE = GO_ORTHOGONAL_REGULAR + + !> This kernel writes only to internal grid points + INTEGER :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_pnew_code + end type compute_pnew + +contains + + !=================================================== + + subroutine invoke_compute_pnew(pnew, pold, cu, cv, tdt) + implicit none + type(r2d_field), intent(inout) :: pnew + type(r2d_field), intent(in) :: pold, cu, cv + real(go_wp), intent(in) :: tdt + ! Locals + integer :: I, J + real(go_wp) :: dx, dy + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CT. + ! This loop writes to pnew(1:M,1:N) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! o o o o + ! x x x o j=N + ! x x x o + ! x x x o j=1 + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! PNEW(I,J) = POLD(I,J)-TDTSDX*(CU(I+1,J)-CU(I,J)) & + ! -TDTSDY*(CV(I,J+1)-CV(I,J)) + ! END DO + ! END DO + + ! pnew(i,j) depends upon: + ! pold(i,j) : CT + ! cu(i,j), cu(i+1,j) : CU + ! => lateral CU neighbours of the CT pt being updated + ! cv(i,j), cv(i,j+1) : CT + ! => vertical CV neighbours of the CT pt being updated + + ! x-------vij+1---fi+1j+1 + ! | | | + ! | | | + ! uij-----Tij-----ui+1j + ! | | | + ! | | | + ! fij-----vij-----fi+1j + ! | | | + ! | | | + ! uij-1- -Tij-1---ui+1j-1 + ! + dx = pnew%grid%dx + dy = pnew%grid%dy + + DO J=pnew%internal%ystart, pnew%internal%ystop, 1 + DO I=pnew%internal%xstart, pnew%internal%xstop, 1 + + CALL compute_pnew_code(i, j, & + pnew%data, pold%data, & + cu%data, cv%data, tdt, dx, dy) + END DO + END DO + + end subroutine invoke_compute_pnew + + !=================================================== + + subroutine compute_pnew_code(i, j, & + pnew, pold, cu, cv, & + tdt, dx, dy) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(in) :: dx, dy + real(go_wp), intent(out), dimension(:,:) :: pnew + real(go_wp), intent(in), dimension(:,:) :: pold, cu, cv + real(go_wp), intent(in) :: tdt + ! Locals + real(go_wp) :: tdtsdx, tdtsdy + + !> These quantities are computed here because tdt is not + !! constant. (It is == dt for first time step, 2xdt for + !! all remaining time steps.) + tdtsdx = tdt/dx + tdtsdy = tdt/dy + + PNEW(I,J) = POLD(I,J)-TDTSDX*(CU(I+1,J)-CU(I,J)) & + -TDTSDY*(CV(I,J+1)-CV(I,J)) + + END SUBROUTINE compute_pnew_code + +END MODULE compute_pnew_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_unew_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_unew_mod.f90 new file mode 100644 index 0000000000..eac0a7933b --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_unew_mod.f90 @@ -0,0 +1,129 @@ +module compute_unew_mod + USE kind_params_mod + USE kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_unew + public compute_unew, compute_unew_code + + type, extends(kernel_type) :: compute_unew + type(go_arg), dimension(7) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CU, GO_POINTWISE), & ! unew + go_arg(GO_READ, GO_CU, GO_POINTWISE), & ! uold + go_arg(GO_READ, GO_CF, GO_STENCIL(010,010,000)), & ! z + go_arg(GO_READ, GO_CV, GO_STENCIL(110,110,000)), & ! cv + go_arg(GO_READ, GO_CT, GO_STENCIL(000,110,000)), & ! h + go_arg(GO_READ, GO_R_SCALAR, GO_POINTWISE), & ! tdt + go_arg(GO_READ, GO_GRID_DY_CONST) & ! dy + /) + !> This kernel operates on fields that live on an + !! orthogonal, regular grid. + integer :: GRID_TYPE = GO_ORTHOGONAL_REGULAR + + !> We only have one value per grid point and that means + !! we have a single DOF per grid point. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_unew_code + end type compute_unew + +contains + + !=================================================== + + subroutine invoke_compute_unew(unew, uold, z, cv, h, tdt) + implicit none + type(r2d_field), intent(inout) :: unew + type(r2d_field), intent(in) :: uold, z, cv, h + real(go_wp), intent(in) :: tdt + ! Locals + integer :: I, J + real(go_wp) :: dx + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CU. + ! This loop writes to unew(2:M+1,1:N) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! o o o o + ! o x x x j=N + ! o x x x + ! o x x x j=1 + + ! unew(i,j) depends upon: + ! uold(i,j) + ! z(i,j+1), z(i,j) + ! cv(i,j), cv(i,j+1), cv(i-1,j+1), cv(i-1,j) + ! h(i,j), h(i-1,j) + + ! Swap indices, e.g. XX(i+1,j) => YY(i,j+1) + ! Any field on U replaced with field on V + ! => produces same code for the update of corresponding field on V. + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! UNEW(I+1,J) = UOLD(I+1,J)+ & + ! TDTS8*(Z(I+1,J+1)+Z(I+1,J))*(CV(I+1,J+1)+CV(I,J+1)+CV(I,J) & + ! +CV(I+1,J))-TDTSDX*(H(I+1,J)-H(I,J)) + ! END DO + ! END DO + dx = unew%grid%dx + + DO J=unew%internal%ystart, unew%internal%ystop, 1 + DO I=unew%internal%xstart, unew%internal%xstop, 1 + + CALL compute_unew_code(i, j, & + unew%data, uold%data, & + z%data, cv%data, h%data, tdt, dx) + END DO + END DO + + END SUBROUTINE invoke_compute_unew + + !=================================================== + + subroutine compute_unew_code(i, j, & + unew, uold, z, cv, h, tdt, dx) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(in) :: dx + real(go_wp), intent(out), dimension(:,:) :: unew + real(go_wp), intent(in), dimension(:,:) :: uold, z, cv, h + real(go_wp), intent(in) :: tdt + ! Locals + real(go_wp) :: tdts8, tdtsdx + + !> These quantities are computed here because tdt is not + !! constant. (It is == dt for first time step, 2xdt for + !! all remaining time steps.) + tdts8 = tdt/8.0d0 + tdtsdx = tdt/dx + + UNEW(I,J) = UOLD(I,J) + & + TDTS8*(Z(I,J+1) + Z(I,J)) * & + (CV(I,J+1)+CV(I-1,J+1)+CV(I-1,J)+CV(I,J)) - & + TDTSDX*(H(I,J)-H(I-1,J)) + + end subroutine compute_unew_code + +end module compute_unew_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_vnew_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_vnew_mod.f90 new file mode 100644 index 0000000000..61ae02e667 --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_vnew_mod.f90 @@ -0,0 +1,141 @@ +module compute_vnew_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_vnew + public compute_vnew, compute_vnew_code + + TYPE, EXTENDS(kernel_type) :: compute_vnew + TYPE(go_arg), DIMENSION(7) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CV, GO_POINTWISE), & ! vnew + go_arg(GO_READ, GO_CV, GO_POINTWISE), & ! vold + go_arg(GO_READ, GO_CF, GO_STENCIL(000,011,000)), & ! z + go_arg(GO_READ, GO_CU, GO_STENCIL(000,011,011)), & ! cu + go_arg(GO_READ, GO_CT, GO_STENCIL(000,010,010)), & ! h + go_arg(GO_READ, GO_R_SCALAR, GO_POINTWISE), & ! tdt + go_arg(GO_READ, GO_GRID_DY_CONST) & ! dy + /) + !> This kernel operates on fields that live on an + !! orthogonal, regular grid. + integer :: GRID_TYPE = GO_ORTHOGONAL_REGULAR + + !> We only have one value per grid point and that means + !! we have a single DOF per grid point. + INTEGER :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + CONTAINS + procedure, nopass :: code => compute_vnew_code + END TYPE compute_vnew + +CONTAINS + + !=================================================== + + subroutine invoke_compute_vnew(vnew, vold, z, cu, h, tdt) + implicit none + type(r2d_field), intent(inout) :: vnew + type(r2d_field), intent(in) :: vold, z, cu, h + real(go_wp), intent(in) :: tdt + ! Locals + integer :: I, J + real(go_wp) :: dy + + ! Note that we do not loop over the full extent of the field. + ! Fields are allocated with extents (M+1,N+1). + ! Presumably the extra row and column are needed for periodic BCs. + ! We are updating a quantity on CU. + ! This loop writes to vnew(1:M,2:N+1) so this looks like + ! (using x to indicate a location that is written): + ! + ! i=1 i=M + ! x x x o + ! x x x o j=N + ! x x x o + ! o o o o j=1 + + ! Original code looked like: + ! + ! DO J=1,N + ! DO I=1,M + ! VNEW(I,J+1) = VOLD(I,J+1)-TDTS8*(Z(I+1,J+1)+Z(I,J+1)) & + ! *(CU(I+1,J+1)+CU(I,J+1)+CU(I,J)+CU(I+1,J)) & + ! -TDTSDY*(H(I,J+1)-H(I,J)) + ! END DO + ! END DO + + ! vnew(i,j) depends upon: + ! vold(i,j) : CV + ! z(i+1,j), z(i,j) : CF + ! => lateral CF neighbours of the CV pt being updated + ! cu(i,j), cu(i+1,j),cu(i,j-1),cu(i+1,j-1) : CU + ! => all CU neighbours of the CV pt being updated + ! h(i,j), h(i,j-1) : CT + ! => vertical CT neighbours of the CV pt being updated + + ! x-------x-------fi+1j+1 + ! | | | + ! | | | + ! uij-----Tij-----ui+1j + ! | | | + ! | | | + ! fij-----vij-----fi+1j + ! | | | + ! | | | + ! uij-1- -Tij-1---ui+1j-1 + ! + + dy = vnew%grid%dy + + DO J=vnew%internal%ystart, vnew%internal%ystop, 1 + DO I=vnew%internal%xstart, vnew%internal%xstop, 1 + + CALL compute_vnew_code(i, j, & + vnew%data, vold%data, & + z%data, cu%data, h%data, tdt, dy) + END DO + END DO + + END SUBROUTINE invoke_compute_vnew + + !=================================================== + + subroutine compute_vnew_code(i, j, & + vnew, vold, z, cu, h, tdt, dy) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(in) :: dy + REAL(go_wp), intent(out), DIMENSION(:,:) :: vnew + REAL(go_wp), intent(in), DIMENSION(:,:) :: vold, z, cu, h + REAL(go_wp), intent(in) :: tdt + ! Locals + REAL(go_wp) :: tdts8, tdtsdy + + !> These quantities are computed here because tdt is not + !! constant. (It is == dt for first time step, 2xdt for + !! all remaining time steps.) + tdts8 = tdt/8.0d0 + tdtsdy = tdt/dy + + VNEW(I,J) = VOLD(I,J)- & + TDTS8*(Z(I+1,J)+Z(I,J)) & + *(CU(I+1,J)+CU(I,J)+CU(I,J-1)+CU(I+1,J-1)) & + -TDTSDY*(H(I,J)-H(I,J-1)) + + END SUBROUTINE compute_vnew_code + +END MODULE compute_vnew_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_z_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_z_mod.f90 new file mode 100644 index 0000000000..db640947fd --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_z_mod.f90 @@ -0,0 +1,101 @@ +!> \brief Compute the potential vorticity, z +!! \detail Given the current pressure and velocity fields, +!! computes the potential voriticity. +module compute_z_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + private + + public invoke_compute_z + public compute_z, compute_z_code + + type, extends(kernel_type) :: compute_z + type(go_arg), dimension(6) :: meta_args = & + (/ go_arg(GO_WRITE, GO_CF, GO_POINTWISE), & ! z + go_arg(GO_READ, GO_CT, GO_STENCIL(000,110,110)), & ! p + go_arg(GO_READ, GO_CU, GO_STENCIL(000,010,010)), & ! u + go_arg(GO_READ, GO_CV, GO_STENCIL(000,110,000)), & ! v + go_arg(GO_READ, GO_GRID_DX_CONST), & ! dx + go_arg(GO_READ, GO_GRID_DY_CONST) & ! dy + /) + !> This kernel operates on fields that live on an + !! orthogonal, regular grid. + integer :: GRID_TYPE = GO_ORTHOGONAL_REGULAR + + !> This kernel writes only to internal points of the + !! simulation domain. + integer :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel assumes that the U,V and F points that + !! share the same index as a given T point are those immediately + !! to the South and West of it. + integer :: index_offset = GO_OFFSET_SW + + contains + procedure, nopass :: code => compute_z_code + end type compute_z + +contains + + !=================================================== + + !> Manual implementation of the code needed to invoke + !! compute_z_code(). + subroutine invoke_compute_z(zfld, pfld, ufld, vfld) + implicit none + type(r2d_field), intent(inout) :: zfld + type(r2d_field), intent(in) :: pfld, ufld, vfld + ! Locals + integer :: I, J + real(go_wp) :: dx, dy + + dx = zfld%grid%dx + dy = zfld%grid%dy + + do J=zfld%internal%ystart, zfld%internal%ystop, 1 + do I=zfld%internal%xstart, zfld%internal%xstop, 1 + + call compute_z_code(i, j, & + zfld%data, & + pfld%data, & + ufld%data, & + vfld%data, & + dx, dy) + + end do + end do + + end subroutine invoke_compute_z + + !=================================================== + + !> Compute the potential vorticity on the grid point (i,j) + subroutine compute_z_code(i, j, z, p, u, v, dx, dy) + implicit none + integer, intent(in) :: I, J + real(go_wp), intent(in) :: dx, dy + real(go_wp), intent(inout), dimension(:,:) :: z + real(go_wp), intent(in), dimension(:,:) :: p, u, v + + ! Original code looked like: + ! DO J=1,N + ! DO I=1,M + ! Z(I+1,J+1) =(FSDX*(V(I+1,J+1)-V(I,J+1))-FSDY*(U(I+1,J+1) & + ! -U(I+1,J)))/(P(I,J)+P(I+1,J)+P(I+1,J+1)+P(I,J+1)) + + Z(I,J) =( (4.0d0/dx)*( V(I,J)-V(I-1,J))- & + (4.0d0/dy)*( U(I,J)-U(I,J-1)) ) / & + (P(I-1,J-1)+P(I,J-1)+ P(I,J)+P(I-1,J)) + + end subroutine compute_z_code + +end module compute_z_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/infrastructure_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/infrastructure_mod.f90 new file mode 100644 index 0000000000..69352025da --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/infrastructure_mod.f90 @@ -0,0 +1,45 @@ + +module infrastructure_mod + use kind_params_mod + use kernel_mod + use argument_mod + use grid_mod + use field_mod + implicit none + + !> This module is for kernels that implement what will one + !! day be infrastructure calls. For the moment we provide + !! these in normal kernel form so that they can be included + !! in a standard invoke(). + + type, extends(kernel_type) :: copy + type(go_arg), dimension(2) :: meta_args = & + (/ go_arg(GO_WRITE, GO_EVERY, GO_POINTWISE), & ! output fld + go_arg(GO_READ, GO_EVERY, GO_POINTWISE) & ! input fld + /) + !> This kernel copies a whole field + integer :: ITERATES_OVER = GO_ALL_PTS + + !> This kernel doesn't care about grids and offsets + integer :: index_offset = GO_OFFSET_ANY + + contains + procedure, nopass :: code => field_copy_code + end type copy + +contains + + !=================================================== + + subroutine field_copy_code(ji, jj, & + output, input) + implicit none + integer, intent(in) :: ji, jj + real(go_wp), dimension(:,:), intent(in) :: input + real(go_wp), dimension(:,:), intent(out) :: output + + output(ji,jj) = input(ji,jj) + + end subroutine field_copy_code + +end module infrastructure_mod diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/shallow_alg.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/shallow_alg.f90 new file mode 100644 index 0000000000..3eef8d8326 --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/shallow_alg.f90 @@ -0,0 +1,251 @@ +program shallow + +!> @mainpage BENCHMARK WEATHER PREDICTION PROGRAM FOR COMPARING THE +!! PERFORMANCE OF CURRENT SUPERCOMPUTERS. THE MODEL IS +!! BASED OF THE PAPER - THE DYNAMICS OF FINITE-DIFFERENCE +!! MODELS OF THE SHALLOW-WATER EQUATIONS, BY ROBERT SADOURNY +!! J. ATM. SCIENCES, VOL 32, NO 4, APRIL 1975. +!! +!! CODE BY PAUL N. SWARZTRAUBER, NATIONAL CENTER FOR +!! ATMOSPHERIC RESEARCH, BOULDER, CO, OCTOBER 1984. +!! Modified by Juliana Rew, NCAR, January 2006 +! +! In this version, shallow4.f, initial and calculated values +! of U, V, and P are written to a netCDF file +! for later use in visualizing the results. The netCDF data +! management library is freely available from +! http://www.unidata.ucar.edu/software/netcdf +! This code is still serial but has been brought up to modern +! Fortran constructs and uses portable intrinsic Fortran 90 timing routines +! This can be compiled on the IBM SP using: +! xlf90 -qmaxmem=-1 -g -o shallow4 -qfixed=132 -qsclk=micro \ +! -I/usr/local/include shallow4.f -L/usr/local/lib32/r4i4 -l netcdf +! where the -L and -I point to local installation of netCDF +! +! Changes from shallow4.f (Annette Osprey, January 2010): +! - Converted to free-form fortran 90. +! - Some tidying up of old commented-out code. +! - Explicit type declarations. +! - Variables n, m, itmax and mprint read in from namelist. +! - Dynamic array allocation. +! - Only write to netcdf at mprint timesteps. +! - Don't write wrap-around points to NetCDF file. +! - Use 8-byte reals. +! +! This version heavily modified as part of the GOcean-2D project +! with the mantra "all computation must occur in a kernel." +! Andrew Porter, April 2014 + + use kind_params_mod + use shallow_io_mod + use timing_mod + use gocean_mod, only: model_write_log + use model_mod + use grid_mod + use field_mod + use initial_conditions_mod + use time_smooth_mod, only: time_smooth + use apply_bcs_mod, only: invoke_apply_bcs + use compute_cu_mod, only: compute_cu + use compute_cv_mod, only: compute_cv + use compute_z_mod, only: compute_z + use compute_h_mod, only: compute_h + use compute_unew_mod, only: compute_unew + use compute_vnew_mod, only: compute_vnew + use compute_pnew_mod, only: compute_pnew + use infrastructure_mod,only: copy + implicit none + + type(grid_type), target :: model_grid + !> Pressure at {current,previous,next} time step + type(r2d_field) :: p_fld, pold_fld, pnew_fld + !> Velocity in x direction at {current,previous,next} time step + type(r2d_field) :: u_fld, uold_fld, unew_fld + !> Velocity in x direction at {current,previous,next} time step + type(r2d_field) :: v_fld, vold_fld, vnew_fld + !> Mass flux in x and y directions + type(r2d_field) :: cu_fld, cv_fld + !> Potential vorticity + type(r2d_field) :: z_fld + !> Surface height + type(r2d_field) :: h_fld + !> Stream function + type(r2d_field) :: psi_fld + + !> Loop counter for time-stepping loop + INTEGER :: ncycle, itmax + + !> Integer tags for timers + INTEGER :: idxt0, idxt1 + REAL(KIND=go_wp) dt, tdt + + !> \todo The call to grid_type here should *not* specify the grid + !! offset choice as that is an implementation detail. PSyclone + !! should re-write this call to pass the offset information after it + !! has examined the kernels to see what they are expecting. + model_grid = grid_type(GO_ARAKAWA_C, & + (/GO_BC_PERIODIC,GO_BC_PERIODIC,GO_BC_NONE/), & + GO_OFFSET_SW) + + ! ** Initialisations of model parameters (dt etc) ** + CALL model_init(model_grid) + + ! Create fields on this grid + p_fld = r2d_field(model_grid, GO_T_POINTS) + pold_fld = r2d_field(model_grid, GO_T_POINTS) + pnew_fld = r2d_field(model_grid, GO_T_POINTS) + + u_fld = r2d_field(model_grid, GO_U_POINTS) + uold_fld = r2d_field(model_grid, GO_U_POINTS) + unew_fld = r2d_field(model_grid, GO_U_POINTS) + + v_fld = r2d_field(model_grid, GO_V_POINTS) + vold_fld = r2d_field(model_grid, GO_V_POINTS) + vnew_fld = r2d_field(model_grid, GO_V_POINTS) + + cu_fld = r2d_field(model_grid, GO_U_POINTS) + + cv_fld = r2d_field(model_grid, GO_V_POINTS) + + z_fld = r2d_field(model_grid, GO_F_POINTS) + + h_fld = r2d_field(model_grid, GO_T_POINTS) + + psi_fld = r2d_field(model_grid, GO_F_POINTS) + + ! NOTE BELOW THAT TWO DELTA T (TDT) IS SET TO DT ON THE FIRST + ! CYCLE AFTER WHICH IT IS RESET TO DT+DT. + tdt = dt + + ! INITIAL VALUES OF THE STREAM FUNCTION AND P + + call init_initial_condition_params(p_fld) + call invoke_init_stream_fn_kernel(psi_fld) + call init_pressure(p_fld) + + ! INITIALIZE VELOCITIES + + call init_velocity_u(u_fld, psi_fld) + call init_velocity_v(v_fld, psi_fld) + + ! PERIODIC CONTINUATION + call invoke_apply_bcs(u_fld) + call invoke_apply_bcs(v_fld) + + ! Generate and output checksums of initial fields + CALL model_write_log("('psi initial CHECKSUM = ',E24.16)", & + field_checksum(psi_fld)) + CALL model_write_log("('P initial CHECKSUM = ',E24.16)", & + field_checksum(p_fld)) + CALL model_write_log("('U initial CHECKSUM = ',E24.16)", & + field_checksum(u_fld)) + CALL model_write_log("('V initial CHECKSUM = ',E24.16)", & + field_checksum(v_fld)) + + ! Initialise fields that will hold data at previous time step + CALL copy_field(u_fld, uold_fld) + CALL copy_field(v_fld, vold_fld) + CALL copy_field(p_fld, pold_fld) + + ! Write initial values of p, u, and v into a netCDF file + call ascii_write(0, 'psifld.dat', psi_fld%data, & + psi_fld%internal%nx, psi_fld%internal%ny, & + psi_fld%internal%xstart, psi_fld%internal%ystart) + CALL model_write(0, p_fld, u_fld, v_fld) + + ! Start timer + CALL timer_start('Time-stepping',idxt0) + + ! ** Start of time loop ** + DO ncycle=1,itmax + + ! COMPUTE CAPITAL U, CAPITAL V, Z AND H + + CALL timer_start('Compute c{u,v},z,h', idxt1) + + call invoke( compute_cu(CU_fld, p_fld, u_fld), & + compute_cv(CV_fld, p_fld, v_fld), & + compute_z(z_fld, p_fld, u_fld, v_fld), & + compute_h(h_fld, p_fld, u_fld, v_fld) ) + + call timer_stop(idxt1) + + ! PERIODIC CONTINUATION + + call timer_start('PBCs-1',idxt1) + ! This call could be generated automatically by PSyclone + call invoke_apply_bcs(CU_fld) + call invoke_apply_bcs(CV_fld) + call invoke_apply_bcs(H_fld) + call invoke_apply_bcs(Z_fld) + call timer_stop(idxt1) + + ! COMPUTE NEW VALUES U,V AND P + + call timer_start('Compute new fields', idxt1) + call invoke( compute_unew(unew_fld, uold_fld, z_fld, cv_fld, h_fld, tdt), & + compute_vnew(vnew_fld, vold_fld, z_fld, cu_fld, h_fld, tdt), & + compute_pnew(pnew_fld, pold_fld, cu_fld, cv_fld, tdt) ) + call timer_stop(idxt1) + + ! PERIODIC CONTINUATION + call timer_start('PBCs-2',idxt1) + ! This call could be generated by PSyclone + call invoke_apply_bcs(UNEW_fld) + call invoke_apply_bcs(VNEW_fld) + call invoke_apply_bcs(PNEW_fld) + call timer_stop(idxt1) + + ! Time is in seconds but we never actually need it + !time = time + dt + + call model_write(ncycle, p_fld, u_fld, v_fld) + + ! TIME SMOOTHING AND UPDATE FOR NEXT CYCLE + if(NCYCLE .GT. 1) then + + call timer_start('Time smoothing',idxt1) + + call invoke( time_smooth(u_fld, UNEW_fld, UOLD_fld), & + time_smooth(v_fld, VNEW_fld, VOLD_fld), & + time_smooth(p_fld, PNEW_fld, POLD_fld) ) + + call timer_stop(idxt1) + + else ! ncycle == 1 + + ! Make TDT actually = 2*DT + tdt = tdt + dt + + endif ! ncycle > 1 + + call timer_start('Field copy',idxt1) + + call invoke( & + copy(u_fld, unew_fld), & + copy(v_fld, vnew_fld), & + copy(p_fld, pnew_fld) & + ) +! call copy_field(UNEW_fld, U_fld) +! call copy_field(VNEW_fld, V_fld) +! call copy_field(PNEW_fld, p_fld) + + call timer_stop(idxt1) + + end do + + ! ** End of time loop ** + + call timer_stop(idxt0) + + ! Output field checksums at end of run for correctness check + call model_write_log("('P CHECKSUM after ',I6,' steps = ',E24.16)", & + itmax, field_checksum(pnew_fld)) + call model_write_log("('U CHECKSUM after ',I6,' steps = ',E24.16)", & + itmax, field_checksum(unew_fld)) + call model_write_log("('V CHECKSUM after ',I6,' steps = ',E24.16)", & + itmax, field_checksum(vnew_fld)) + + call model_finalise() + +end program shallow diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/time_smooth_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/time_smooth_mod.f90 new file mode 100644 index 0000000000..208e29a7cd --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/time_smooth_mod.f90 @@ -0,0 +1,102 @@ +module time_smooth_mod + use kind_params_mod + use grid_mod + use field_mod + use kernel_mod + use argument_mod + IMPLICIT none + + PRIVATE + + PUBLIC time_smooth_init, invoke_time_smooth + PUBLIC time_smooth, time_smooth_code + + !> Parameter for time smoothing + REAL(go_wp), save :: alpha + + !> The time smoothing operates in time rather than space + !! and therefore takes three fields defined on any one + !! of the four grid point types (T, U, V or Q). + !! Presumably FE should be FD for us and maybe CELLS + !! should be COLUMNS? + TYPE, EXTENDS(kernel_type) :: time_smooth + TYPE(go_arg), DIMENSION(3) :: meta_args = & + (/ go_arg(GO_READ, GO_EVERY, GO_POINTWISE), & + go_arg(GO_READ, GO_EVERY, GO_POINTWISE), & + go_arg(GO_READWRITE , GO_EVERY, GO_POINTWISE) & + /) + + !> This kernel writes only to internal points of the + !! simulation domain. + INTEGER :: ITERATES_OVER = GO_INTERNAL_PTS + + !> Although the staggering of variables used in an Arakawa + !! C grid is well defined, the way in which they are indexed is + !! an implementation choice. This can be thought of as choosing + !! which grid-point types have the same (i,j) index as a T + !! point. This kernel is independent of this choice (because it + !! acts in time rather than space). + integer :: index_offset = GO_OFFSET_ANY + + CONTAINS + procedure, nopass :: code => time_smooth_code + END type time_smooth + +CONTAINS + + !=================================================== + + !> Initialise the time-smoothing module. Sets parameter + !! alpha that is used in the time-smooth kernel. + SUBROUTINE time_smooth_init(alpha_tmp) + IMPLICIT none + REAL(go_wp), INTENT(in) :: alpha_tmp + + alpha = alpha_tmp + + END SUBROUTINE time_smooth_init + + !=================================================== + + !> Manual implementation of code to invoke the time-smoothing + !! kernel + subroutine invoke_time_smooth(field, field_new, field_old) + implicit none + type(r2d_field), intent(in) :: field + type(r2d_field), intent(in) :: field_new + type(r2d_field), intent(inout) :: field_old + ! Locals + integer :: i, j + integer :: idim1, idim2 + + ! Here we will query what should be field objects to get at + ! raw data. + idim1 = SIZE(field%data, 1) + idim2 = SIZE(field%data, 2) + + ! Loop over 'columns' + DO J=1,idim2 + DO I=1,idim1 + CALL time_smooth_code(i, j, & + field%data, field_new%data, field_old%data) + END DO + END DO + + end subroutine invoke_time_smooth + + !=================================================== + + !> Kernel to smooth supplied field in time + SUBROUTINE time_smooth_code(i, j, field, field_new, field_old) + IMPLICIT none + INTEGER, INTENT(in) :: i, j + REAL(go_wp), INTENT(in), DIMENSION(:,:) :: field + REAL(go_wp), INTENT(in), DIMENSION(:,:) :: field_new + REAL(go_wp), INTENT(inout), DIMENSION(:,:) :: field_old + + field_old(i,j) = field(i,j) + & + alpha*(field_new(i,j) - 2.0d0*field(i,j) + field_old(i,j)) + + END SUBROUTINE time_smooth_code + +END MODULE time_smooth_mod diff --git a/src/psyclone/tests/utilities.py b/src/psyclone/tests/utilities.py index 169441cfed..f5885454dd 100644 --- a/src/psyclone/tests/utilities.py +++ b/src/psyclone/tests/utilities.py @@ -75,7 +75,6 @@ def __init__(self, value): PSycloneError.value = "Compile error: " + str(value) -# ============================================================================= def line_number(root, string_name): '''Helper routine which returns the first index of the supplied name in the root object, when it is converted into a string, or @@ -98,7 +97,6 @@ def line_number(root, string_name): return -1 -# ============================================================================= def count_lines(root, string_name): '''Helper routine which returns the number of lines that contain the supplied string. @@ -120,7 +118,6 @@ def count_lines(root, string_name): return count -# ============================================================================= def print_diffs(expected, actual): ''' Pretty-print the diff between the two, possibly multi-line, strings @@ -135,7 +132,6 @@ def print_diffs(expected, actual): pprint(diff_list) -# ============================================================================= @contextmanager def change_dir(new_dir): '''This is a small context manager that changes the current working @@ -154,7 +150,6 @@ def change_dir(new_dir): os.chdir(prev_dir) -# ============================================================================= class Compile(): '''This class provides compile functionality to the testing framework. It stores the name of the compiler, compiler flags, and a temporary @@ -507,8 +502,7 @@ def string_compiles(self, code: str) -> bool: return success -# ============================================================================= -def get_base_path(api: str) -> str: +def get_base_path(api: str = "") -> str: '''Get the absolute base path for the specified API relative to the 'tests/test_files' directory, i.e. the directory in which all Fortran test files are stored. @@ -524,7 +518,7 @@ def get_base_path(api: str) -> str: # Note that the nemo files are outside of the default tests/test_files # directory, they are in tests/nemo/test_files api_2_path = {"lfric": "lfric", - "nemo": "../nemo/test_files", + "": "../nemo/test_files", "gocean": "gocean1p0"} try: dir_name = api_2_path[api] @@ -555,7 +549,6 @@ def get_infrastructure_path(api: str) -> str: f"Supported values are 'lfric' and 'gocean'.") -# ============================================================================= def get_invoke(algfile: str, api: str, idx: Optional[int] = None, @@ -600,7 +593,50 @@ def get_invoke(algfile: str, return psy, invoke -# ============================================================================= +def get_psylayer_schedule( + algfile: str, + api: str, + invoke_name: str = "", + dist_mem: bool = False +) -> Node: + ''' + Utility method to get the psylayer schedule associated with the given + algorithm file. + + :param algfile: name of the Algorithm source file (Fortran). + :param api: which PSyclone API this Algorithm uses. + :param invoke_name: return the schedule of a given invoke. + :param dist_mem: if the psy instance should be created with or + without distributed memory support. + + :returns: the associated psylayer schedule. + + ''' + + config = Config.get() + config.api = api + # Ensure infrastructure module files can be discovered. + # config.include_paths.append(get_infrastructure_path(api)) + filepath = os.path.join(get_base_path(api), algfile) + + _, info = parse(filepath, api=api) + psy = PSyFactory(api, distributed_memory=dist_mem).create(info) + if invoke_name: + return psy.invokes.get(invoke_name).schedule + else: + return psy.invokes.invoke_list[0].schedule + + +def get_file_path(relative_path: str): + ''' + :param relative_path: given a relative file path. + + :returns: its absolute file path inside the test directory. + + ''' + return os.path.join(get_base_path(), relative_path) + + def get_ast(api: str, filename: str) -> BeginSource: '''Returns the fparser1 parse tree for a filename that is stored in the test files for the specified API. @@ -618,7 +654,6 @@ def get_ast(api: str, filename: str) -> BeginSource: return ast -# ============================================================================= def check_links(parent: Node, children: list[Node]) -> None: '''Utility routine to check that the parent node has children as its children in the order specified and that the children have parent @@ -665,7 +700,6 @@ def make_external_module(monkeypatch, monkeypatch.setitem(mman._modules, mod_name, minfo) -# ============================================================================ min_version_3_10 = pytest.mark.skipif( sys.version_info < (3, 10), reason="tests require python 3.10 or higher" ) diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index fd15a432ef..fd887cabd0 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -356,17 +356,18 @@ class ColourTrans(LoopTrans): Apply a colouring transformation to a loop (in order to permit a subsequent parallelisation over colours). For example: - >>> invoke = ... - >>> schedule = invoke.schedule - >>> - >>> ctrans = ColourTrans() - >>> - >>> # Colour all of the loops - >>> for child in schedule.children: - >>> ctrans.apply(child) - >>> - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + # FIXME + # >>> invoke = ... + # >>> schedule = invoke.schedule + # >>> + # >>> ctrans = ColourTrans() + # >>> + # >>> # Colour all of the loops + # >>> for child in schedule.children: + # >>> ctrans.apply(child) + # >>> + # >>> # Uncomment the following line to see a text view of the schedule + # >>> # print(schedule.view()) ''' def __str__(self): @@ -445,34 +446,24 @@ class LFRicColourTrans(ColourTrans): '''Split an LFRic loop over cells into colours so that it can be parallelised. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> import transformations - >>> import os - >>> import pytest + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "4.6_multikernel_invokes.f90" + >>> schedule = get_psylayer_schedule(filename, api="lfric") >>> - >>> TEST_API = "lfric" - >>> _,info=parse(os.path.join(os.path.dirname(os.path.abspath(__file__)), - >>> "tests", "test_files", "lfric", - >>> "4.6_multikernel_invokes.f90"), - >>> api=TEST_API) - >>> psy = PSyFactory(TEST_API).create(info) - >>> invoke = psy.invokes.get('invoke_0') - >>> schedule = invoke.schedule + >>> from psyclone.psyir.nodes import Loop + >>> from psyclone.transformations import ( + ... LFRicColourTrans, LFRicOMPParallelLoopTrans) >>> >>> ctrans = LFRicColourTrans() >>> otrans = LFRicOMPParallelLoopTrans() >>> >>> # Colour all of the loops - >>> for child in schedule.children: - >>> ctrans.apply(child) + >>> for child in schedule.walk(Loop, stop_type=Loop): + ... ctrans.apply(child) >>> >>> # Then apply OpenMP to each of the colour loops - >>> for child in schedule.children: - >>> otrans.apply(child.children[0]) - >>> - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + >>> for child in schedule.walk(Loop, stop_type=Loop): + ... otrans.apply(child.loop_body[0]) Colouring in the LFRic API is subject to the following rules: @@ -686,21 +677,15 @@ class OMPSingleTrans(ParallelRegionTrans): For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPSingleTrans >>> from psyclone.psyir.transformations import OMPParallelTrans >>> singletrans = OMPSingleTrans() >>> paralleltrans = OMPParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Enclose all of these loops within a single OpenMP >>> # SINGLE region >>> singletrans.apply(schedule.children) @@ -817,29 +802,21 @@ class OMPMasterTrans(ParallelRegionTrans): For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPMasterTrans >>> from psyclone.psyir.transformations import OMPParallelTrans >>> mastertrans = OMPMasterTrans() >>> paralleltrans = OMPParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Enclose all of these loops within a single OpenMP >>> # MASTER region >>> mastertrans.apply(schedule.children) >>> # Enclose all of these loops within a single OpenMP >>> # PARALLEL region >>> paralleltrans.apply(schedule.children) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) ''' # The types of node that this transformation cannot enclose @@ -1251,17 +1228,19 @@ class LFRicAsyncHaloExchangeTrans(Transformation): >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory >>> api = "lfric" - >>> ast, invokeInfo = parse("file.f90", api=api) - >>> psy=PSyFactory(api).create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) >>> - >>> from psyclone.transformations import LFRicAsyncHaloExchangeTrans - >>> trans = LFRicAsyncHaloExchangeTrans() - >>> trans.apply(schedule.children[0]) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + # FIXME + # >>> ast, invokeInfo = parse("file.f90", api=api) + # >>> psy=PSyFactory(api).create(invokeInfo) + # >>> schedule = psy.invokes.get('invoke_0').schedule + # >>> # Uncomment the following line to see a text view of the schedule + # >>> # print(schedule.view()) + # >>> + # >>> from psyclone.transformations import LFRicAsyncHaloExchangeTrans + # >>> trans = LFRicAsyncHaloExchangeTrans() + # >>> trans.apply(schedule.children[0]) + # >>> # Uncomment the following line to see a text view of the schedule + # >>> # print(schedule.view()) ''' @@ -1337,20 +1316,22 @@ class LFRicKernelConstTrans(Transformation, CalleeTransformationMixin): >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory >>> api = "lfric" - >>> ast, invokeInfo = parse("file.f90", api=api) - >>> psy=PSyFactory(api).create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) >>> - >>> from psyclone.transformations import LFRicKernelConstTrans - >>> trans = LFRicKernelConstTrans() - >>> for kernel in schedule.coded_kernels(): - >>> trans.apply(kernel, number_of_layers=150) - >>> kernel_schedule = kernel.get_callees()[0] - >>> # Uncomment the following line to see a text view of the - >>> # symbol table - >>> # print(kernel_schedule.symbol_table.view()) + # FIXME + # >>> ast, invokeInfo = parse("file.f90", api=api) + # >>> psy=PSyFactory(api).create(invokeInfo) + # >>> schedule = psy.invokes.get('invoke_0').schedule + # >>> # Uncomment the following line to see a text view of the schedule + # >>> # print(schedule.view()) + # >>> + # >>> from psyclone.transformations import LFRicKernelConstTrans + # >>> trans = LFRicKernelConstTrans() + # >>> for kernel in schedule.coded_kernels(): + # >>> trans.apply(kernel, number_of_layers=150) + # >>> kernel_schedule = kernel.get_callees()[0] + # >>> # Uncomment the following line to see a text view of the + # >>> # symbol table + # >>> # print(kernel_schedule.symbol_table.view()) ''' @@ -1668,22 +1649,17 @@ class ACCEnterDataTrans(Transformation): Adds an OpenACC "enter data" directive to a Schedule. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> - >>> from psyclone.transformations import \ - ACCEnterDataTrans, ACCLoopTrans, ACCParallelTrans + >>> from psyclone.transformations import ( + ... ACCEnterDataTrans, ACCParallelTrans) + >>> from psyclone.psyir.transformations import ACCLoopTrans >>> dtrans = ACCEnterDataTrans() >>> ltrans = ACCLoopTrans() >>> ptrans = ACCParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Apply the OpenACC Loop transformation to *every* loop in the schedule >>> for child in schedule.children[:]: ... ltrans.apply(child) @@ -1693,9 +1669,6 @@ class ACCEnterDataTrans(Transformation): >>> >>> # Add an enter data directive >>> dtrans.apply(schedule) - >>> - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) ''' def __str__(self): @@ -1822,19 +1795,20 @@ class ACCRoutineTrans(Transformation, MarkRoutineForGPUMixin, (causing it to be compiled for the OpenACC accelerator device). For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> + >>> from psyclone.psyGen import CodedKern >>> from psyclone.transformations import ACCRoutineTrans >>> rtrans = ACCRoutineTrans() + >>> from psyclone.domain.common.transformations import ( + ... KernelModuleInlineTrans) + >>> itrans = KernelModuleInlineTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> kern = schedule.children[0].children[0].children[0] + >>> kern = schedule.walk(CodedKern)[0] + >>> # Bring the kernel into the same module + >>> itrans.apply(kern) >>> # Transform the kernel >>> rtrans.apply(kern) @@ -1941,8 +1915,10 @@ class ACCDataTrans(RegionTrans): For example: - >>> from psyclone.psyir.frontend import FortranReader - >>> psyir = FortranReader().psyir_from_source(NEMO_SOURCE_FILE) + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.tests.utilities import get_file_path + >>> filename = get_file_path("code/tra_adv.F90") + >>> psyir = FortranReader().psyir_from_file(filename) >>> >>> from psyclone.transformations import ACCDataTrans >>> from psyclone.psyir.transformations import ACCKernelsTrans @@ -1950,15 +1926,13 @@ class ACCDataTrans(RegionTrans): >>> dtrans = ACCDataTrans() >>> >>> schedule = psyir.children[0] - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) >>> >>> # Add a kernels construct for execution on the device - >>> kernels = schedule.children[9] + >>> kernels = schedule.children[26] >>> ktrans.apply(kernels) >>> >>> # Enclose the kernels in a data construct - >>> kernels = schedule.children[9] + >>> kernels = schedule.children[26] >>> dtrans.apply(kernels) '''