From 4c25522cb02aebbd68c2c92f5cd28dff5d28f6ae Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 18 May 2026 09:29:46 +0100 Subject: [PATCH 01/21] Fix some docstring tests --- src/psyclone/psyir/nodes/node.py | 10 ++- src/psyclone/transformations.py | 143 +++++++++++++++++-------------- 2 files changed, 86 insertions(+), 67 deletions(-) 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/transformations.py b/src/psyclone/transformations.py index fd15a432ef..081458f52e 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -90,6 +90,13 @@ OMPParallelLoopTrans) +# Files used in doctest examples +GOCEAN_SOURCE_FILE = ( + "src/psyclone/tests/test_files/gocean1p0/" + "test11_different_iterates_over_one_invoke.f90") +NEMO_SOURCE_FILE = ("examples/nemo/code/tra_adv.F90") + + def check_intergrid(node): ''' Utility function to check that the supplied node does not have @@ -356,17 +363,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,17 +453,18 @@ class LFRicColourTrans(ColourTrans): '''Split an LFRic loop over cells into colours so that it can be parallelised. For example: + >>> import os + >>> from psyclone.psyir.nodes import Loop >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory - >>> import transformations - >>> import os - >>> import pytest + >>> from psyclone.transformations import ( + ... LFRicColourTrans, LFRicOMPParallelLoopTrans) >>> >>> 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) + ... "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 @@ -464,12 +473,12 @@ class LFRicColourTrans(ColourTrans): >>> 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]) + >>> for child in schedule.walk(Loop, stop_type=Loop): + ... otrans.apply(child.loop_body[0]) >>> >>> # Uncomment the following line to see a text view of the schedule >>> # print(schedule.view()) @@ -1251,17 +1260,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 +1348,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()) ''' @@ -1674,8 +1687,9 @@ class ACCEnterDataTrans(Transformation): >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) >>> psy = PSyFactory(api).create(invokeInfo) >>> - >>> 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() @@ -1823,18 +1837,20 @@ class ACCRoutineTrans(Transformation, MarkRoutineForGPUMixin, For example: >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory + >>> from psyclone.psyGen import PSyFactory, CodedKern >>> api = "gocean" >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) >>> psy = PSyFactory(api).create(invokeInfo) >>> >>> 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 = psy.invokes.get('invoke_0').schedule.walk(CodedKern)[0] + >>> # Bring the kernel into the same module + >>> itrans.apply(kern) >>> # Transform the kernel >>> rtrans.apply(kern) @@ -1941,25 +1957,24 @@ 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 + >>> psyir = FortranReader().psyir_from_file(NEMO_SOURCE_FILE) >>> >>> from psyclone.transformations import ACCDataTrans >>> from psyclone.psyir.transformations import ACCKernelsTrans >>> ktrans = ACCKernelsTrans() >>> 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] - >>> ktrans.apply(kernels) - >>> - >>> # Enclose the kernels in a data construct - >>> kernels = schedule.children[9] - >>> dtrans.apply(kernels) + # FIXME + # >>> schedule = psyir.children[0] + # >>> + # >>> # Add a kernels construct for execution on the device + # >>> kernels = schedule.children[9] + # >>> ktrans.apply(kernels) + # >>> + # >>> # Enclose the kernels in a data construct + # >>> kernels = schedule.children[9] + # >>> dtrans.apply(kernels) ''' excluded_node_types = (CodeBlock, Return, PSyDataNode) From 53f4e7a5c76b6b7bf32a6a1761de15e0b358b22d Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 22 May 2026 11:03:23 +0100 Subject: [PATCH 02/21] Fix doctests in src/psyclone/psyir --- src/psyclone/psyir/symbols/symbol.py | 6 ++-- .../transformations/acc_kernels_trans.py | 9 +++-- .../psyir/transformations/acc_loop_trans.py | 6 ++-- .../transformations/arrayaccess2loop_trans.py | 6 ++-- .../transformations/datanode_to_temp_trans.py | 5 ++- .../transformations/debug_checksum_trans.py | 15 ++++---- .../hoist_local_arrays_trans.py | 10 +++--- .../hoist_loop_bound_expr_trans.py | 10 +++--- .../intrinsics/maxval2loop_trans.py | 6 ++-- .../intrinsics/minval2loop_trans.py | 6 ++-- .../intrinsics/product2loop_trans.py | 6 ++-- .../intrinsics/sum2loop_trans.py | 6 ++-- .../psyir/transformations/loop_swap_trans.py | 12 +++---- .../psyir/transformations/move_trans.py | 26 +++++++------- .../omp_declare_target_trans.py | 36 +++++++++---------- .../psyir/transformations/omp_loop_trans.py | 3 +- .../omp_minimise_sync_trans.py | 12 ++++--- .../omp_parallel_loop_trans.py | 20 +++++------ .../transformations/omp_parallel_trans.py | 14 ++++---- .../transformations/omp_taskloop_trans.py | 18 +++++----- .../transformations/omp_taskwait_trans.py | 15 ++++---- .../psyir/transformations/profile_trans.py | 5 ++- .../psyir/transformations/psy_data_trans.py | 36 ++++++++++--------- .../replace_reference_by_literal_trans.py | 8 +++-- src/psyclone/transformations.py | 19 +++++----- 25 files changed, 166 insertions(+), 149 deletions(-) diff --git a/src/psyclone/psyir/symbols/symbol.py b/src/psyclone/psyir/symbols/symbol.py index a80c74dae8..44733bef7e 100644 --- a/src/psyclone/psyir/symbols/symbol.py +++ b/src/psyclone/psyir/symbols/symbol.py @@ -482,8 +482,10 @@ 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..f16b2fa931 100644 --- a/src/psyclone/psyir/transformations/acc_kernels_trans.py +++ b/src/psyclone/psyir/transformations/acc_kernels_trans.py @@ -58,6 +58,7 @@ TransformationError) from psyclone.utils import transformation_documentation_wrapper +NEMO_SOURCE_FILE = ("examples/nemo/code/tra_adv.F90") @transformation_documentation_wrapper class ACCKernelsTrans(RegionTrans): @@ -67,16 +68,14 @@ 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 + >>> psyir = FortranReader().psyir_from_file(NEMO_SOURCE_FILE) >>> >>> 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..6159c0ca84 100644 --- a/src/psyclone/psyir/transformations/acc_loop_trans.py +++ b/src/psyclone/psyir/transformations/acc_loop_trans.py @@ -44,6 +44,10 @@ ParallelLoopTrans) from psyclone.psyir.nodes import (ACCLoopDirective, PSyDataNode) +GOCEAN_SOURCE_FILE = ( + "src/psyclone/tests/test_files/gocean1p0/" + "test11_different_iterates_over_one_invoke.f90") + class ACCLoopTrans(ParallelLoopTrans): ''' @@ -66,8 +70,6 @@ class ACCLoopTrans(ParallelLoopTrans): >>> 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[:]: 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..5031d08712 100644 --- a/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py +++ b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py @@ -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..53fc906379 100644 --- a/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py +++ b/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py @@ -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..a74c71dbc7 100644 --- a/src/psyclone/psyir/transformations/loop_swap_trans.py +++ b/src/psyclone/psyir/transformations/loop_swap_trans.py @@ -65,17 +65,15 @@ class LoopSwapTrans(LoopTrans): >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("shallow_alg.f90") + >>> filename = "examples/gocean/eg1/shallow_alg.f90" + >>> ast, invokeInfo = parse(filename, api="gocean") >>> 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 + >>> from psyclone.psyir.transformations import LoopSwapTrans + >>> from psyclone.psyir.nodes import Loop >>> swap = LoopSwapTrans() - >>> swap.apply(schedule.children[0]) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + >>> 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..41ea8d8d73 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py @@ -53,17 +53,15 @@ class OMPParallelLoopTrans(OMPLoopTrans): >>> 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()) + + # # FIXME: File does not exist + # >>> ast, invokeInfo = parse("lfric.F90") + # >>> psy = PSyFactory("lfric").create(invokeInfo) + # >>> schedule = psy.invokes.get('invoke_v3_kernel_type').schedule + # >>> + # >>> from psyclone.transformations import OMPParallelLoopTrans + # >>> trans = OMPParallelLoopTrans() + # >>> trans.apply(schedule.children[0]) ''' def __str__(self): diff --git a/src/psyclone/psyir/transformations/omp_parallel_trans.py b/src/psyclone/psyir/transformations/omp_parallel_trans.py index c6fe3843d7..31d1b47869 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_trans.py @@ -56,6 +56,11 @@ from psyclone.utils import transformation_documentation_wrapper +GOCEAN_SOURCE_FILE = ( + "src/psyclone/tests/test_files/gocean1p0/" + "test11_different_iterates_over_one_invoke.f90") + + @transformation_documentation_wrapper class OMPParallelTrans(ParallelRegionTrans): ''' @@ -73,22 +78,19 @@ class OMPParallelTrans(ParallelRegionTrans): >>> from psyclone.psyGen import TransInfo >>> t = TransInfo() >>> ltrans = t.get_trans_name('GOceanOMPLoopTrans') - >>> rtrans = t.get_trans_name('OMPParallelTrans') + >>> from psyclone.psyir.transformations import OMPParallelTrans + >>> rtrans = OMPParallelTrans() >>> >>> 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 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..fea10a8de8 100644 --- a/src/psyclone/psyir/transformations/omp_taskloop_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskloop_trans.py @@ -29,6 +29,10 @@ OMPTaskloopDirective) from psyclone.utils import transformation_documentation_wrapper +GOCEAN_SOURCE_FILE = ( + "src/psyclone/tests/test_files/gocean1p0/" + "test11_different_iterates_over_one_invoke.f90") + @transformation_documentation_wrapper class OMPTaskloopTrans(ParallelLoopTrans): @@ -47,7 +51,7 @@ class OMPTaskloopTrans(ParallelLoopTrans): For example: - >>> from pysclone.parse.algorithm import parse + >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory >>> api = "gocean" >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) @@ -55,7 +59,7 @@ class OMPTaskloopTrans(ParallelLoopTrans): >>> >>> 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() @@ -63,15 +67,13 @@ class OMPTaskloopTrans(ParallelLoopTrans): >>> 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,9 +81,9 @@ 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): diff --git a/src/psyclone/psyir/transformations/omp_taskwait_trans.py b/src/psyclone/psyir/transformations/omp_taskwait_trans.py index 13d2858693..415562f426 100644 --- a/src/psyclone/psyir/transformations/omp_taskwait_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskwait_trans.py @@ -66,16 +66,17 @@ class OMPTaskwaitTrans(Transformation): For example: - >>> from pysclone.parse.algorithm import parse + >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory >>> api = "gocean" - >>> filename = "nemolite2d_alg.f90" + >>> filename = ("src/psyclone/tests/test_files/gocean1p0/" + ... "nemolite2d_alg_mod.f90") >>> ast, invokeInfo = parse(filename, api=api, invoke_name="invoke") >>> psy = PSyFactory(api).create(invokeInfo) >>> >>> 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() @@ -83,22 +84,22 @@ class OMPTaskwaitTrans(Transformation): >>> 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..210c26a2f9 100644 --- a/src/psyclone/psyir/transformations/profile_trans.py +++ b/src/psyclone/psyir/transformations/profile_trans.py @@ -53,18 +53,17 @@ class ProfileTrans(PSyDataTrans): >>> from psyclone.psyGen import PSyFactory, GenerationError >>> from psyclone.psyir.transformations import ProfileTrans >>> api = "gocean" - >>> filename = "nemolite2d_alg.f90" + >>> filename = ("src/psyclone/tests/test_files/gocean1p0/" + ... "nemolite2d_alg_mod.f90") >>> ast, invokeInfo = parse(filename, api=api, invoke_name="invoke") >>> psy = PSyFactory(api).create(invokeInfo) >>> >>> 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..01e20d9e77 100644 --- a/src/psyclone/psyir/transformations/psy_data_trans.py +++ b/src/psyclone/psyir/transformations/psy_data_trans.py @@ -55,23 +55,25 @@ 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) - >>> - >>> 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 - >>> # print(schedule.view()) - >>> # Or to use custom region name: - >>> data_trans.apply(schedule.children, - ... {"region_name": ("module","region")}) + + # FIXME: What file? + # >>> ast, invoke_info = parse(SOURCE_FILE, api=api) + # >>> psy = PSyFactory(api).create(invoke_info) + # >>> + # >>> 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 + # >>> # print(schedule.view()) + # >>> # Or to use custom region name: + # >>> data_trans.apply(schedule.children, + # ... {"region_name": ("module","region")}) :param node_class: The Node class of which an instance will be inserted \ into the tree (defaults to PSyDataNode). 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..d976bed067 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,11 @@ 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.transformations import ( - ReplaceReferenceByLiteralTrans) + ... ReplaceReferenceByLiteralTrans) >>> source = """program test ... use mymod ... type(my_type):: t1, t2, t3, t4 @@ -115,7 +116,7 @@ class ReplaceReferenceByLiteralTrans(Transformation): integer :: ic2 integer :: ic3 real, dimension(10) :: a - + invariant = 1 do i = 1, 10, 1 t1%a = 13 @@ -124,11 +125,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/transformations.py b/src/psyclone/transformations.py index 081458f52e..c58681ca02 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -1965,16 +1965,15 @@ class ACCDataTrans(RegionTrans): >>> ktrans = ACCKernelsTrans() >>> dtrans = ACCDataTrans() >>> - # FIXME - # >>> schedule = psyir.children[0] - # >>> - # >>> # Add a kernels construct for execution on the device - # >>> kernels = schedule.children[9] - # >>> ktrans.apply(kernels) - # >>> - # >>> # Enclose the kernels in a data construct - # >>> kernels = schedule.children[9] - # >>> dtrans.apply(kernels) + >>> schedule = psyir.children[0] + >>> + >>> # Add a kernels construct for execution on the device + >>> kernels = schedule.children[26] + >>> ktrans.apply(kernels) + >>> + >>> # Enclose the kernels in a data construct + >>> kernels = schedule.children[26] + >>> dtrans.apply(kernels) ''' excluded_node_types = (CodeBlock, Return, PSyDataNode) From 67027b66c49e843eb9ec9f3f0f94caed7454b296 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 22 May 2026 14:05:29 +0100 Subject: [PATCH 03/21] Fix remaining doctest (some by deleting or commenting them) --- src/psyclone/alg_gen.py | 22 ++++-------- src/psyclone/core/symbolic_maths.py | 5 ++- .../gocean_const_loop_bounds_trans.py | 9 +++-- .../transformations/gocean_extract_trans.py | 8 ++--- .../transformations/gocean_loop_fuse_trans.py | 11 +++--- .../transformations/gocean_opencl_trans.py | 7 ++-- .../transformations/lfric_extract_trans.py | 23 ++++++------ .../transformations/lfric_loop_fuse_trans.py | 25 +++++++------ src/psyclone/generator.py | 9 ----- src/psyclone/gocean1p0.py | 8 +++-- src/psyclone/parse/algorithm.py | 11 ------ src/psyclone/psyGen.py | 36 ++----------------- src/psyclone/tests/exceptions_test.py | 13 ++++--- 13 files changed, 71 insertions(+), 116 deletions(-) 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..11f7f692dd 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 @@ -74,18 +74,17 @@ class GOConstLoopBoundsTrans(Transformation): >>> 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) + >>> filename = "src/psyclone/tests/test_files/gocean1p0/single_invoke.f90" + >>> _, info = parse(filename, api=TEST_API) >>> psy = PSyFactory(TEST_API).create(info) >>> invoke = psy.invokes.get('invoke_0_compute_cu') >>> schedule = invoke.schedule >>> - >>> 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..b80e43a14b 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py @@ -46,15 +46,15 @@ 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) + >>> FILENAME = "examples/gocean/eg1/shallow_alg.f90" + >>> ast, invoke_info = parse(FILENAME, api=API) >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) >>> schedule = psy.invokes.get('invoke_0').schedule >>> @@ -63,7 +63,7 @@ class GOceanExtractTrans(ExtractTrans): >>> >>> # 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..02d64fa0e6 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py @@ -51,15 +51,16 @@ class GOceanLoopFuseTrans(LoopFuseTrans): >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("shallow_alg.f90") + >>> filename = "examples/gocean/eg1/shallow_alg.f90" + >>> ast, invokeInfo = parse(filename, "gocean") >>> psy = PSyFactory("gocean").create(invokeInfo) >>> schedule = psy.invokes.get('invoke_0').schedule - >>> print(schedule.view()) >>> - >>> 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..be862de4a9 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -67,13 +67,14 @@ class GOOpenCLTrans(Transformation): >>> from psyclone.parse.algorithm import parse >>> from psyclone.psyGen import PSyFactory >>> API = "gocean" - >>> FILENAME = "shallow_alg.f90" # examples/gocean/eg1 + >>> FILENAME = "examples/gocean/eg1/shallow_alg.f90" >>> ast, invoke_info = parse(FILENAME, api=API) >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) >>> schedule = psy.invokes.get('invoke_0').schedule >>> 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..8a7c13c8b4 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py @@ -53,17 +53,18 @@ class LFRicExtractTrans(ExtractTrans): >>> 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()) + + # FIXME: Which solver_mod.x90 file? + # >>> FILENAME = "solver_mod.x90" + # >>> 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.domain.lfric.transformations import LFRicExtractTrans + # >>> etrans = LFRicExtractTrans() + # >>> + # >>> # Apply LFRicExtractTrans transformation to selected Nodes + # >>> etrans.apply(schedule.children[0:3]) ''' 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/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 From 3319d268de12afe2541fbd437aed68761e10d52b Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 22 May 2026 14:14:29 +0100 Subject: [PATCH 04/21] Test docstring doctest in the CI pytest --- .github/workflows/python-package.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bc4c65ab2a..f67136b853 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -127,12 +127,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 From 43a9b5dfc52bdc8156aa35ffe839ff217bdc2f5c Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 22 May 2026 18:36:12 +0100 Subject: [PATCH 05/21] Fix some doctests by creating a get_psylayer_schedule test utility and brining the shallow example inside the test directory --- .../transformations/gocean_loop_fuse_trans.py | 9 +- .../transformations/gocean_opencl_trans.py | 11 +- src/psyclone/psyir/symbols/symbol.py | 1 + .../psyir/transformations/acc_loop_trans.py | 18 +- .../hoist_loop_bound_expr_trans.py | 2 +- .../psyir/transformations/loop_swap_trans.py | 19 +- .../transformations/omp_parallel_trans.py | 17 +- .../transformations/omp_taskloop_trans.py | 15 +- .../transformations/omp_taskwait_trans.py | 12 +- .../psyir/transformations/profile_trans.py | 15 +- .../reference2arrayrange_trans.py | 16 +- .../transformations/scalarisation_trans.py | 6 +- .../tests/psyir/nodes/profile_node_test.py | 4 +- .../gocean1p0/shallow/compute_cu_mod.f90 | 120 +++++++++ .../gocean1p0/shallow/compute_cv_mod.f90 | 116 ++++++++ .../gocean1p0/shallow/compute_h_mod.f90 | 117 ++++++++ .../gocean1p0/shallow/compute_pnew_mod.f90 | 136 ++++++++++ .../gocean1p0/shallow/compute_unew_mod.f90 | 129 +++++++++ .../gocean1p0/shallow/compute_vnew_mod.f90 | 141 ++++++++++ .../gocean1p0/shallow/compute_z_mod.f90 | 101 +++++++ .../gocean1p0/shallow/infrastructure_mod.f90 | 45 ++++ .../gocean1p0/shallow/shallow_alg.f90 | 251 ++++++++++++++++++ .../gocean1p0/shallow/time_smooth_mod.f90 | 102 +++++++ src/psyclone/tests/utilities.py | 31 +++ src/psyclone/transformations.py | 55 +--- 25 files changed, 1351 insertions(+), 138 deletions(-) create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_cu_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_cv_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_h_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_pnew_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_unew_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_vnew_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_z_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/infrastructure_mod.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/shallow_alg.f90 create mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/time_smooth_mod.f90 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 02d64fa0e6..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,12 +49,9 @@ 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 - >>> filename = "examples/gocean/eg1/shallow_alg.f90" - >>> ast, invokeInfo = parse(filename, "gocean") - >>> psy = PSyFactory("gocean").create(invokeInfo) - >>> 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 GOceanLoopFuseTrans >>> ftrans = GOceanLoopFuseTrans() diff --git a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py index be862de4a9..c75de99577 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -64,13 +64,10 @@ 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 = "examples/gocean/eg1/shallow_alg.f90" - >>> 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") + >>> >>> ocl_trans = GOOpenCLTrans() # FIXME: Needs GOMoveIterationBoundariesInsideKernelTrans diff --git a/src/psyclone/psyir/symbols/symbol.py b/src/psyclone/psyir/symbols/symbol.py index 44733bef7e..a0b865e190 100644 --- a/src/psyclone/psyir/symbols/symbol.py +++ b/src/psyclone/psyir/symbols/symbol.py @@ -484,6 +484,7 @@ def is_array_access(self, index_variable=None, access_info=None): at least one of the indices. For example: .. code-block:: fortran + do i=1, n a(j) = 2 diff --git a/src/psyclone/psyir/transformations/acc_loop_trans.py b/src/psyclone/psyir/transformations/acc_loop_trans.py index 6159c0ca84..5c981514aa 100644 --- a/src/psyclone/psyir/transformations/acc_loop_trans.py +++ b/src/psyclone/psyir/transformations/acc_loop_trans.py @@ -44,10 +44,6 @@ ParallelLoopTrans) from psyclone.psyir.nodes import (ACCLoopDirective, PSyDataNode) -GOCEAN_SOURCE_FILE = ( - "src/psyclone/tests/test_files/gocean1p0/" - "test11_different_iterates_over_one_invoke.f90") - class ACCLoopTrans(ParallelLoopTrans): ''' @@ -56,28 +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 - >>> >>> # 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/hoist_loop_bound_expr_trans.py b/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py index 53fc906379..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" diff --git a/src/psyclone/psyir/transformations/loop_swap_trans.py b/src/psyclone/psyir/transformations/loop_swap_trans.py index a74c71dbc7..8f098e51d5 100644 --- a/src/psyclone/psyir/transformations/loop_swap_trans.py +++ b/src/psyclone/psyir/transformations/loop_swap_trans.py @@ -63,17 +63,14 @@ class LoopSwapTrans(LoopTrans): This transform is used as follows: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> filename = "examples/gocean/eg1/shallow_alg.f90" - >>> ast, invokeInfo = parse(filename, api="gocean") - >>> psy = PSyFactory("gocean").create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> - >>> from psyclone.psyir.transformations import LoopSwapTrans - >>> from psyclone.psyir.nodes import Loop - >>> swap = LoopSwapTrans() - >>> swap.apply(schedule.walk(Loop)[0]) + >>> 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/omp_parallel_trans.py b/src/psyclone/psyir/transformations/omp_parallel_trans.py index 31d1b47869..cb76927a26 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_trans.py @@ -56,24 +56,15 @@ from psyclone.utils import transformation_documentation_wrapper -GOCEAN_SOURCE_FILE = ( - "src/psyclone/tests/test_files/gocean1p0/" - "test11_different_iterates_over_one_invoke.f90") - - @transformation_documentation_wrapper 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() @@ -81,8 +72,6 @@ class OMPParallelTrans(ParallelRegionTrans): >>> from psyclone.psyir.transformations import OMPParallelTrans >>> rtrans = OMPParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> >>> # Apply the OpenMP Loop transformation to *every* loop >>> # in the schedule >>> for child in schedule.children: diff --git a/src/psyclone/psyir/transformations/omp_taskloop_trans.py b/src/psyclone/psyir/transformations/omp_taskloop_trans.py index fea10a8de8..e206a87e7e 100644 --- a/src/psyclone/psyir/transformations/omp_taskloop_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskloop_trans.py @@ -29,10 +29,6 @@ OMPTaskloopDirective) from psyclone.utils import transformation_documentation_wrapper -GOCEAN_SOURCE_FILE = ( - "src/psyclone/tests/test_files/gocean1p0/" - "test11_different_iterates_over_one_invoke.f90") - @transformation_documentation_wrapper class OMPTaskloopTrans(ParallelLoopTrans): @@ -51,11 +47,9 @@ class OMPTaskloopTrans(ParallelLoopTrans): 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 = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPSingleTrans >>> from psyclone.psyir.transformations import OMPParallelTrans @@ -66,8 +60,6 @@ class OMPTaskloopTrans(ParallelLoopTrans): >>> tasklooptrans = OMPTaskloopTrans() >>> taskwaittrans = OMPTaskwaitTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> >>> # Apply the OpenMP Taskloop transformation to *every* loop >>> # in the schedule. >>> # This ignores loop dependencies. These can be handled @@ -86,6 +78,7 @@ class OMPTaskloopTrans(ParallelLoopTrans): # >>> 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 415562f426..4440ce70fc 100644 --- a/src/psyclone/psyir/transformations/omp_taskwait_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskwait_trans.py @@ -66,13 +66,9 @@ class OMPTaskwaitTrans(Transformation): For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> filename = ("src/psyclone/tests/test_files/gocean1p0/" - ... "nemolite2d_alg_mod.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 @@ -83,8 +79,6 @@ class OMPTaskwaitTrans(Transformation): >>> tasklooptrans = OMPTaskloopTrans() >>> taskwaittrans = OMPTaskwaitTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> >>> # Apply the OpenMP Taskloop transformation to *every* loop >>> # in the schedule. >>> # This ignores loop dependencies. These are handled by the diff --git a/src/psyclone/psyir/transformations/profile_trans.py b/src/psyclone/psyir/transformations/profile_trans.py index 210c26a2f9..addc72ecaf 100644 --- a/src/psyclone/psyir/transformations/profile_trans.py +++ b/src/psyclone/psyir/transformations/profile_trans.py @@ -48,20 +48,13 @@ 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 = ("src/psyclone/tests/test_files/gocean1p0/" - ... "nemolite2d_alg_mod.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 - >>> >>> # Enclose all children within a single profile region >>> p_trans.apply(schedule.children) 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/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/psyir/nodes/profile_node_test.py b/src/psyclone/tests/psyir/nodes/profile_node_test.py index 55e8c0288e..4b20cc1888 100644 --- a/src/psyclone/tests/psyir/nodes/profile_node_test.py +++ b/src/psyclone/tests/psyir/nodes/profile_node_test.py @@ -116,7 +116,7 @@ def test_lower_to_lang_level_single_node(): a single ProfileNode. ''' - Profiler.set_options([Profiler.INVOKES], api="nemo") + Profiler.set_options([Profiler.INVOKES], api="gocean") symbol_table = SymbolTable() arg1 = symbol_table.new_symbol( symbol_type=DataSymbol, datatype=ScalarType.real_type()) @@ -154,7 +154,7 @@ def test_lower_named_profile_node(): a ProfileNode has pre-set names for the module and region. ''' - Profiler.set_options([Profiler.INVOKES], api="nemo") + Profiler.set_options([Profiler.INVOKES], api="gocean") symbol_table = SymbolTable() arg1 = symbol_table.new_symbol( symbol_type=DataSymbol, datatype=ScalarType.real_type()) diff --git a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_cu_mod.f90 b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_cu_mod.f90 new file mode 100644 index 0000000000..a9e5cca198 --- /dev/null +++ b/src/psyclone/tests/test_files/gocean1p0/shallow/compute_cu_mod.f90 @@ -0,0 +1,120 @@ +!> \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..208bc9bad8 100644 --- a/src/psyclone/tests/utilities.py +++ b/src/psyclone/tests/utilities.py @@ -600,6 +600,37 @@ def get_invoke(algfile: str, return psy, invoke +def get_psylayer_schedule( + algfile: str, + api: str, + invoke_name: str = "invoke_0", + 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) + return psy.invokes.get(invoke_name).schedule + + # ============================================================================= def get_ast(api: str, filename: str) -> BeginSource: '''Returns the fparser1 parse tree for a filename that is stored in the diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index c58681ca02..0cd41210da 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -91,9 +91,6 @@ # Files used in doctest examples -GOCEAN_SOURCE_FILE = ( - "src/psyclone/tests/test_files/gocean1p0/" - "test11_different_iterates_over_one_invoke.f90") NEMO_SOURCE_FILE = ("examples/nemo/code/tra_adv.F90") @@ -695,21 +692,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) @@ -826,29 +817,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 @@ -1681,11 +1664,9 @@ 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, ACCParallelTrans) @@ -1694,10 +1675,6 @@ class ACCEnterDataTrans(Transformation): >>> 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) @@ -1707,9 +1684,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): @@ -1836,19 +1810,18 @@ 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, CodedKern - >>> 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() >>> - >>> kern = psy.invokes.get('invoke_0').schedule.walk(CodedKern)[0] + >>> kern = schedule.walk(CodedKern)[0] >>> # Bring the kernel into the same module >>> itrans.apply(kern) >>> # Transform the kernel From d618ba6ab2e5496a77507cd8fd7c0bda75028c5e Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 22 May 2026 19:31:02 +0100 Subject: [PATCH 06/21] Fix several issues with the docs doctest check --- doc/developer_guide/dependency.rst | 6 ++-- doc/developer_guide/module_manager.rst | 28 ----------------- doc/developer_guide/psyir_symbols.rst | 2 +- doc/developer_guide/transformations.rst | 5 +--- doc/user_guide/psyir.rst | 4 +-- doc/user_guide/transformations.rst | 5 ++++ .../gocean_const_loop_bounds_trans.py | 12 ++------ .../transformations/gocean_extract_trans.py | 11 ++----- .../transformations/gocean_opencl_trans.py | 4 +++ .../transformations/lfric_extract_trans.py | 17 ----------- src/psyclone/psyir/nodes/structure_member.py | 1 + .../hoist_local_arrays_trans.py | 2 +- .../omp_parallel_loop_trans.py | 15 +--------- .../psyir/transformations/psy_data_trans.py | 30 ++++++++----------- .../replace_reference_by_literal_trans.py | 1 + .../tests/psyir/symbols/symbol_test.py | 6 ++-- src/psyclone/tests/utilities.py | 7 +++-- src/psyclone/transformations.py | 19 +++--------- 18 files changed, 51 insertions(+), 124 deletions(-) diff --git a/doc/developer_guide/dependency.rst b/doc/developer_guide/dependency.rst index 782a3a18e1..4cf87394ca 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..e23f42b2d8 100644 --- a/doc/developer_guide/transformations.rst +++ b/doc/developer_guide/transformations.rst @@ -35,11 +35,8 @@ .. 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") + 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/doc/user_guide/transformations.rst b/doc/user_guide/transformations.rst index 84bf744efa..fcbba1f429 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -36,6 +36,11 @@ .. I. Kavcic, Met Office .. J. Dendy, Met Office +.. testsetup:: + + # Define NEMO_SOURCE_FILE to point to an existing nemo file. + NEMO_SOURCE_FILE = "../examples/nemo/code/tra_adv.F90" + .. _transformations: Transformations 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 11f7f692dd..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,15 +70,9 @@ 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" - >>> filename = "src/psyclone/tests/test_files/gocean1p0/single_invoke.f90" - >>> _, info = parse(filename, 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.domain.gocean.transformations import \ GOConstLoopBoundsTrans diff --git a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py index b80e43a14b..47b97d0fb0 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py @@ -49,14 +49,9 @@ class GOceanExtractTrans(ExtractTrans): ''' 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 = "examples/gocean/eg1/shallow_alg.f90" - >>> 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 GOceanExtractTrans >>> etrans = GOceanExtractTrans() diff --git a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py index c75de99577..a9ad2b7406 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -68,6 +68,10 @@ class GOOpenCLTrans(Transformation): >>> 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() # FIXME: Needs GOMoveIterationBoundariesInsideKernelTrans diff --git a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py index 8a7c13c8b4..ff79af0941 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py @@ -49,23 +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" - - # FIXME: Which solver_mod.x90 file? - # >>> FILENAME = "solver_mod.x90" - # >>> 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.domain.lfric.transformations import LFRicExtractTrans - # >>> etrans = LFRicExtractTrans() - # >>> - # >>> # Apply LFRicExtractTrans transformation to selected Nodes - # >>> etrans.apply(schedule.children[0:3]) - ''' def __init__(self): 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/transformations/hoist_local_arrays_trans.py b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py index 5031d08712..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" diff --git a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py index 41ea8d8d73..38133ad505 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py @@ -49,21 +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 - - # # FIXME: File does not exist - # >>> ast, invokeInfo = parse("lfric.F90") - # >>> psy = PSyFactory("lfric").create(invokeInfo) - # >>> schedule = psy.invokes.get('invoke_v3_kernel_type').schedule - # >>> - # >>> from psyclone.transformations import OMPParallelLoopTrans - # >>> trans = OMPParallelLoopTrans() - # >>> trans.apply(schedule.children[0]) - ''' + def __str__(self): return "Add an 'OpenMP PARALLEL DO' directive" diff --git a/src/psyclone/psyir/transformations/psy_data_trans.py b/src/psyclone/psyir/transformations/psy_data_trans.py index 01e20d9e77..71d8757b70 100644 --- a/src/psyclone/psyir/transformations/psy_data_trans.py +++ b/src/psyclone/psyir/transformations/psy_data_trans.py @@ -57,23 +57,19 @@ class PSyDataTrans(RegionTrans): >>> api = "gocean" # FIXME: What file? - # >>> ast, invoke_info = parse(SOURCE_FILE, api=api) - # >>> psy = PSyFactory(api).create(invoke_info) - # >>> - # >>> 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 - # >>> # print(schedule.view()) - # >>> # Or to use custom region name: - # >>> data_trans.apply(schedule.children, - # ... {"region_name": ("module","region")}) + >>> 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() + >>> + >>> # 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 + >>> # print(schedule.view()) + >>> # Or to use custom region name: + >>> data_trans.apply(schedule.children, + ... {"region_name": ("module","region")}) :param node_class: The Node class of which an instance will be inserted \ into the tree (defaults to PSyDataNode). 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 d976bed067..fc5c051796 100644 --- a/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py +++ b/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py @@ -71,6 +71,7 @@ class ReplaceReferenceByLiteralTrans(Transformation): >>> 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) >>> source = """program test diff --git a/src/psyclone/tests/psyir/symbols/symbol_test.py b/src/psyclone/tests/psyir/symbols/symbol_test.py index 6e42a08e12..745722975a 100644 --- a/src/psyclone/tests/psyir/symbols/symbol_test.py +++ b/src/psyclone/tests/psyir/symbols/symbol_test.py @@ -459,9 +459,9 @@ def test_symbol_array_handling(): assert ("index variable 'i' specified, but no access information given" in str(err.value)) # Supply some access information. - svinfo = AccessSequence(Signature("i")) - svinfo.add_access(AccessType.READ, Reference(asym)) - assert not asym.is_array_access("i", svinfo) + access_seq = AccessSequence(Signature("i")) + access_seq.add_access(AccessType.READ, Reference(asym)) + assert not asym.is_array_access("i", access_seq) @pytest.mark.parametrize("table", [None, SymbolTable()]) diff --git a/src/psyclone/tests/utilities.py b/src/psyclone/tests/utilities.py index 208bc9bad8..9bb95d25cc 100644 --- a/src/psyclone/tests/utilities.py +++ b/src/psyclone/tests/utilities.py @@ -603,7 +603,7 @@ def get_invoke(algfile: str, def get_psylayer_schedule( algfile: str, api: str, - invoke_name: str = "invoke_0", + invoke_name: str = "", dist_mem: bool = False ) -> Node: ''' @@ -628,7 +628,10 @@ def get_psylayer_schedule( _, info = parse(filepath, api=api) psy = PSyFactory(api, distributed_memory=dist_mem).create(info) - return psy.invokes.get(invoke_name).schedule + if invoke_name: + return psy.invokes.get(invoke_name).schedule + else: + return psy.invokes.invoke_list[0].schedule # ============================================================================= diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 0cd41210da..0bd992c4be 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -450,22 +450,14 @@ class LFRicColourTrans(ColourTrans): '''Split an LFRic loop over cells into colours so that it can be parallelised. For example: - >>> import os + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "4.6_multikernel_invokes.f90" + >>> schedule = get_psylayer_schedule(filename, api="lfric") + >>> >>> from psyclone.psyir.nodes import Loop - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory >>> from psyclone.transformations import ( ... LFRicColourTrans, LFRicOMPParallelLoopTrans) >>> - >>> 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 - >>> >>> ctrans = LFRicColourTrans() >>> otrans = LFRicOMPParallelLoopTrans() >>> @@ -476,9 +468,6 @@ class LFRicColourTrans(ColourTrans): >>> # Then apply OpenMP to each of the colour loops >>> for child in schedule.walk(Loop, stop_type=Loop): ... otrans.apply(child.loop_body[0]) - >>> - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) Colouring in the LFRic API is subject to the following rules: From 8c6267dcaa84025ace655555651f400ece70d2c1 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Tue, 26 May 2026 10:07:26 +0100 Subject: [PATCH 07/21] Fix shpinx doctest and uncomment the CI test --- .github/workflows/python-package.yml | 3 +- doc/developer_guide/dependency.rst | 2 +- doc/developer_guide/transformations.rst | 5 - doc/user_guide/transformations.rst | 5 - .../transformations/acc_kernels_trans.py | 5 +- .../tests/nemo/test_files/code/tra_adv.F90 | 273 +++++++++++++ .../tests/nemo/test_files/code/traldf_iso.F90 | 385 ++++++++++++++++++ src/psyclone/tests/utilities.py | 24 +- src/psyclone/transformations.py | 8 +- 9 files changed, 678 insertions(+), 32 deletions(-) create mode 100755 src/psyclone/tests/nemo/test_files/code/tra_adv.F90 create mode 100644 src/psyclone/tests/nemo/test_files/code/traldf_iso.F90 diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index f67136b853..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 diff --git a/doc/developer_guide/dependency.rst b/doc/developer_guide/dependency.rst index 4cf87394ca..b9e80e75b5 100644 --- a/doc/developer_guide/dependency.rst +++ b/doc/developer_guide/dependency.rst @@ -581,7 +581,7 @@ can be parallelised: .. testoutput:: :hide: - 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'. + 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/transformations.rst b/doc/developer_guide/transformations.rst index e23f42b2d8..766e150c34 100644 --- a/doc/developer_guide/transformations.rst +++ b/doc/developer_guide/transformations.rst @@ -33,11 +33,6 @@ .. ----------------------------------------------------------------------------- .. Written by R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab -.. testsetup:: - - # 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/transformations.rst b/doc/user_guide/transformations.rst index fcbba1f429..84bf744efa 100644 --- a/doc/user_guide/transformations.rst +++ b/doc/user_guide/transformations.rst @@ -36,11 +36,6 @@ .. I. Kavcic, Met Office .. J. Dendy, Met Office -.. testsetup:: - - # Define NEMO_SOURCE_FILE to point to an existing nemo file. - NEMO_SOURCE_FILE = "../examples/nemo/code/tra_adv.F90" - .. _transformations: Transformations diff --git a/src/psyclone/psyir/transformations/acc_kernels_trans.py b/src/psyclone/psyir/transformations/acc_kernels_trans.py index f16b2fa931..bf191d9cdd 100644 --- a/src/psyclone/psyir/transformations/acc_kernels_trans.py +++ b/src/psyclone/psyir/transformations/acc_kernels_trans.py @@ -58,7 +58,6 @@ TransformationError) from psyclone.utils import transformation_documentation_wrapper -NEMO_SOURCE_FILE = ("examples/nemo/code/tra_adv.F90") @transformation_documentation_wrapper class ACCKernelsTrans(RegionTrans): @@ -69,7 +68,9 @@ class ACCKernelsTrans(RegionTrans): For example: >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> psyir = FortranReader().psyir_from_file(NEMO_SOURCE_FILE) + >>> 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() 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= 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, @@ -634,7 +627,16 @@ def get_psylayer_schedule( 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. @@ -652,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 @@ -699,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 0bd992c4be..fd887cabd0 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -90,10 +90,6 @@ OMPParallelLoopTrans) -# Files used in doctest examples -NEMO_SOURCE_FILE = ("examples/nemo/code/tra_adv.F90") - - def check_intergrid(node): ''' Utility function to check that the supplied node does not have @@ -1920,7 +1916,9 @@ class ACCDataTrans(RegionTrans): For example: >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> psyir = FortranReader().psyir_from_file(NEMO_SOURCE_FILE) + >>> 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 From 825775443196555007f67a49132bf1ea52b76912 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 1 Jun 2026 11:57:56 +0100 Subject: [PATCH 08/21] Fix get_base_path test --- src/psyclone/tests/utilities_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/utilities_test.py b/src/psyclone/tests/utilities_test.py index 03ced7a465..856e66175f 100644 --- a/src/psyclone/tests/utilities_test.py +++ b/src/psyclone/tests/utilities_test.py @@ -376,7 +376,7 @@ def test_get_base_path() -> None: assert "tests/test_files/gocean1p0" in gocean lfric = get_base_path("lfric") assert "tests/test_files/lfric" in lfric - nemo = get_base_path("nemo") + nemo = get_base_path() assert "nemo/test_files" in nemo with pytest.raises(RuntimeError) as err: From 994a08db7b6834ac7368f4008f02332dc2946042 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 1 Jun 2026 12:30:13 +0100 Subject: [PATCH 09/21] Remove doctest FIXMEs --- doc/developer_guide/module_manager.rst | 27 ++++++++ .../transformations/gocean_loop_fuse_trans.py | 3 +- .../transformations/gocean_opencl_trans.py | 11 +++- .../transformations/lfric_loop_fuse_trans.py | 29 +++------ .../psyir/transformations/move_trans.py | 22 ++----- .../transformations/omp_taskloop_trans.py | 4 +- .../transformations/omp_taskwait_trans.py | 4 +- .../psyir/transformations/psy_data_trans.py | 1 - src/psyclone/transformations.py | 63 +++++-------------- 9 files changed, 69 insertions(+), 95 deletions(-) diff --git a/doc/developer_guide/module_manager.rst b/doc/developer_guide/module_manager.rst index cde0308af2..6f4e13b4f3 100644 --- a/doc/developer_guide/module_manager.rst +++ b/doc/developer_guide/module_manager.rst @@ -115,6 +115,33 @@ 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``: + +.. code-block :: python + + 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)) + + +.. The snippet above fails a `.. testcode ::` block, but if it didn't we +.. excpect the follwoing `.. 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/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py index 35ce13bcfb..cf0fbedf4b 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py @@ -56,7 +56,8 @@ class GOceanLoopFuseTrans(LoopFuseTrans): >>> from psyclone.domain.gocean.transformations import GOceanLoopFuseTrans >>> ftrans = GOceanLoopFuseTrans() - # FIXME + # Currently produces an error with "Cannot fuse loops that are over " + # "different grid-point types: go_cu and go_cv" # >>> ftrans.apply(schedule[0], schedule[1]) ''' diff --git a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py index a9ad2b7406..b4b4e13c75 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -71,11 +71,16 @@ class GOOpenCLTrans(Transformation): >>> from psyclone.domain.gocean.transformations import ( ... GOMoveIterationBoundariesInsideKernelTrans, ... GOOpenCLTrans) + >>> from psyclone.domain.common.transformations import ( + ... KernelModuleInlineTrans) >>> move_trans = GOMoveIterationBoundariesInsideKernelTrans() + >>> mod_inline_trans = KernelModuleInlineTrans() >>> ocl_trans = GOOpenCLTrans() - - # FIXME: Needs GOMoveIterationBoundariesInsideKernelTrans - # >>> ocl_trans.apply(schedule) + >>> for kern in schedule.kernels(): + ... # Put kernels in same container and iterate the whole space + ... mod_inline_trans.apply(kern) + ... move_trans.apply(kern) + >>> ocl_trans.apply(schedule) ''' # Specify which OpenCL command queue to use for management operations like 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 c13cb384d3..1e6b0f986c 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py @@ -48,31 +48,20 @@ @transformation_documentation_wrapper class LFRicLoopFuseTrans(LoopFuseTrans): - ''' LFRic API specialisation of the - :py:class:`base class ` in order to fuse two LFRic - loops after performing validity checks. For example: + ''' LFRic API specialisation of the :py:class:`base class ` in order + to fuse two LFRic loops after performing validity checks. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> - >>> API = "lfric" + .. code-block :: python - # 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 + from psyclone.domain.lfric.transformations import LFRicLoopFuseTrans + ftrans = LFRicLoopFuseTrans() + ftrans.apply(schedule[0], schedule[1]) The optional argument `same_space` can be set as - # >>> ftrans.apply(schedule[0], schedule[1], {"same_space": True}) + .. code-block :: python + + ftrans.apply(schedule[0], schedule[1], {"same_space": True}) when applying the transformation. diff --git a/src/psyclone/psyir/transformations/move_trans.py b/src/psyclone/psyir/transformations/move_trans.py index 76189ee2ae..77c2dd4306 100644 --- a/src/psyclone/psyir/transformations/move_trans.py +++ b/src/psyclone/psyir/transformations/move_trans.py @@ -55,22 +55,12 @@ class MoveTrans(Transformation): '''Provides a transformation to move a node in the tree. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - - # 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()) + .. code-block :: python + + from psyclone.transformations import MoveTrans + trans=MoveTrans() + trans.apply(schedule.children[0], schedule.children[2], + options = {"position":"after") 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_taskloop_trans.py b/src/psyclone/psyir/transformations/omp_taskloop_trans.py index e206a87e7e..ab8901e620 100644 --- a/src/psyclone/psyir/transformations/omp_taskloop_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskloop_trans.py @@ -73,9 +73,7 @@ class OMPTaskloopTrans(ParallelLoopTrans): >>> # PARALLEL region >>> paralleltrans.apply(schedule.children) >>> # Ensure loop dependencies are satisfied - - # FIXME - # >>> taskwaittrans.apply(schedule.children) + >>> taskwaittrans.apply(schedule.children[0]) ''' diff --git a/src/psyclone/psyir/transformations/omp_taskwait_trans.py b/src/psyclone/psyir/transformations/omp_taskwait_trans.py index 4440ce70fc..d159094d2f 100644 --- a/src/psyclone/psyir/transformations/omp_taskwait_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskwait_trans.py @@ -91,9 +91,7 @@ class OMPTaskwaitTrans(Transformation): >>> # Enclose all of these loops within a single OpenMP >>> # PARALLEL region >>> paralleltrans.apply(schedule.children) - - # FIXME - # >>> taskwaittrans.apply(schedule.children) + >>> taskwaittrans.apply(schedule.children[0]) ''' def __str__(self): diff --git a/src/psyclone/psyir/transformations/psy_data_trans.py b/src/psyclone/psyir/transformations/psy_data_trans.py index 71d8757b70..feb298e4fb 100644 --- a/src/psyclone/psyir/transformations/psy_data_trans.py +++ b/src/psyclone/psyir/transformations/psy_data_trans.py @@ -56,7 +56,6 @@ class PSyDataTrans(RegionTrans): >>> from psyclone.psyGen import PSyFactory >>> api = "gocean" - # 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") diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index fd887cabd0..85ceadc276 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -356,18 +356,14 @@ class ColourTrans(LoopTrans): Apply a colouring transformation to a loop (in order to permit a subsequent parallelisation over colours). For example: - # 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()) + .. code-block:: python + + invoke = ... + schedule = invoke.schedule + ctrans = ColourTrans() + # Colour all of the loops + for child in schedule.children: + ctrans.apply(child) ''' def __str__(self): @@ -1225,23 +1221,6 @@ class LFRicAsyncHaloExchangeTrans(Transformation): '''Splits a synchronous halo exchange into a halo exchange start and halo exchange end. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "lfric" - >>> - # 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()) - ''' def __str__(self): @@ -1313,25 +1292,13 @@ class LFRicKernelConstTrans(Transformation, CalleeTransformationMixin): number of quadrature points are fixed in the kernel rather than being passed in by argument. - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "lfric" - >>> - # 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()) + .. code-block:: python + + 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] ''' From 38eba7bff6b8b5a6f6024b55e6c4b3df177d6371 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 1 Jun 2026 12:35:16 +0100 Subject: [PATCH 10/21] Fix flake8 issue --- .../domain/lfric/transformations/lfric_loop_fuse_trans.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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 1e6b0f986c..26b473b721 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_loop_fuse_trans.py @@ -48,8 +48,9 @@ @transformation_documentation_wrapper class LFRicLoopFuseTrans(LoopFuseTrans): - ''' LFRic API specialisation of the :py:class:`base class ` in order - to fuse two LFRic loops after performing validity checks. For example: + ''' LFRic API specialisation of the :py:class:`base class ` + in order to fuse two LFRic loops after performing validity checks. For + example: .. code-block :: python From abcb4761f81bcd8f62f1e5538fa2c04afa8b3b9c Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 1 Jun 2026 12:57:38 +0100 Subject: [PATCH 11/21] Use -e when testing if doctest passes --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a52b51ad7a..8a19682aec 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -68,7 +68,7 @@ jobs: python-version: '3.14' - run: sudo apt-get install -y graphviz doxygen - run: python -m pip install --upgrade pip - - run: pip install .[doc] + - run: pip install -e .[doc] # Now we can check for warnings and broken links - run: cd doc; make doctest - run: cd doc; make html SPHINXOPTS="-W --keep-going" From ddfba4b5e9a74c5bcac98dad05d8c5c1ade0b837 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 1 Jun 2026 13:05:52 +0100 Subject: [PATCH 12/21] Add tests to the package --- .github/workflows/python-package.yml | 2 +- pyproject.toml | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 8a19682aec..a52b51ad7a 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -68,7 +68,7 @@ jobs: python-version: '3.14' - run: sudo apt-get install -y graphviz doxygen - run: python -m pip install --upgrade pip - - run: pip install -e .[doc] + - 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" diff --git a/pyproject.toml b/pyproject.toml index 7d2ee07ede..7a2c2d28b9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,7 +114,6 @@ script-files = [ [tool.setuptools.packages.find] where = ["src"] -exclude = ["psyclone.tests", "psyclone.tests.*"] [tool.setuptools.dynamic] version = { attr = "psyclone.version.__VERSION__" } From f8c9485a3f9e6c19a00411e2220c36ec6c6a6237 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 1 Jun 2026 14:26:24 +0100 Subject: [PATCH 13/21] Add test to build-documentation action for make doctest to work properly --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index a52b51ad7a..1a96a25ceb 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -68,7 +68,7 @@ jobs: python-version: '3.14' - run: sudo apt-get install -y graphviz doxygen - run: python -m pip install --upgrade pip - - run: pip install .[doc] + - run: pip install .[test,doc] # Now we can check for warnings and broken links - run: cd doc; make doctest - run: cd doc; make html SPHINXOPTS="-W --keep-going" From ad88895daf9a9aae24e4b2935726b7fae5b16af3 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 1 Jun 2026 14:27:48 +0100 Subject: [PATCH 14/21] Try to rever pyproject change --- .github/workflows/python-package.yml | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 1a96a25ceb..0beaec79f3 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -68,7 +68,7 @@ jobs: python-version: '3.14' - run: sudo apt-get install -y graphviz doxygen - run: python -m pip install --upgrade pip - - run: pip install .[test,doc] + - run: pip install -e .[test,doc] # Now we can check for warnings and broken links - run: cd doc; make doctest - run: cd doc; make html SPHINXOPTS="-W --keep-going" diff --git a/pyproject.toml b/pyproject.toml index 7a2c2d28b9..7d2ee07ede 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -114,6 +114,7 @@ script-files = [ [tool.setuptools.packages.find] where = ["src"] +exclude = ["psyclone.tests", "psyclone.tests.*"] [tool.setuptools.dynamic] version = { attr = "psyclone.version.__VERSION__" } From d7805b6ebd5d8f2eca04ac87741599b7bf6468a0 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Tue, 9 Jun 2026 11:14:45 +0100 Subject: [PATCH 15/21] Fix pydocs and improve test coverage --- src/psyclone/tests/dependency_test.py | 28 ++++++++----------- .../transformations/loop_swap_trans_test.py | 8 ++---- src/psyclone/utils.py | 24 ++++++++++------ 3 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/psyclone/tests/dependency_test.py b/src/psyclone/tests/dependency_test.py index 51d66d03f9..724d2d77be 100644 --- a/src/psyclone/tests/dependency_test.py +++ b/src/psyclone/tests/dependency_test.py @@ -45,7 +45,7 @@ from psyclone.parse.algorithm import parse from psyclone.psyGen import PSyFactory from psyclone.psyir.nodes import Assignment, IfBlock, Loop -from psyclone.tests.utilities import get_invoke, get_ast +from psyclone.tests.utilities import get_psylayer_schedule, get_invoke, get_ast # Constants API = "nemo" @@ -208,9 +208,9 @@ def test_nemo_array_range(fortran_reader): def test_goloop(): ''' Check the handling of PSyKAL (e.g. GOLoops) do loops. ''' - _, invoke = get_invoke("single_invoke_two_kernels_scalars.f90", - "gocean", name="invoke_0") - do_loop = invoke.schedule.children[0] + schedule = get_psylayer_schedule( + "single_invoke_two_kernels_scalars.f90", api="gocean") + do_loop = schedule.children[0] assert isinstance(do_loop, Loop) # The third argument is GO_GRID_X_MAX_INDEX, which is scalar @@ -234,13 +234,7 @@ def test_lfric(): from the kernel metadata, not the actual kernel usage. ''' - _, info = parse(os.path.join(os.path.dirname(os.path.abspath(__file__)), - "test_files", "lfric", - "1_single_invoke.f90"), - api="lfric") - psy = PSyFactory("lfric", distributed_memory=False).create(info) - invoke = psy.invokes.get('invoke_0_testkern_type') - schedule = invoke.schedule + schedule = get_psylayer_schedule("1_single_invoke.f90", api="lfric") var_accesses = schedule.reference_accesses() assert str(var_accesses) == ( "a: READ, cell: WRITE+READ, f1_data: INC, f2_data: READ, field_type: " @@ -309,9 +303,9 @@ def test_lfric_ref_element(): '''Test handling of variables if an LFRic's RefElement is used. ''' - psy, invoke_info = get_invoke("23.4_ref_elem_all_faces_invoke.f90", - "lfric", idx=0) - var_info = str(invoke_info.schedule.reference_accesses()) + schedule = get_psylayer_schedule("23.4_ref_elem_all_faces_invoke.f90", + "lfric") + var_info = str(schedule.reference_accesses()) assert "normals_to_faces: READ," in var_info assert "out_normals_to_faces: READ," in var_info assert "nfaces_re: READ," in var_info @@ -322,15 +316,15 @@ def test_lfric_operator(): handled correctly. ''' - psy, invoke_info = get_invoke("6.1_eval_invoke.f90", "lfric", idx=0) - var_info = str(invoke_info.schedule.reference_accesses()) + schedule = get_psylayer_schedule("6.1_eval_invoke.f90", "lfric") + var_info = str(schedule.reference_accesses()) assert "f0_data: INC," in var_info assert "cmap_data: READ," in var_info assert "basis_w0_on_w0: READ," in var_info assert "diff_basis_w1_on_w0: READ," in var_info -def test_lfric_cma(fortran_writer): +def test_lfric_cma(): '''Test that parameters related to CMA operators are handled correctly in the variable usage analysis. diff --git a/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py b/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py index 957d4d462c..8b533bd6a4 100644 --- a/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/loop_swap_trans_test.py @@ -45,7 +45,7 @@ from psyclone.psyir.symbols import ContainerSymbol from psyclone.psyir.transformations import LoopSwapTrans, TransformationError from psyclone.tests.gocean_build import GOceanBuild -from psyclone.tests.utilities import get_invoke +from psyclone.tests.utilities import get_invoke, get_psylayer_schedule def test_loop_swap_apply(tmpdir): @@ -278,10 +278,8 @@ def test_loop_swap_schedule_is_kept(): contain annotations). ''' - psy, _ = get_invoke("test27_loop_swap.f90", "gocean", idx=0, - dist_mem=False) - invoke = psy.invokes.get("invoke_loop1") - schedule = invoke.schedule + schedule = get_psylayer_schedule( + "test27_loop_swap.f90", api="gocean", invoke_name="invoke_loop1") schedule_str = str(schedule) # First make sure to throw an early error if the source file diff --git a/src/psyclone/utils.py b/src/psyclone/utils.py index a822d56fa2..a7153d6854 100644 --- a/src/psyclone/utils.py +++ b/src/psyclone/utils.py @@ -107,26 +107,34 @@ def transformation_documentation_wrapper(*args, transformation_docstring_wrapper that is handled by python. The length 0 set would happen with a case like this: - >>> @transformation_documentation_wrapper(inherit=True) - ... class mytrans(Transformation): - ... pass + .. code-block:: python + + @transformation_documentation_wrapper(inherit=True) + class mytrans(Transformation): + pass as this code is equivalent to: - >>> mytrans = transformation_documentation_wrapper(inherit=True)(mytrans) + .. code-block:: python + + mytrans = transformation_documentation_wrapper(inherit=True)(mytrans) For this case *args is empty, as only the inherit argument is provided to the transformation_documentation_wrapper call and is through kwargs. Without arguments to the decorator: - >>> @transformation_documentation_wrapper - ... class mytrans(Transformation): - ... pass + .. code-block:: python + + @transformation_documentation_wrapper + class mytrans(Transformation): + pass the resultant code is the same as writing: - >>> mytrans = transformation_documentation_wrapper(mytrans) + .. code-block:: python + + mytrans = transformation_documentation_wrapper(mytrans) In this case *args contains the wrapped class in *args, which needs to be passed manually to the sub-function inside the wrapper. From e6240eaed21ae3c6974a7464e8ec1b781d8ab745 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Tue, 9 Jun 2026 13:44:07 +0100 Subject: [PATCH 16/21] Remove duplicated test/examples files and instead update the test utilities to be able to find them --- doc/developer_guide/interface_example.py | 7 - doc/user_guide/gocean1p0.rst | 4 +- .../transformations/gocean_extract_trans.py | 4 +- .../transformations/gocean_loop_fuse_trans.py | 4 +- .../transformations/gocean_opencl_trans.py | 7 +- .../transformations/acc_kernels_trans.py | 4 +- .../psyir/transformations/loop_swap_trans.py | 4 +- .../tests/nemo/test_files/code/tra_adv.F90 | 273 ------------- .../tests/nemo/test_files/code/traldf_iso.F90 | 385 ------------------ .../gocean1p0/shallow/compute_cu_mod.f90 | 120 ------ .../gocean1p0/shallow/compute_cv_mod.f90 | 116 ------ .../gocean1p0/shallow/compute_h_mod.f90 | 117 ------ .../gocean1p0/shallow/compute_pnew_mod.f90 | 136 ------- .../gocean1p0/shallow/compute_unew_mod.f90 | 129 ------ .../gocean1p0/shallow/compute_vnew_mod.f90 | 141 ------- .../gocean1p0/shallow/compute_z_mod.f90 | 101 ----- .../gocean1p0/shallow/infrastructure_mod.f90 | 45 -- .../gocean1p0/shallow/shallow_alg.f90 | 251 ------------ .../gocean1p0/shallow/time_smooth_mod.f90 | 102 ----- src/psyclone/tests/utilities.py | 38 +- src/psyclone/transformations.py | 4 +- 21 files changed, 39 insertions(+), 1953 deletions(-) delete mode 100755 src/psyclone/tests/nemo/test_files/code/tra_adv.F90 delete mode 100644 src/psyclone/tests/nemo/test_files/code/traldf_iso.F90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_cu_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_cv_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_h_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_pnew_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_unew_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_vnew_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/compute_z_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/infrastructure_mod.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/shallow_alg.f90 delete mode 100644 src/psyclone/tests/test_files/gocean1p0/shallow/time_smooth_mod.f90 diff --git a/doc/developer_guide/interface_example.py b/doc/developer_guide/interface_example.py index 8bd1a215a3..1d6262b457 100644 --- a/doc/developer_guide/interface_example.py +++ b/doc/developer_guide/interface_example.py @@ -35,11 +35,4 @@ def some_function(filename, kernel_path, node=None): it can be raised by different errors. :raises GenerationError: same exception, raised by a different error. - For example: - - >>> from psyclone.generator import generate - >>> API="gocean" - >>> alg, psy = generate(SOURCE_FILE, api=API) - >>> alg, psy = generate(SOURCE_FILE, api=API, kernel_paths=[KERNEL_PATH]) - ''' diff --git a/doc/user_guide/gocean1p0.rst b/doc/user_guide/gocean1p0.rst index a0ff8859d0..ef3010522f 100644 --- a/doc/user_guide/gocean1p0.rst +++ b/doc/user_guide/gocean1p0.rst @@ -196,8 +196,8 @@ Example +++++++ PSyclone is distributed with a full example of the use of the -GOcean Library. See ``/examples/gocean/shallow_alg.f90``. In what -follows we will walk through a slightly cut-down example for a +GOcean Library. See ``/examples/gocean/eg1/shallow_alg.f90``. +In what follows we will walk through a slightly cut-down example for a different program. The following code illustrates the use of dl_esm_inf for constructing an diff --git a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py index 47b97d0fb0..5f909ddb36 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py @@ -50,8 +50,8 @@ class GOceanExtractTrans(ExtractTrans): to extract code into a stand-alone program. For example: >>> from psyclone.tests.utilities import get_psylayer_schedule - >>> filename = "shallow/shallow_alg.f90" - >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, "gocean-examples") >>> >>> from psyclone.domain.gocean.transformations import GOceanExtractTrans >>> etrans = GOceanExtractTrans() 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 cf0fbedf4b..663bbd0896 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py @@ -50,8 +50,8 @@ class GOceanLoopFuseTrans(LoopFuseTrans): that the loops are over the same grid-point type). For example: >>> from psyclone.tests.utilities import get_psylayer_schedule - >>> filename = "shallow/shallow_alg.f90" - >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, "gocean-examples") >>> >>> from psyclone.domain.gocean.transformations import GOceanLoopFuseTrans >>> ftrans = GOceanLoopFuseTrans() diff --git a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py index b4b4e13c75..fa9b7c669a 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -65,8 +65,8 @@ class GOOpenCLTrans(Transformation): each of the kernels referenced by the Invoke. For example: >>> from psyclone.tests.utilities import get_psylayer_schedule - >>> filename = "shallow/shallow_alg.f90" - >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, "gocean-examples") >>> >>> from psyclone.domain.gocean.transformations import ( ... GOMoveIterationBoundariesInsideKernelTrans, @@ -80,7 +80,8 @@ class GOOpenCLTrans(Transformation): ... # Put kernels in same container and iterate the whole space ... mod_inline_trans.apply(kern) ... move_trans.apply(kern) - >>> ocl_trans.apply(schedule) + >>> # Commented to prevent generating doctest output .cl files + >>> # ocl_trans.apply(schedule) ''' # Specify which OpenCL command queue to use for management operations like diff --git a/src/psyclone/psyir/transformations/acc_kernels_trans.py b/src/psyclone/psyir/transformations/acc_kernels_trans.py index bf191d9cdd..beb7e49397 100644 --- a/src/psyclone/psyir/transformations/acc_kernels_trans.py +++ b/src/psyclone/psyir/transformations/acc_kernels_trans.py @@ -68,8 +68,8 @@ class ACCKernelsTrans(RegionTrans): For example: >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.tests.utilities import get_file_path - >>> filename = get_file_path("code/tra_adv.F90") + >>> from psyclone.tests.utilities import get_examples_path + >>> filename = get_examples_path("nemo/code/tra_adv.F90") >>> psyir = FortranReader().psyir_from_file(filename) >>> >>> from psyclone.psyir.transformations import ACCKernelsTrans diff --git a/src/psyclone/psyir/transformations/loop_swap_trans.py b/src/psyclone/psyir/transformations/loop_swap_trans.py index 8f098e51d5..dd784eb2a8 100644 --- a/src/psyclone/psyir/transformations/loop_swap_trans.py +++ b/src/psyclone/psyir/transformations/loop_swap_trans.py @@ -64,8 +64,8 @@ class LoopSwapTrans(LoopTrans): This transform is used as follows: >>> from psyclone.tests.utilities import get_psylayer_schedule - >>> filename = "shallow/shallow_alg.f90" - >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean-examples") >>> >>> from psyclone.psyir.transformations import LoopSwapTrans >>> from psyclone.psyir.nodes import Loop diff --git a/src/psyclone/tests/nemo/test_files/code/tra_adv.F90 b/src/psyclone/tests/nemo/test_files/code/tra_adv.F90 deleted file mode 100755 index a70fa2b7ae..0000000000 --- a/src/psyclone/tests/nemo/test_files/code/tra_adv.F90 +++ /dev/null @@ -1,273 +0,0 @@ - !!===================================================================================== - !! *** 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 deleted file mode 100644 index 210fcf843b..0000000000 --- a/src/psyclone/tests/nemo/test_files/code/traldf_iso.F90 +++ /dev/null @@ -1,385 +0,0 @@ -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 deleted file mode 100644 index 098c5f289d..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_cv_mod.f90 +++ /dev/null @@ -1,116 +0,0 @@ -!> \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 deleted file mode 100644 index d75ef38c8e..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_h_mod.f90 +++ /dev/null @@ -1,117 +0,0 @@ -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 deleted file mode 100644 index d31c0acba9..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_pnew_mod.f90 +++ /dev/null @@ -1,136 +0,0 @@ -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 deleted file mode 100644 index eac0a7933b..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_unew_mod.f90 +++ /dev/null @@ -1,129 +0,0 @@ -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 deleted file mode 100644 index 61ae02e667..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_vnew_mod.f90 +++ /dev/null @@ -1,141 +0,0 @@ -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 deleted file mode 100644 index db640947fd..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/compute_z_mod.f90 +++ /dev/null @@ -1,101 +0,0 @@ -!> \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 deleted file mode 100644 index 69352025da..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/infrastructure_mod.f90 +++ /dev/null @@ -1,45 +0,0 @@ - -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 deleted file mode 100644 index 3eef8d8326..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/shallow_alg.f90 +++ /dev/null @@ -1,251 +0,0 @@ -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 deleted file mode 100644 index 208e29a7cd..0000000000 --- a/src/psyclone/tests/test_files/gocean1p0/shallow/time_smooth_mod.f90 +++ /dev/null @@ -1,102 +0,0 @@ -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 f5885454dd..b2ab0c5d90 100644 --- a/src/psyclone/tests/utilities.py +++ b/src/psyclone/tests/utilities.py @@ -507,7 +507,8 @@ def get_base_path(api: str = "") -> str: 'tests/test_files' directory, i.e. the directory in which all Fortran test files are stored. - :param api: name of the API. + :param api: name of the API. Also accepts 'gocean-examples' to mean + a gocean api file under the examples directory. :returns: the base path for the API. @@ -515,11 +516,12 @@ def get_base_path(api: str = "") -> str: ''' # Define the mapping of supported APIs to Fortran directories - # 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/test_files", - "gocean": "gocean1p0"} + api_2_path = { + "lfric": "lfric", + "": "../nemo/test_files", + "gocean": "gocean1p0", + "gocean-examples": "../../../../examples/gocean" + } try: dir_name = api_2_path[api] except KeyError as err: @@ -604,7 +606,9 @@ def get_psylayer_schedule( algorithm file. :param algfile: name of the Algorithm source file (Fortran). - :param api: which PSyclone API this Algorithm uses. + :param api: which PSyclone API this Algorithm uses. Also accepts + 'gocean-examples' to mean a gocean api file under the examples + directory. :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. @@ -612,12 +616,13 @@ def get_psylayer_schedule( :returns: the associated psylayer schedule. ''' - + filepath = os.path.join(get_base_path(api), algfile) config = Config.get() + if api == "gocean-examples": + # gocean-examples in this utility is a shorthand for using the gocean + # API but from its examples directory as a basepath + api = "gocean" 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) @@ -627,14 +632,17 @@ def get_psylayer_schedule( return psy.invokes.invoke_list[0].schedule -def get_file_path(relative_path: str): +def get_examples_path(relative_path: str): ''' - :param relative_path: given a relative file path. + :param relative_path: given a relative examples file path. - :returns: its absolute file path inside the test directory. + :returns: its absolute file path. ''' - return os.path.join(get_base_path(), relative_path) + return os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "../../../examples", + relative_path) def get_ast(api: str, filename: str) -> BeginSource: diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 9f3cd93636..538606e1ba 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -1673,8 +1673,8 @@ class ACCDataTrans(RegionTrans): For example: >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.tests.utilities import get_file_path - >>> filename = get_file_path("code/tra_adv.F90") + >>> from psyclone.tests.utilities import get_examples_path + >>> filename = get_examples_path("nemo/code/tra_adv.F90") >>> psyir = FortranReader().psyir_from_file(filename) >>> >>> from psyclone.transformations import ACCDataTrans From 82b621e320d102d16175bb13c100866817d721b7 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 12 Jun 2026 16:05:17 +0100 Subject: [PATCH 17/21] Bring back ModuleManager testcode block --- doc/developer_guide/module_manager.rst | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/developer_guide/module_manager.rst b/doc/developer_guide/module_manager.rst index 6f4e13b4f3..5b17116e03 100644 --- a/doc/developer_guide/module_manager.rst +++ b/doc/developer_guide/module_manager.rst @@ -118,10 +118,11 @@ 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``: -.. code-block :: python +.. testcode :: mod_manager = ModuleManager.get() - # Add the path to the PSyclone LFRic example codes: + # Add the path to the LFRic module directories: + mod_manager.add_search_path("../external/lfric_infrastructure/") mod_manager.add_search_path("../src/psyclone/tests/test_files/" "lfric") @@ -135,8 +136,8 @@ which prints the filenames of all modules used in ``tl_testkern_mod``: print("Module:", module_name, os.path.basename(mod_info.filename)) -.. The snippet above fails a `.. testcode ::` block, but if it didn't we -.. excpect the follwoing `.. testoutput::` +.. testoutput:: + Module: argument_mod argument_mod.f90 Module: constants_mod constants_mod.f90 Module: fs_continuity_mod fs_continuity_mod.f90 From 359e0d0c7b41273d6ff3af14b49bb58564a47e3e Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 19 Jun 2026 10:33:55 +0100 Subject: [PATCH 18/21] Remove duplicated code in test utils and simplify Profile set_options --- .../transformations/gocean_extract_trans.py | 4 +-- .../transformations/lfric_extract_trans.py | 2 +- src/psyclone/generator.py | 2 +- src/psyclone/profiler.py | 8 ++--- src/psyclone/tests/generator_test.py | 2 +- .../tests/psyir/nodes/profile_node_test.py | 6 ++-- .../psyir/transformations/profile_test.py | 24 +++++++-------- src/psyclone/tests/utilities.py | 30 ++++++++----------- src/psyclone/tests/utilities_test.py | 5 ++-- 9 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py index 5f909ddb36..c4cde8ea0d 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py @@ -46,7 +46,7 @@ class GOceanExtractTrans(ExtractTrans): - ''' GOcean1.0 API application of ExtractTrans transformation + ''' GOcean API application of ExtractTrans transformation to extract code into a stand-alone program. For example: >>> from psyclone.tests.utilities import get_psylayer_schedule @@ -63,7 +63,7 @@ class GOceanExtractTrans(ExtractTrans): # ------------------------------------------------------------------------ def validate(self, node_list, options=None): - ''' Perform GOcean1.0 API specific validation checks before applying + ''' Perform GOcean API specific validation checks before applying the transformation. :param node_list: the list of Node(s) we are checking. diff --git a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py index ff79af0941..3a54fdf965 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py @@ -47,7 +47,7 @@ class LFRicExtractTrans(ExtractTrans): ''' LFRic API application of ExtractTrans transformation - to extract code into a stand-alone program. For example: + to extract code into a stand-alone program. ''' diff --git a/src/psyclone/generator.py b/src/psyclone/generator.py index 6487904b75..b7f765c490 100644 --- a/src/psyclone/generator.py +++ b/src/psyclone/generator.py @@ -679,7 +679,7 @@ def main(arguments): # Record any profiling options. if args.profile: try: - Profiler.set_options(args.profile, api) + Profiler.set_options(args.profile, args.psykal_dsl is not None) except ValueError as err: print(f"Invalid profiling option: {err}", file=sys.stderr) sys.exit(1) diff --git a/src/psyclone/profiler.py b/src/psyclone/profiler.py index 9e6cf8ca1b..f84a493ed4 100644 --- a/src/psyclone/profiler.py +++ b/src/psyclone/profiler.py @@ -64,13 +64,13 @@ class Profiler(): # ------------------------------------------------------------------------- @staticmethod - def set_options(options, api=""): + def set_options(options, is_psykal: bool = False): '''Sets the option the user required. :param options: options selected by the user, or None to disable all automatic profiling. :type options: Optional[list[str]] - :param str api: the PSyclone API that is in use. + :param is_psykal: whether this is profiling a psykal application. :raises ValueError: if an invalid option is supplied. @@ -86,8 +86,8 @@ def set_options(options, api=""): f"must be one of {allowed_options} but " f"found '{option}' at {index}") - if not api and (Profiler.KERNELS in options or - Profiler.INVOKES in options): + if not is_psykal and (Profiler.KERNELS in options or + Profiler.INVOKES in options): raise ValueError( f"The profiling '{Profiler.KERNELS}' and '{Profiler.INVOKES}'" f" options are only available when using PSyKAl DSLs.") diff --git a/src/psyclone/tests/generator_test.py b/src/psyclone/tests/generator_test.py index 4e993b4620..1df6674d07 100644 --- a/src/psyclone/tests/generator_test.py +++ b/src/psyclone/tests/generator_test.py @@ -464,7 +464,7 @@ def test_profile_gocean(): information if this has been specified. ''' - Profiler.set_options(['invokes'], "gocean") + Profiler.set_options(['invokes'], is_psykal=True) _, psy = generate( os.path.join(BASE_PATH, "gocean1p0", "single_invoke.f90"), api="gocean") diff --git a/src/psyclone/tests/psyir/nodes/profile_node_test.py b/src/psyclone/tests/psyir/nodes/profile_node_test.py index 4b20cc1888..634b258103 100644 --- a/src/psyclone/tests/psyir/nodes/profile_node_test.py +++ b/src/psyclone/tests/psyir/nodes/profile_node_test.py @@ -116,7 +116,7 @@ def test_lower_to_lang_level_single_node(): a single ProfileNode. ''' - Profiler.set_options([Profiler.INVOKES], api="gocean") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) symbol_table = SymbolTable() arg1 = symbol_table.new_symbol( symbol_type=DataSymbol, datatype=ScalarType.real_type()) @@ -154,7 +154,7 @@ def test_lower_named_profile_node(): a ProfileNode has pre-set names for the module and region. ''' - Profiler.set_options([Profiler.INVOKES], api="gocean") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) symbol_table = SymbolTable() arg1 = symbol_table.new_symbol( symbol_type=DataSymbol, datatype=ScalarType.real_type()) @@ -180,7 +180,7 @@ def test_lower_to_lang_level_multi_node(): ''' # We use a GOcean example containing multiple kernel calls - Profiler.set_options([Profiler.KERNELS], api="gocean") + Profiler.set_options([Profiler.KERNELS], is_psykal=True) _, invoke = get_invoke("single_invoke_two_kernels.f90", "gocean", idx=0) sched = invoke.schedule diff --git a/src/psyclone/tests/psyir/transformations/profile_test.py b/src/psyclone/tests/psyir/transformations/profile_test.py index 4a1c643650..f3eae07ebd 100644 --- a/src/psyclone/tests/psyir/transformations/profile_test.py +++ b/src/psyclone/tests/psyir/transformations/profile_test.py @@ -74,7 +74,7 @@ def teardown_function(): def test_profile_basic(): '''Check basic functionality: node names, schedule view. ''' - Profiler.set_options([Profiler.INVOKES], api="gocean") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", "gocean", idx=0, dist_mem=False) Profiler.add_profile_nodes(invoke.schedule, Loop) @@ -132,7 +132,7 @@ def test_profile_errors2(): def test_profile_invokes_gocean1p0(fortran_writer): '''Check that an invoke is instrumented correctly ''' - Profiler.set_options([Profiler.INVOKES], "gocean") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", "gocean", idx=0) Profiler.add_profile_nodes(invoke.schedule, Loop) @@ -199,7 +199,7 @@ def test_unique_region_names(fortran_writer): '''Test that unique region names are created even when the kernel names are identical.''' - Profiler.set_options([Profiler.KERNELS], "gocean") + Profiler.set_options([Profiler.KERNELS], is_psykal=True) _, invoke = get_invoke("single_invoke_two_identical_kernels.f90", "gocean", 0, dist_mem=False) Profiler.add_profile_nodes(invoke.schedule, Loop) @@ -242,7 +242,7 @@ def test_unique_region_names(fortran_writer): def test_profile_kernels_gocean1p0(fortran_writer): '''Check that all kernels are instrumented correctly ''' - Profiler.set_options([Profiler.KERNELS], "gocean") + Profiler.set_options([Profiler.KERNELS], is_psykal=True) _, invoke = get_invoke("single_invoke_two_kernels.f90", "gocean", idx=0, dist_mem=False) Profiler.add_profile_nodes(invoke.schedule, Loop) @@ -309,7 +309,7 @@ def test_profile_named_gocean1p0(fortran_writer): def test_profile_invokes_lfric(fortran_writer): '''Check that an LFRic invoke is instrumented correctly ''' - Profiler.set_options([Profiler.INVOKES], "lfric") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) # First test for a single invoke with a single kernel work as expected: _, invoke = get_invoke("1_single_invoke.f90", "lfric", idx=0) @@ -410,7 +410,7 @@ def test_profile_kernels_lfric(fortran_writer): '''Check that all kernels are instrumented correctly in a LFRic invoke. ''' - Profiler.set_options([Profiler.KERNELS], "lfric") + Profiler.set_options([Profiler.KERNELS], is_psykal=True) _, invoke = get_invoke("1_single_invoke.f90", "lfric", idx=0) Profiler.add_profile_nodes(invoke.schedule, Loop) @@ -461,7 +461,7 @@ def test_profile_fused_kernels_lfric(): invoke which has had them fused (i.e. there is more than one Kernel inside a loop). ''' - Profiler.set_options([Profiler.KERNELS], "lfric") + Profiler.set_options([Profiler.KERNELS], is_psykal=True) psy, invoke = get_invoke("1.2_multi_invoke.f90", "lfric", idx=0, dist_mem=False) @@ -492,7 +492,7 @@ def test_profile_kernels_without_loop_lfric(): impossible so we construct an artificial Schedule to test. ''' - Profiler.set_options([Profiler.KERNELS], "lfric") + Profiler.set_options([Profiler.KERNELS], is_psykal=True) _, invoke = get_invoke("1.2_multi_invoke.f90", "lfric", idx=0, dist_mem=False) @@ -512,7 +512,7 @@ def test_profile_kernels_in_directive_lfric(): ''' Check that a kernel is instrumented correctly if it is within a directive. ''' - Profiler.set_options([Profiler.KERNELS], "lfric") + Profiler.set_options([Profiler.KERNELS], is_psykal=True) psy, invoke = get_invoke("1_single_invoke_w3.f90", "lfric", idx=0, dist_mem=False) ktrans = ACCKernelsTrans() @@ -801,7 +801,7 @@ def test_auto_invoke_return_last_stmt(): # Double-check that the tree is as we expect assert isinstance(kschedule[-1], Return) - Profiler.set_options([Profiler.INVOKES], "nemo") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) Profiler.add_profile_nodes(kschedule, Loop) # The Return should be a sibling of the ProfileNode rather than a child assert isinstance(kschedule[0], ProfileNode) @@ -813,7 +813,7 @@ def test_auto_invoke_no_return(capsys): ''' Check that using the auto-invoke profiling option does not add any profiling if the invoke contains a Return anywhere other than as the last statement. ''' - Profiler.set_options([Profiler.INVOKES], "nemo") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) symbol_table = SymbolTable() arg1 = symbol_table.new_symbol( symbol_type=DataSymbol, datatype=ScalarType.real_type()) @@ -857,7 +857,7 @@ def test_auto_invoke_no_return(capsys): def test_auto_invoke_empty_schedule(capsys): ''' Check the auto-invoke profiling option rejects an empty Schedule, i.e the routine has no statements. ''' - Profiler.set_options([Profiler.INVOKES], "nemo") + Profiler.set_options([Profiler.INVOKES], is_psykal=True) symbol_table = SymbolTable() # Create Schedule with Return at the start. kschedule = KernelSchedule.create( diff --git a/src/psyclone/tests/utilities.py b/src/psyclone/tests/utilities.py index b2ab0c5d90..6fd3e75386 100644 --- a/src/psyclone/tests/utilities.py +++ b/src/psyclone/tests/utilities.py @@ -525,8 +525,8 @@ def get_base_path(api: str = "") -> str: try: dir_name = api_2_path[api] except KeyError as err: - raise RuntimeError(f"The API '{api}' is not supported. " - f"Supported types are {api_2_path.keys()}.") \ + raise ValueError(f"The API '{api}' is not supported. " + f"Supported types are {api_2_path.keys()}.") \ from err return os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files", dir_name) @@ -581,12 +581,19 @@ def get_invoke(algfile: str, raise RuntimeError("Either the index or the name of the " "requested invoke must be specified") + filepath = os.path.join(get_base_path(api), algfile) + + if api == "gocean-examples": + # gocean-examples in this utility is a shorthand for using the gocean + # API but from its examples directory as a basepath + api = "gocean" config = Config.get() config.api = api + # Ensure infrastructure module files can be discovered. config.include_paths.append(get_infrastructure_path(api)) - _, info = parse(os.path.join(get_base_path(api), algfile), api=api) + _, info = parse(filepath, api=api) psy = PSyFactory(api, distributed_memory=dist_mem).create(info) if name: invoke = psy.invokes.get(name) @@ -616,20 +623,9 @@ def get_psylayer_schedule( :returns: the associated psylayer schedule. ''' - filepath = os.path.join(get_base_path(api), algfile) - config = Config.get() - if api == "gocean-examples": - # gocean-examples in this utility is a shorthand for using the gocean - # API but from its examples directory as a basepath - api = "gocean" - config.api = api - - _, 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 + idx = 0 if invoke_name == "" else None + _, invoke = get_invoke(algfile, api, idx, invoke_name, dist_mem) + return invoke.schedule def get_examples_path(relative_path: str): diff --git a/src/psyclone/tests/utilities_test.py b/src/psyclone/tests/utilities_test.py index 856e66175f..27eb87949a 100644 --- a/src/psyclone/tests/utilities_test.py +++ b/src/psyclone/tests/utilities_test.py @@ -334,8 +334,7 @@ def test_get_invoke(): with pytest.raises(ValueError) as excinfo: get_invoke("test11_different_iterates_over_one_invoke.f90", "invalid-api", name="invalid_name") - assert "'invalid-api' is not a valid API," in str(excinfo.value) - # assert "The API 'invalid-api' is not supported" in str(excinfo.value) + assert "'invalid-api' is not supported" in str(excinfo.value) # Test that invalid parameter combinations raise an exception: with pytest.raises(RuntimeError) as excinfo: @@ -379,7 +378,7 @@ def test_get_base_path() -> None: nemo = get_base_path() assert "nemo/test_files" in nemo - with pytest.raises(RuntimeError) as err: + with pytest.raises(ValueError) as err: _ = get_base_path("INVALID") assert "The API 'INVALID' is not supported" in str(err.value) From 46a296c42ea2581f653a0fe5cb5b15e628021eef Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 19 Jun 2026 12:20:37 +0100 Subject: [PATCH 19/21] Add ompparallelloop doctest --- .../omp_parallel_loop_trans.py | 33 +++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py index 38133ad505..09970027a3 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py @@ -47,8 +47,37 @@ class OMPParallelLoopTrans(OMPLoopTrans): - ''' Adds an OpenMP PARALLEL DO directive to a loop. - + ''' Adds an OpenMP PARALLEL DO directive to a loop. For example: + + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.backend.fortran import FortranWriter + >>> psyir = FortranReader().psyir_from_source(""" + ... program do_loop + ... real, dimension(10) :: A + ... integer i + ... do i = 1, 10 + ... A(i) = i + ... end do + ... end program do_loop + ... """) + >>> from psyclone.psyir.nodes import Loop + >>> from psyclone.transformations import OMPParallelLoopTrans + >>> trans = OMPParallelLoopTrans() + >>> trans.apply(psyir.walk(Loop)[0]) + >>> print(FortranWriter()(psyir)) + program do_loop + real, dimension(10) :: a + integer :: i + + !$omp parallel do default(shared) private(i) schedule(auto) + do i = 1, 10, 1 + a(i) = i + enddo + !$omp end parallel do + + end program do_loop + + ''' def __str__(self): From 10ab93b9f74c3c9fc067315db030742341cc40ce Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 19 Jun 2026 13:25:25 +0100 Subject: [PATCH 20/21] Add OpenMP doctest and simplify psyclone exceptions testing --- .../omp_parallel_loop_trans.py | 2 +- src/psyclone/tests/exceptions_test.py | 102 +++--------------- src/psyclone/transformations.py | 2 +- 3 files changed, 17 insertions(+), 89 deletions(-) diff --git a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py index 09970027a3..1d6bee09ad 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py @@ -77,7 +77,7 @@ class OMPParallelLoopTrans(OMPLoopTrans): end program do_loop - + ''' def __str__(self): diff --git a/src/psyclone/tests/exceptions_test.py b/src/psyclone/tests/exceptions_test.py index 8985c11bf9..7b54b807ea 100644 --- a/src/psyclone/tests/exceptions_test.py +++ b/src/psyclone/tests/exceptions_test.py @@ -32,108 +32,36 @@ # POSSIBILITY OF SUCH DAMAGE. # ----------------------------------------------------------------------------- # Author: A. J. Voysey, Met Office +# Modified: S. Siso, STFC Daresbury Lab ''' Test exception classes to ensure consistent __repr__ & __str__ methods. ''' -import pkgutil -import inspect -import importlib +from psyclone import errors -from psyclone.errors import PSycloneError - -class DummyPSycloneError(PSycloneError): +class DummyPSycloneError(errors.PSycloneError): ''' Provides a dummy PSyclone specific error class as for use in this test ''' def __init__(self): - PSycloneError.__init__(self, "") + super().__init__(self, "") self.value = "Dummy PSyclone Error" -def all_sub_exceptions(expt): - ''' Recursively find the set of all the exceptions which are subclasses of - the given exception class. ''' - - new_sub_except = [add_except for sub_except in expt.__subclasses__() - for add_except in all_sub_exceptions(sub_except)] - return set(expt.__subclasses__()).union(new_sub_except) - - -def import_submodules(package, recursive=True): - """ Import all submodules of a module, recursively, including subpackages - - :param package: package (name or actual module) - :type package: str | module - - :rtype: dict[str, types.ModuleType] - """ - if isinstance(package, str): - package = importlib.import_module(package) - results = {} - for _, name, is_pkg in pkgutil.walk_packages(package.__path__): - full_name = package.__name__ + '.' + name - if "test" not in full_name: - results[full_name] = importlib.import_module(full_name) - if recursive and is_pkg: - results.update(import_submodules(full_name)) - return results - - def test_exception_repr(): ''' Test the properties of Exception classes defined by PSyclone. ''' - modules = {} - - # Recursively walk through the psyclone module, importing sub-modules. - # Store any class definitions we come across. - - modules = import_submodules("psyclone") - for mod in modules: - _ = importlib.import_module(mod) - - all_exceptions = all_sub_exceptions(Exception) - 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 - # passing/failing assertions depending on which pytest version is - # installed. - # - # To avoid this we will enforce the following conditions for exceptions - # defined by psyclone: - - # i) Exceptions will implement their own __str__ and __repr__ methods. - # ii) These will not be the same as each other for a given exception. - # iii) The string returned by the __str__ method will not be contained - # with that returned by the __repr__ method, - # - # When these conditions are met, assertion behaviour is consistent across - # all pytest versions. - - for psy_except in psy_excepts: - - # 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__] - + psy_exception_classes = [ + errors.UnresolvedDependencyError, + errors.GenerationError, + errors.FieldNotFoundError, + errors.InternalError, + errors.DocParseError, + ] + + for psy_except in psy_exception_classes: + # Ensure PSyclone exceptions inherit from PSycloneError + assert issubclass(psy_except, errors.PSycloneError) # Ensure there are __str__ & __repr__ methods implemented which are not # inherited from the parent Exception class assert psy_except.__str__ is not Exception.__str__ assert psy_except.__repr__ is not Exception.__repr__ - - # Simulate arguments to the exception constructor - arglist = list(inspect.getfullargspec(psy_except).args) - args = [None for arg in arglist if arg != 'self'] - - # Check that the _str__ & __repr__ do not return the same string, and - # that one is not contained within the other - if len(args) > 0: - assert str(psy_except(*args)) != str(repr(psy_except(*args))) - assert str(psy_except(*args)) not in repr(psy_except(*args)) - else: - assert str(psy_except()) != repr(psy_except()) - assert str(psy_except()) not in repr(psy_except()) diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 538606e1ba..a77bce1710 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -1009,7 +1009,7 @@ def apply(self, target_nodes, options=None): class LFRicAsyncHaloExchangeTrans(Transformation): '''Splits a synchronous halo exchange into a halo exchange start and - halo exchange end. For example: + halo exchange end. ''' From ce724c472153fe14cd7b7060fc4183a74dc51fd7 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 19 Jun 2026 13:50:52 +0100 Subject: [PATCH 21/21] Add missing coverage to exceptions test --- src/psyclone/tests/exceptions_test.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/psyclone/tests/exceptions_test.py b/src/psyclone/tests/exceptions_test.py index 7b54b807ea..936b4de006 100644 --- a/src/psyclone/tests/exceptions_test.py +++ b/src/psyclone/tests/exceptions_test.py @@ -42,12 +42,12 @@ class DummyPSycloneError(errors.PSycloneError): ''' Provides a dummy PSyclone specific error class as for use in this test ''' - def __init__(self): - super().__init__(self, "") - self.value = "Dummy PSyclone Error" + def __init__(self, value): + super().__init__(value) + self.value = f"Dummy PSyclone Error: {value}" -def test_exception_repr(): +def test_exception_str_and_repr(): ''' Test the properties of Exception classes defined by PSyclone. ''' psy_exception_classes = [ @@ -65,3 +65,8 @@ def test_exception_repr(): # inherited from the parent Exception class assert psy_except.__str__ is not Exception.__str__ assert psy_except.__repr__ is not Exception.__repr__ + + # Now test that an example error class behaves as expected + testerror = DummyPSycloneError("my msg") + assert str(testerror) == "Dummy PSyclone Error: my msg" + assert repr(testerror) == "DummyPSycloneError()"