diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bc4c65ab2a..0beaec79f3 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -68,12 +68,11 @@ 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 .[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" - run: cd doc; make linkcheck - # TODO #2936: There are many doctest issues, so commenting out for now - # - run: cd doc; make doctest build: if: ${{ github.repository != 'stfc/PSyclone-mirror' }} runs-on: ubuntu-latest @@ -127,12 +126,12 @@ jobs: if: ${{ !(matrix.python-version == '3.9') }} run: | locale - pytest -n auto --cov=psyclone --cov-report=xml src/psyclone/tests + pytest -n auto --doctest-modules --cov=psyclone --cov-report=xml src/psyclone - name: Test with pytest and C Locale if: ${{ matrix.python-version == '3.9' }} run: | locale - pytest -n auto --cov=psyclone --cov-report=xml src/psyclone/tests + pytest -n auto --doctest-modules --cov=psyclone --cov-report=xml src/psyclone env: LC_ALL: C LANG: C diff --git a/doc/developer_guide/dependency.rst b/doc/developer_guide/dependency.rst index 782a3a18e1..b9e80e75b5 100644 --- a/doc/developer_guide/dependency.rst +++ b/doc/developer_guide/dependency.rst @@ -461,7 +461,7 @@ thread-private. Note that this code does not handle the usage of # the access information as well as from the symbol table # into account. access_sequence = var_accesses[signature] - if symbol.is_array_access(access_info=access_info): + if symbol.is_array_access(access_info=access_sequence): # It's not a scalar variable, so it will not be private continue @@ -504,7 +504,7 @@ until we find accesses that would prevent parallelisation: for next_statement in statements: # Add the variable accesses of the next statement to # the existing accesses: - next_statement.reference_accesses(accesses) + accesses = next_statement.reference_accesses() # Stop when the next statement can not be parallelised # together with the previous accesses: if not can_be_parallelised(accesses): @@ -581,7 +581,7 @@ can be parallelised: .. testoutput:: :hide: - Error: The write access to 'a(i,i)' and the read access to 'a(i + 1,i + 1)' are dependent and cannot be parallelised. Variable: 'a'. + Error: The write access to 'a(i,i)' in 'a(i,i) = j + k' and the read access to 'a(i + 1,i + 1)' in 'a(i,i) = a(i + 1,i + 1)' are dependent and cannot be parallelised. Variable: 'a'. .. _defusechain: diff --git a/doc/developer_guide/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/developer_guide/module_manager.rst b/doc/developer_guide/module_manager.rst index a9f8fc62b6..5b17116e03 100644 --- a/doc/developer_guide/module_manager.rst +++ b/doc/developer_guide/module_manager.rst @@ -121,7 +121,8 @@ 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: + # 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") @@ -134,6 +135,7 @@ which prints the filenames of all modules used in ``tl_testkern_mod``: 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 @@ -141,8 +143,6 @@ which prints the filenames of all modules used in ``tl_testkern_mod``: Module: fs_continuity_mod fs_continuity_mod.f90 Module: kernel_mod kernel_mod.f90 - - FileInfo ======== diff --git a/doc/developer_guide/psyir_symbols.rst b/doc/developer_guide/psyir_symbols.rst index c5b04f9f8f..abb3ebb0d9 100644 --- a/doc/developer_guide/psyir_symbols.rst +++ b/doc/developer_guide/psyir_symbols.rst @@ -337,7 +337,7 @@ specialisations are possible: >>> # The following statement would fail because the initial_value doesn't >>> # match the datatype of the symbol: >>> # sym2.specialise(DataSymbol, datatype=ScalarType.integer_type(), - ... initial_value=3.14) + >>> # initial_value=3.14) >>> # The following statement is valid and initial_value is set to 3 >>> # (and is_constant will default to False): >>> sym2.specialise(DataSymbol, datatype=ScalarType.integer_type(), initial_value=3) diff --git a/doc/developer_guide/transformations.rst b/doc/developer_guide/transformations.rst index 19b31333ea..766e150c34 100644 --- a/doc/developer_guide/transformations.rst +++ b/doc/developer_guide/transformations.rst @@ -33,14 +33,6 @@ .. ----------------------------------------------------------------------------- .. Written by R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab -.. testsetup:: - - # Define GOCEAN_SOURCE_FILE to point to an existing gocean 1.0 file. - GOCEAN_SOURCE_FILE = ("../src/psyclone/tests/test_files/" - "gocean1p0/test11_different_iterates_over_one_invoke.f90") - # Define NEMO_SOURCE_FILE to point to an existing nemo file. - NEMO_SOURCE_FILE = ("../examples/nemo/code/tra_adv.F90") - Transformations ############### diff --git a/doc/user_guide/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/doc/user_guide/psyir.rst b/doc/user_guide/psyir.rst index 9f385d7f63..caa82d3e87 100644 --- a/doc/user_guide/psyir.rst +++ b/doc/user_guide/psyir.rst @@ -40,7 +40,7 @@ .. testsetup:: from psyclone.psyir.symbols import DataSymbol, ScalarType, ArrayType - from psyclone.psyir.nodes import Reference + from psyclone.psyir.nodes import Reference .. _psyir-ug: @@ -268,7 +268,7 @@ example: ... ScalarType.Precision.SINGLE) >>> bool_type = ScalarType(ScalarType.Intrinsic.BOOLEAN, 4) >>> symbol = DataSymbol("rdef", int_type, initial_value=4) - >>> scalar_type = ScalarType(ScalarType.Intrinsic.REAL, symbol) + >>> scalar_type = ScalarType(ScalarType.Intrinsic.REAL, Reference(symbol)) For convenience ScalarType has static methods to create a number of common scalar datatypes: diff --git a/src/psyclone/alg_gen.py b/src/psyclone/alg_gen.py index fd290e6ca8..2781e03fe7 100644 --- a/src/psyclone/alg_gen.py +++ b/src/psyclone/alg_gen.py @@ -72,26 +72,16 @@ class Alg: latter allows consistent names to be generated between the algorithm (calling) and psy (callee) layers. - For example: - - >>> from psyclone.algorithm.parse import parse - >>> parse_tree, info = parse("argspec.F90") - >>> from psyclone.psyGen import PSy - >>> psy = PSy(info) - >>> from psyclone.alg_gen import Alg - >>> alg = Alg(parse_tree, psy) - >>> print(alg.gen) - - :param parse_tree: an object containing a parse tree of the \ - algorithm specification which was produced by the function \ - :func:`psyclone.parse.algorithm.parse`. Assumes the algorithm \ - will be parsed by fparser2 and expects a valid program unit, \ + :param parse_tree: an object containing a parse tree of the + algorithm specification which was produced by the function + :func:`psyclone.parse.algorithm.parse`. Assumes the algorithm + will be parsed by fparser2 and expects a valid program unit, program, module, subroutine or function. :type parse_tree: :py:class:`fparser.two.utils.Base` :param psy: an object containing information about the PSy layer. :type psy: :py:class:`psyclone.psyGen.PSy` - :param str invoke_name: the name that the algorithm layer uses to \ - indicate an invoke call. This is an optional argument that \ + :param str invoke_name: the name that the algorithm layer uses to + indicate an invoke call. This is an optional argument that defaults to the name "invoke". ''' diff --git a/src/psyclone/core/symbolic_maths.py b/src/psyclone/core/symbolic_maths.py index 00ef2a3910..62854baecd 100644 --- a/src/psyclone/core/symbolic_maths.py +++ b/src/psyclone/core/symbolic_maths.py @@ -50,10 +50,13 @@ class SymbolicMaths: provides convenience functions for PSyclone. It has a Singleton access, e.g.: + >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.core import SymbolicMaths >>> sympy = SymbolicMaths.get() - >>> # Assume lhs is the PSyIR of 'i+j', and rhs is 'j+i' + >>> reader = FortranReader() + >>> lhs = reader.psyir_from_expression('i+j') + >>> rhs = reader.psyir_from_expression('j+i') >>> if sympy.equal(lhs, rhs): ... writer = FortranWriter() ... print(f"'{writer(lhs)}' and '{writer(rhs)}' are equal.") diff --git a/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py b/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py index 3a01823b7b..ad097948f9 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_const_loop_bounds_trans.py @@ -70,22 +70,15 @@ class GOConstLoopBoundsTrans(Transformation): In practice, the application of the constant loop bounds transformation looks something like, e.g.: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> import os - >>> TEST_API = "gocean" - >>> _, info = parse(os.path.join("tests", "test_files", "gocean1p0", - ... "single_invoke.f90"), - ... api=TEST_API) - >>> psy = PSyFactory(TEST_API).create(info) - >>> invoke = psy.invokes.get('invoke_0_compute_cu') - >>> schedule = invoke.schedule + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "single_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> - >>> from psyclone.transformations import GOConstLoopBoundsTrans + >>> from psyclone.domain.gocean.transformations import \ + GOConstLoopBoundsTrans >>> clbtrans = GOConstLoopBoundsTrans() >>> >>> clbtrans.apply(schedule) - >>> print(schedule.view()) ''' diff --git a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py index e14e364988..c4cde8ea0d 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_extract_trans.py @@ -46,29 +46,24 @@ 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.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> - >>> API = "gocean" - >>> FILENAME = "shallow_alg.f90" - >>> ast, invokeInfo = parse(FILENAME, api=API) - >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) - >>> schedule = psy.invokes.get('invoke_0').schedule + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, "gocean-examples") >>> >>> from psyclone.domain.gocean.transformations import GOceanExtractTrans >>> etrans = GOceanExtractTrans() >>> >>> # Apply GOceanExtractTrans transformation to selected Nodes >>> etrans.apply(schedule.children[0]) - >>> print(schedule.view()) + ''' # ------------------------------------------------------------------------ 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/gocean/transformations/gocean_loop_fuse_trans.py b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py index ed50422aaa..663bbd0896 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_loop_fuse_trans.py @@ -49,17 +49,16 @@ class GOceanLoopFuseTrans(LoopFuseTrans): in order to fuse two GOcean loops after performing validity checks (e.g. that the loops are over the same grid-point type). For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("shallow_alg.f90") - >>> psy = PSyFactory("gocean").create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> print(schedule.view()) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, "gocean-examples") >>> - >>> from psyclone.transformations import GOceanLoopFuseTrans + >>> from psyclone.domain.gocean.transformations import GOceanLoopFuseTrans >>> ftrans = GOceanLoopFuseTrans() - >>> ftrans.apply(schedule[0], schedule[1]) - >>> print(schedule.view()) + + # 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]) ''' 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..fa9b7c669a 100644 --- a/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py +++ b/src/psyclone/domain/gocean/transformations/gocean_opencl_trans.py @@ -64,16 +64,24 @@ class GOOpenCLTrans(Transformation): InvokeSchedule. Additionally, it will generate OpenCL kernels for each of the kernels referenced by the Invoke. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> API = "gocean" - >>> FILENAME = "shallow_alg.f90" # examples/gocean/eg1 - >>> ast, invoke_info = parse(FILENAME, api=API) - >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) - >>> schedule = psy.invokes.get('invoke_0').schedule + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, "gocean-examples") + >>> + >>> 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() - >>> ocl_trans.apply(schedule) - >>> print(schedule.view()) + >>> for kern in schedule.kernels(): + ... # Put kernels in same container and iterate the whole space + ... mod_inline_trans.apply(kern) + ... move_trans.apply(kern) + >>> # 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/domain/lfric/transformations/lfric_extract_trans.py b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py index 3e2dd5d292..3a54fdf965 100644 --- a/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py +++ b/src/psyclone/domain/lfric/transformations/lfric_extract_trans.py @@ -47,23 +47,7 @@ class LFRicExtractTrans(ExtractTrans): ''' LFRic API application of ExtractTrans transformation - to extract code into a stand-alone program. For example: - - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> - >>> API = "lfric" - >>> FILENAME = "solver_alg.x90" - >>> ast, invokeInfo = parse(FILENAME, api=API) - >>> psy = PSyFactory(API, distributed_memory=False).create(invoke_info) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> - >>> from psyclone.domain.lfric.transformations import LFRicExtractTrans - >>> etrans = LFRicExtractTrans() - >>> - >>> # Apply LFRicExtractTrans transformation to selected Nodes - >>> etrans.apply(schedule.children[0:3]) - >>> print(schedule.view()) + to extract code into a stand-alone program. ''' 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..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,28 +48,21 @@ @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" - >>> 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()) + .. code-block :: python + + 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/generator.py b/src/psyclone/generator.py index f0005709a2..b7f765c490 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__) @@ -688,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/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/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/psyGen.py b/src/psyclone/psyGen.py index 1f8c926592..dec424c9fb 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -204,15 +204,6 @@ class PSy(): by the function :func:`parse.algorithm.parse`. :type invoke_info: :py:class:`psyclone.parse.algorithm.FileInfo` - For example: - - >>> from psyclone.parse.algorithm import parse - >>> ast, info = parse("argspec.F90") - >>> from psyclone.psyGen import PSyFactory - >>> api = "..." - >>> psy = PSyFactory(api).create(info) - >>> print(psy.gen) - ''' def __init__(self, invoke_info): @@ -630,21 +621,10 @@ class InvokeSchedule(Routine): Stores schedule information for an invocation call. Schedules can be optimised using transformations. - >>> from psyclone.parse.algorithm import parse - >>> ast, info = parse("algorithm.f90") - >>> from psyclone.psyGen import PSyFactory - >>> api = "..." - >>> psy = PSyFactory(api).create(info) - >>> invokes = psy.invokes - >>> invokes.names - >>> invoke = invokes.get("name") - >>> schedule = invoke.schedule - >>> print(schedule.view()) - :param symbol: RoutineSymbol representing the invoke. :type symbol: :py:class:`psyclone.psyir.symbols.RoutineSymbol` - :param type KernFactory: class instance of the factory to use when \ - creating Kernels. e.g. \ + :param type KernFactory: class instance of the factory to use when + creating Kernels. e.g. \ :py:class:`psyclone.domain.lfric.LFRicKernCallFactory`. :param type BuiltInFactory: class instance of the factory to use when \ creating built-ins. e.g. \ @@ -2209,18 +2189,6 @@ class TransInfo(): This utility will not find Transformations under the new file structure (TODO #620) and is deprecated. - For example: - - >>> from psyclone.psyGen import TransInfo - >>> t = TransInfo() - >>> print(t.list) - There is 1 transformation available: - 1: SwapTrans, A test transformation - >>> # accessing a transformation by index - >>> trans = t.get_trans_num(1) - >>> # accessing a transformation by name - >>> trans = t.get_trans_name("SwapTrans") - ''' def __init__(self, module=None, base_class=None): diff --git a/src/psyclone/psyir/nodes/node.py b/src/psyclone/psyir/nodes/node.py index acc90484d4..15943fb7e9 100644 --- a/src/psyclone/psyir/nodes/node.py +++ b/src/psyclone/psyir/nodes/node.py @@ -1808,10 +1808,14 @@ def path_from(self, ancestor): The result of this method can be used to find the node from its ancestor for example by: - >>> index_list = node.path_from(ancestor) - >>> cursor = ancestor + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.psyir.nodes import Literal + >>> psyir = FortranReader().psyir_from_statement("a = b + c + 1") + >>> node = psyir.walk(Literal)[0] + >>> index_list = node.path_from(psyir) + >>> cursor = psyir >>> for index in index_list: - >>> cursor = cursor.children[index] + ... cursor = cursor.children[index] >>> assert cursor is node :param ancestor: an ancestor node of self to find the path from. diff --git a/src/psyclone/psyir/nodes/structure_member.py b/src/psyclone/psyir/nodes/structure_member.py index 0e09e44db8..f8efc6b202 100644 --- a/src/psyclone/psyir/nodes/structure_member.py +++ b/src/psyclone/psyir/nodes/structure_member.py @@ -67,6 +67,7 @@ def create(member_name, inner_member): the 'xstart' component of it. We would therefore create the `StructureMember` for this by calling: + >>> from psyclone.psyir.nodes import StructureMember, Member >>> smem = StructureMember.create("subdomain", Member("xstart")) :param str member_name: name of the structure member. diff --git a/src/psyclone/psyir/symbols/symbol.py b/src/psyclone/psyir/symbols/symbol.py index a80c74dae8..a0b865e190 100644 --- a/src/psyclone/psyir/symbols/symbol.py +++ b/src/psyclone/psyir/symbols/symbol.py @@ -482,8 +482,11 @@ def is_array_access(self, index_variable=None, access_info=None): If a `loop_variable` is specified, a variable access will only be considered an array access if the specified variable is used in at least one of the indices. For example: - >>> do i=1, n - >>> a(j) = 2 + + .. code-block:: fortran + + do i=1, n + a(j) = 2 the access to `a` is not considered an array if `loop_variable` is set to `i`. If `loop_variable` is specified, `access_information` diff --git a/src/psyclone/psyir/transformations/acc_kernels_trans.py b/src/psyclone/psyir/transformations/acc_kernels_trans.py index 3473c5dc5b..beb7e49397 100644 --- a/src/psyclone/psyir/transformations/acc_kernels_trans.py +++ b/src/psyclone/psyir/transformations/acc_kernels_trans.py @@ -67,16 +67,16 @@ class ACCKernelsTrans(RegionTrans): For example: - >>> from psyclone.psyir.frontend import FortranReader - >>> psyir = FortranReader().psyir_from_source(NEMO_SOURCE_FILE) + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.tests.utilities import get_examples_path + >>> filename = get_examples_path("nemo/code/tra_adv.F90") + >>> psyir = FortranReader().psyir_from_file(filename) >>> >>> from psyclone.psyir.transformations import ACCKernelsTrans >>> ktrans = ACCKernelsTrans() >>> >>> schedule = psyir.children[0] - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> kernels = schedule.children[9] + >>> kernels = schedule.children[26] >>> # Transform the kernel >>> ktrans.apply(kernels) diff --git a/src/psyclone/psyir/transformations/acc_loop_trans.py b/src/psyclone/psyir/transformations/acc_loop_trans.py index 86e2036f5f..5c981514aa 100644 --- a/src/psyclone/psyir/transformations/acc_loop_trans.py +++ b/src/psyclone/psyir/transformations/acc_loop_trans.py @@ -52,30 +52,22 @@ class ACCLoopTrans(ParallelLoopTrans): For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.parse.utils import ParseError - >>> from psyclone.psyGen import PSyFactory - >>> from psyclone.errors import GenerationError + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") + >>> >>> from psyclone.psyir.transformations import ACCLoopTrans >>> from psyclone.transformations import ACCParallelTrans - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) >>> >>> ltrans = ACCLoopTrans() >>> rtrans = ACCParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Apply the OpenACC Loop transformation to *every* loop in the schedule >>> for child in schedule.children[:]: ... ltrans.apply(child) >>> >>> # Enclose all of these loops within a single OpenACC parallel region >>> rtrans.apply(schedule) - >>> ''' # The types of node that must be excluded from the section of PSyIR diff --git a/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py b/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py index 783cca9399..c3eac09626 100644 --- a/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py +++ b/src/psyclone/psyir/transformations/arrayaccess2loop_trans.py @@ -74,10 +74,10 @@ class ArrayAccess2LoopTrans(Transformation): >>> print(FortranWriter()(psyir)) program example real, dimension(10) :: a - integer :: ji + integer :: idx - do ji = 1, 1, 1 - a(ji) = 0.0 + do idx = 1, 1, 1 + a(idx) = 0.0 enddo end program example diff --git a/src/psyclone/psyir/transformations/datanode_to_temp_trans.py b/src/psyclone/psyir/transformations/datanode_to_temp_trans.py index c9aeba5728..d705650c52 100644 --- a/src/psyclone/psyir/transformations/datanode_to_temp_trans.py +++ b/src/psyclone/psyir/transformations/datanode_to_temp_trans.py @@ -88,13 +88,12 @@ class DataNodeToTempTrans(Transformation): >>> DataNodeToTempTrans().apply(assign.rhs, storage_name="temp") >>> print(FortranWriter()(psyir)) subroutine my_subroutine() - integer, dimension(10,10) :: a integer :: i integer :: j integer :: temp - temp = j * 2 - i = temp + temp = j * 2 + i = temp end subroutine my_subroutine diff --git a/src/psyclone/psyir/transformations/debug_checksum_trans.py b/src/psyclone/psyir/transformations/debug_checksum_trans.py index f33b0a0d92..929b21ffd9 100644 --- a/src/psyclone/psyir/transformations/debug_checksum_trans.py +++ b/src/psyclone/psyir/transformations/debug_checksum_trans.py @@ -69,8 +69,7 @@ class DebugChecksumTrans(RegionTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.transformations import DebugChecksumTrans - + >>> from psyclone.psyir.transformations import DebugChecksumTrans >>> psyir = FortranReader().psyir_from_source(""" ... subroutine mysubroutine() ... integer, dimension(10,10) :: A @@ -78,14 +77,14 @@ class DebugChecksumTrans(RegionTrans): ... integer :: j ... do i = 1, 10 ... do j = 1, 10 - ... A(i,j) = A(i,k) + i-j + ... A(i,j) = A(i,j) + i-j ... end do ... end do ... end subroutine ... """) - ... loop = psyir.children[0].children[0] - ... DebugChecksumTrans().apply(loop) - ... print(FortranWriter()(psyir)) + >>> loop = psyir.children[0].children[0] + >>> DebugChecksumTrans().apply(loop) + >>> print(FortranWriter()(psyir)) subroutine mysubroutine() integer, dimension(10,10) :: a integer :: i @@ -98,9 +97,11 @@ class DebugChecksumTrans(RegionTrans): enddo enddo PSYCLONE_INTERNAL_line_ = __LINE__ + + ! PSyclone DebugChecksumTrans-generated checksums PRINT *, "PSyclone checksums from mysubroutine at line:", \ PSYCLONE_INTERNAL_line_ + 1 - PRINT *, "a checksum", SUM(a) + PRINT *, "a checksum", SUM(a(:, :)) end subroutine mysubroutine diff --git a/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py index 9533e2ab95..9ed14d3fe9 100644 --- a/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py +++ b/src/psyclone/psyir/transformations/hoist_local_arrays_trans.py @@ -61,7 +61,7 @@ class HoistLocalArraysTrans(Transformation): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader - >>> from psyclone.psyir.nodes import Assignment + >>> from psyclone.psyir.nodes import Assignment, Routine >>> from psyclone.psyir.transformations import HoistLocalArraysTrans >>> code = ("module test_mod\\n" ... "contains\\n" @@ -84,22 +84,20 @@ class HoistLocalArraysTrans(Transformation): implicit none real, allocatable, dimension(:,:), private :: a public - - public :: test_sub contains subroutine test_sub(n) integer :: n integer :: i integer :: j - real :: value = 1.0 + real, save :: value = 1.0 - if (.not.allocated(a) .or. ubound(a, 1) /= n .or. ubound(a, 2) /= n) \ -then + if (.not.allocated(a) .or. ubound(a, dim=1) /= n .or. \ +ubound(a, dim=2) /= n) then if (allocated(a)) then deallocate(a) end if - allocate(a(1 : n, 1 : n)) + allocate(a(1:n,1:n)) end if do i = 1, n, 1 do j = 1, n, 1 diff --git a/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py b/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py index 4e1c72f061..deeadf8952 100644 --- a/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py +++ b/src/psyclone/psyir/transformations/hoist_loop_bound_expr_trans.py @@ -62,7 +62,7 @@ class HoistLoopBoundExprTrans(LoopTrans): >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.nodes import Loop - >>> from psyclone.psyir.transformations import HoistTrans + >>> from psyclone.psyir.transformations import HoistLoopBoundExprTrans >>> code = ("program test\\n" ... " use mymod, only: mytype\\n" ... " integer :: i,j,n\\n" @@ -81,12 +81,12 @@ class HoistLoopBoundExprTrans(LoopTrans): integer :: j integer :: n real, dimension(n) :: a - integer :: loop_bound - integer :: loop_bound_1 + integer :: loop_start + integer :: loop_stop - loop_bound_1 = UBOUND(a, 1) - loop_bound = mytype%start - do i = loop_bound, loop_bound_1, 1 + loop_stop = UBOUND(a, dim=1) + loop_start = mytype%start + do i = loop_start, loop_stop, 1 a(i) = 1.0 enddo diff --git a/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py index 2e01075fd3..d44d3efd0b 100644 --- a/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/maxval2loop_trans.py @@ -126,13 +126,15 @@ class Maxval2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = -HUGE(result) + reduction_var = -HUGE(reduction_var) do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = MAX(result, array(idx_1,idx)) + reduction_var = MAX(reduction_var, array(idx_1,idx)) enddo enddo + result = reduction_var end subroutine maxval_test diff --git a/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py index 70620f8b50..70e5b2e5fe 100644 --- a/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/minval2loop_trans.py @@ -126,13 +126,15 @@ class Minval2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = HUGE(result) + reduction_var = HUGE(reduction_var) do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = MIN(result, array(idx_1,idx)) + reduction_var = MIN(reduction_var, array(idx_1,idx)) enddo enddo + result = reduction_var end subroutine minval_test diff --git a/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py index 1484558ed2..a261d96c2e 100644 --- a/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/product2loop_trans.py @@ -127,13 +127,15 @@ class Product2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = 1.0 + reduction_var = 1.0 do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = result * array(idx_1,idx) + reduction_var = reduction_var * array(idx_1,idx) enddo enddo + result = reduction_var end subroutine product_test diff --git a/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py b/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py index f2cfb4c9e6..884e2feaab 100644 --- a/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/sum2loop_trans.py @@ -131,13 +131,15 @@ class Sum2LoopTrans(ArrayReductionBaseTrans): real :: result integer :: idx integer :: idx_1 + real :: reduction_var - result = 0.0 + reduction_var = 0.0 do idx = 1, 10, 1 do idx_1 = 1, 10, 1 - result = result + array(idx_1,idx) + reduction_var = reduction_var + array(idx_1,idx) enddo enddo + result = reduction_var end subroutine sum_test diff --git a/src/psyclone/psyir/transformations/loop_swap_trans.py b/src/psyclone/psyir/transformations/loop_swap_trans.py index 59f72e3d78..dd784eb2a8 100644 --- a/src/psyclone/psyir/transformations/loop_swap_trans.py +++ b/src/psyclone/psyir/transformations/loop_swap_trans.py @@ -63,19 +63,14 @@ class LoopSwapTrans(LoopTrans): This transform is used as follows: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("shallow_alg.f90") - >>> psy = PSyFactory("gocean").create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> - >>> from psyclone.transformations import LoopSwapTrans - >>> swap = LoopSwapTrans() - >>> swap.apply(schedule.children[0]) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "eg1/shallow_alg.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean-examples") + >>> + >>> from psyclone.psyir.transformations import LoopSwapTrans + >>> from psyclone.psyir.nodes import Loop + >>> swap = LoopSwapTrans() + >>> swap.apply(schedule.walk(Loop)[0]) ''' diff --git a/src/psyclone/psyir/transformations/move_trans.py b/src/psyclone/psyir/transformations/move_trans.py index 44aabfeed8..77c2dd4306 100644 --- a/src/psyclone/psyir/transformations/move_trans.py +++ b/src/psyclone/psyir/transformations/move_trans.py @@ -55,20 +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 - >>> 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()) + .. 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_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..1d6bee09ad 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_loop_trans.py @@ -47,25 +47,39 @@ class OMPParallelLoopTrans(OMPLoopTrans): - ''' Adds an OpenMP PARALLEL DO directive to a loop. - - For example: - - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> ast, invokeInfo = parse("lfric.F90") - >>> psy = PSyFactory("lfric").create(invokeInfo) - >>> schedule = psy.invokes.get('invoke_v3_kernel_type').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> - >>> from psyclone.transformations import OMPParallelLoopTrans - >>> trans = OMPParallelLoopTrans() - >>> trans.apply(schedule.children[0]) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + ''' 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): return "Add an 'OpenMP PARALLEL DO' directive" diff --git a/src/psyclone/psyir/transformations/omp_parallel_trans.py b/src/psyclone/psyir/transformations/omp_parallel_trans.py index c6fe3843d7..cb76927a26 100644 --- a/src/psyclone/psyir/transformations/omp_parallel_trans.py +++ b/src/psyclone/psyir/transformations/omp_parallel_trans.py @@ -62,33 +62,24 @@ class OMPParallelTrans(ParallelRegionTrans): Create an OpenMP PARALLEL region by inserting directives. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.parse.utils import ParseError - >>> from psyclone.psyGen import PSyFactory - >>> from psyclone.errors import GenerationError - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.psyGen import TransInfo >>> t = TransInfo() >>> ltrans = t.get_trans_name('GOceanOMPLoopTrans') - >>> rtrans = t.get_trans_name('OMPParallelTrans') - >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + >>> from psyclone.psyir.transformations import OMPParallelTrans + >>> rtrans = OMPParallelTrans() >>> >>> # Apply the OpenMP Loop transformation to *every* loop >>> # in the schedule >>> for child in schedule.children: - >>> ltrans.apply(child) + ... ltrans.apply(child) >>> >>> # Enclose all of these loops within a single OpenMP >>> # PARALLEL region >>> rtrans.apply(schedule.children) - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) ''' # The types of node that this transformation cannot enclose diff --git a/src/psyclone/psyir/transformations/omp_taskloop_trans.py b/src/psyclone/psyir/transformations/omp_taskloop_trans.py index 05bd2e3b20..ab8901e620 100644 --- a/src/psyclone/psyir/transformations/omp_taskloop_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskloop_trans.py @@ -47,31 +47,25 @@ class OMPTaskloopTrans(ParallelLoopTrans): For example: - >>> from pysclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPSingleTrans >>> from psyclone.psyir.transformations import OMPParallelTrans - >>> from psyclone.transformations import OMPTaskloopTrans + >>> from psyclone.psyir.transformations import OMPTaskloopTrans >>> from psyclone.psyir.transformations import OMPTaskwaitTrans >>> singletrans = OMPSingleTrans() >>> paralleltrans = OMPParallelTrans() >>> tasklooptrans = OMPTaskloopTrans() >>> taskwaittrans = OMPTaskwaitTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Apply the OpenMP Taskloop transformation to *every* loop >>> # in the schedule. >>> # This ignores loop dependencies. These can be handled >>> # by the OMPTaskwaitTrans >>> for child in schedule.children: - >>> tasklooptrans.apply(child) + ... tasklooptrans.apply(child) >>> # Enclose all of these loops within a single OpenMP >>> # SINGLE region >>> singletrans.apply(schedule.children) @@ -79,11 +73,10 @@ 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()) + >>> taskwaittrans.apply(schedule.children[0]) ''' + def __init__(self, grainsize=None, num_tasks=None, nogroup=False): self._grainsize = None self._num_tasks = None diff --git a/src/psyclone/psyir/transformations/omp_taskwait_trans.py b/src/psyclone/psyir/transformations/omp_taskwait_trans.py index 13d2858693..d159094d2f 100644 --- a/src/psyclone/psyir/transformations/omp_taskwait_trans.py +++ b/src/psyclone/psyir/transformations/omp_taskwait_trans.py @@ -66,39 +66,32 @@ class OMPTaskwaitTrans(Transformation): For example: - >>> from pysclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> filename = "nemolite2d_alg.f90" - >>> ast, invokeInfo = parse(filename, api=api, invoke_name="invoke") - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPSingleTrans >>> from psyclone.psyir.transformations import OMPParallelTrans - >>> from psyclone.transformations import OMPTaskloopTrans + >>> from psyclone.psyir.transformations import OMPTaskloopTrans >>> from psyclone.psyir.transformations import OMPTaskwaitTrans >>> singletrans = OMPSingleTrans() >>> paralleltrans = OMPParallelTrans() >>> tasklooptrans = OMPTaskloopTrans() >>> taskwaittrans = OMPTaskwaitTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> print(schedule.view()) - >>> >>> # Apply the OpenMP Taskloop transformation to *every* loop >>> # in the schedule. >>> # This ignores loop dependencies. These are handled by the >>> # taskwait transformation. >>> for child in schedule.children: - >>> tasklooptrans.apply(child, nogroup = true) + ... tasklooptrans.apply(child, nogroup = True) >>> # Enclose all of these loops within a single OpenMP >>> # SINGLE region >>> singletrans.apply(schedule.children) >>> # Enclose all of these loops within a single OpenMP >>> # PARALLEL region >>> paralleltrans.apply(schedule.children) - >>> taskwaittrans.apply(schedule.children) - >>> print(schedule.view()) + >>> taskwaittrans.apply(schedule.children[0]) ''' def __str__(self): diff --git a/src/psyclone/psyir/transformations/profile_trans.py b/src/psyclone/psyir/transformations/profile_trans.py index d5e6900a23..addc72ecaf 100644 --- a/src/psyclone/psyir/transformations/profile_trans.py +++ b/src/psyclone/psyir/transformations/profile_trans.py @@ -48,23 +48,15 @@ class ProfileTrans(PSyDataTrans): ''' Create a profile region around a list of statements. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.parse.utils import ParseError - >>> from psyclone.psyGen import PSyFactory, GenerationError - >>> from psyclone.psyir.transformations import ProfileTrans - >>> api = "gocean" - >>> filename = "nemolite2d_alg.f90" - >>> ast, invokeInfo = parse(filename, api=api, invoke_name="invoke") - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "nemolite2d_alg_mod.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> + >>> from psyclone.psyir.transformations import ProfileTrans >>> p_trans = ProfileTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> print(schedule.view()) - >>> >>> # Enclose all children within a single profile region >>> p_trans.apply(schedule.children) - >>> print(schedule.view()) This implementation relies completely on the base class PSyDataTrans for the actual work, it only adjusts the name etc, and the list diff --git a/src/psyclone/psyir/transformations/psy_data_trans.py b/src/psyclone/psyir/transformations/psy_data_trans.py index d7cfe6742d..feb298e4fb 100644 --- a/src/psyclone/psyir/transformations/psy_data_trans.py +++ b/src/psyclone/psyir/transformations/psy_data_trans.py @@ -55,16 +55,13 @@ 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.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> from psyclone.psyir.transformations import PSyDataTrans >>> data_trans = PSyDataTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Enclose all children within a single PSyData region >>> data_trans.apply(schedule.children) >>> # Uncomment the following line to see a text view of the schedule diff --git a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py index acb9b95ce8..f63d13f7e2 100644 --- a/src/psyclone/psyir/transformations/reference2arrayrange_trans.py +++ b/src/psyclone/psyir/transformations/reference2arrayrange_trans.py @@ -77,17 +77,15 @@ class Reference2ArrayRangeTrans(Transformation): >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.nodes import Reference >>> from psyclone.psyir.transformations import TransformationError - >>> CODE = ("program example\\n" - ... "real :: a(:)\\n" - ... "a = 0.0\\n" - ... "end program\\n") + >>> from psyclone.psyir.transformations import Reference2ArrayRangeTrans >>> trans = Reference2ArrayRangeTrans() - >>> psyir = FortranReader().psyir_from_source(CODE) + >>> psyir = FortranReader().psyir_from_source(""" + ... program example + ... real :: a(:) + ... a = 0.0 + ... end program""") >>> for reference in psyir.walk(Reference): - ... try: - ... trans.apply(reference) - ... except TransformationError: - ... pass + ... trans.apply(reference) >>> print(FortranWriter()(psyir)) program example real, dimension(:) :: a diff --git a/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py b/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py index fe4b9420ff..fc5c051796 100644 --- a/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py +++ b/src/psyclone/psyir/transformations/replace_reference_by_literal_trans.py @@ -68,10 +68,12 @@ class ReplaceReferenceByLiteralTrans(Transformation): For example: + >>> from psyclone.psyir.frontend.fortran import FortranReader >>> from psyclone.psyir.backend.fortran import FortranWriter >>> from psyclone.psyir.symbols import ScalarType + >>> from psyclone.psyir.nodes import Routine >>> from psyclone.psyir.transformations import ( - ReplaceReferenceByLiteralTrans) + ... ReplaceReferenceByLiteralTrans) >>> source = """program test ... use mymod ... type(my_type):: t1, t2, t3, t4 @@ -115,7 +117,7 @@ class ReplaceReferenceByLiteralTrans(Transformation): integer :: ic2 integer :: ic3 real, dimension(10) :: a - + invariant = 1 do i = 1, 10, 1 t1%a = 13 @@ -124,11 +126,12 @@ class ReplaceReferenceByLiteralTrans(Transformation): a(ic3) = 3 + (ic3 + 13) * ic3 a(t1%a) = 4 + (t1%a + 4 * 13) * t1%a enddo - + end program test ''' + def __init__(self) -> None: super().__init__() # Dictionary with Literal values of the corresponding symbol diff --git a/src/psyclone/psyir/transformations/scalarisation_trans.py b/src/psyclone/psyir/transformations/scalarisation_trans.py index 496d1dd786..ee076cea14 100644 --- a/src/psyclone/psyir/transformations/scalarisation_trans.py +++ b/src/psyclone/psyir/transformations/scalarisation_trans.py @@ -201,10 +201,12 @@ def _get_index_values_from_indices( ''' Compute a list of index values for a given node. Looks at loop bounds and range declarations to attempt to convert loop variables to an - explicit range, i.e. an access like + explicit range, i.e. an access like: + .. code-block:: fortran + do i = 1, 100 - array(i) = ... + array(i) = ... end do the returned list would contain a range object for [1:100]. diff --git a/src/psyclone/tests/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/exceptions_test.py b/src/psyclone/tests/exceptions_test.py index 7e19d1400e..936b4de006 100644 --- a/src/psyclone/tests/exceptions_test.py +++ b/src/psyclone/tests/exceptions_test.py @@ -32,103 +32,41 @@ # 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, "") - 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 __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. ''' - 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 = [exc for exc in all_exceptions if "psyclone." in str(exc)] - psy_excepts.append(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 - assert issubclass(psy_except, PSycloneError) - + 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()) + # 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()" 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 55e8c0288e..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="nemo") + 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="nemo") + 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/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/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/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 169441cfed..6fd3e75386 100644 --- a/src/psyclone/tests/utilities.py +++ b/src/psyclone/tests/utilities.py @@ -75,7 +75,6 @@ def __init__(self, value): PSycloneError.value = "Compile error: " + str(value) -# ============================================================================= def line_number(root, string_name): '''Helper routine which returns the first index of the supplied name in the root object, when it is converted into a string, or @@ -98,7 +97,6 @@ def line_number(root, string_name): return -1 -# ============================================================================= def count_lines(root, string_name): '''Helper routine which returns the number of lines that contain the supplied string. @@ -120,7 +118,6 @@ def count_lines(root, string_name): return count -# ============================================================================= def print_diffs(expected, actual): ''' Pretty-print the diff between the two, possibly multi-line, strings @@ -135,7 +132,6 @@ def print_diffs(expected, actual): pprint(diff_list) -# ============================================================================= @contextmanager def change_dir(new_dir): '''This is a small context manager that changes the current working @@ -154,7 +150,6 @@ def change_dir(new_dir): os.chdir(prev_dir) -# ============================================================================= class Compile(): '''This class provides compile functionality to the testing framework. It stores the name of the compiler, compiler flags, and a temporary @@ -507,13 +502,13 @@ def string_compiles(self, code: str) -> bool: return success -# ============================================================================= -def get_base_path(api: str) -> str: +def get_base_path(api: str = "") -> str: '''Get the absolute base path for the specified API relative to the 'tests/test_files' directory, i.e. the directory in which all Fortran test files are stored. - :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. @@ -521,16 +516,17 @@ 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": "../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: - 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) @@ -555,7 +551,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, @@ -586,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) @@ -600,7 +602,45 @@ def get_invoke(algfile: str, return psy, invoke -# ============================================================================= +def get_psylayer_schedule( + algfile: str, + api: str, + invoke_name: str = "", + dist_mem: bool = False +) -> Node: + ''' + Utility method to get the psylayer schedule associated with the given + algorithm file. + + :param algfile: name of the Algorithm source file (Fortran). + :param api: which PSyclone API this Algorithm uses. 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. + + :returns: the associated psylayer 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): + ''' + :param relative_path: given a relative examples file path. + + :returns: its absolute file path. + + ''' + return os.path.join( + os.path.dirname(os.path.abspath(__file__)), + "../../../examples", + relative_path) + + def get_ast(api: str, filename: str) -> BeginSource: '''Returns the fparser1 parse tree for a filename that is stored in the test files for the specified API. @@ -618,7 +658,6 @@ def get_ast(api: str, filename: str) -> BeginSource: return ast -# ============================================================================= def check_links(parent: Node, children: list[Node]) -> None: '''Utility routine to check that the parent node has children as its children in the order specified and that the children have parent @@ -665,7 +704,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/tests/utilities_test.py b/src/psyclone/tests/utilities_test.py index 03ced7a465..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: @@ -376,10 +375,10 @@ 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: + with pytest.raises(ValueError) as err: _ = get_base_path("INVALID") assert "The API 'INVALID' is not supported" in str(err.value) diff --git a/src/psyclone/transformations.py b/src/psyclone/transformations.py index 30dd962303..a77bce1710 100644 --- a/src/psyclone/transformations.py +++ b/src/psyclone/transformations.py @@ -356,17 +356,14 @@ 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()) + .. 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): @@ -445,34 +442,24 @@ class LFRicColourTrans(ColourTrans): '''Split an LFRic loop over cells into colours so that it can be parallelised. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> import transformations - >>> import os - >>> import pytest + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "4.6_multikernel_invokes.f90" + >>> schedule = get_psylayer_schedule(filename, api="lfric") >>> - >>> TEST_API = "lfric" - >>> _,info=parse(os.path.join(os.path.dirname(os.path.abspath(__file__)), - >>> "tests", "test_files", "lfric", - >>> "4.6_multikernel_invokes.f90"), - >>> api=TEST_API) - >>> psy = PSyFactory(TEST_API).create(info) - >>> invoke = psy.invokes.get('invoke_0') - >>> schedule = invoke.schedule + >>> from psyclone.psyir.nodes import Loop + >>> from psyclone.transformations import ( + ... LFRicColourTrans, LFRicOMPParallelLoopTrans) >>> >>> ctrans = LFRicColourTrans() >>> otrans = LFRicOMPParallelLoopTrans() >>> >>> # Colour all of the loops - >>> for child in schedule.children: - >>> ctrans.apply(child) + >>> for child in schedule.walk(Loop, stop_type=Loop): + ... ctrans.apply(child) >>> >>> # Then apply OpenMP to each of the colour loops - >>> for child in schedule.children: - >>> otrans.apply(child.children[0]) - >>> - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) + >>> for child in schedule.walk(Loop, stop_type=Loop): + ... otrans.apply(child.loop_body[0]) Colouring in the LFRic API is subject to the following rules: @@ -686,21 +673,15 @@ class OMPSingleTrans(ParallelRegionTrans): For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> >>> from psyclone.transformations import OMPSingleTrans >>> from psyclone.psyir.transformations import OMPParallelTrans >>> singletrans = OMPSingleTrans() >>> paralleltrans = OMPParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Enclose all of these loops within a single OpenMP >>> # SINGLE region >>> singletrans.apply(schedule.children) @@ -817,29 +798,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 @@ -1036,22 +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: - - >>> 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()) + halo exchange end. ''' @@ -1124,23 +1082,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" - >>> 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] ''' @@ -1458,22 +1406,17 @@ class ACCEnterDataTrans(Transformation): Adds an OpenACC "enter data" directive to a Schedule. For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> - >>> from psyclone.transformations import \ - ACCEnterDataTrans, ACCLoopTrans, ACCParallelTrans + >>> from psyclone.transformations import ( + ... ACCEnterDataTrans, ACCParallelTrans) + >>> from psyclone.psyir.transformations import ACCLoopTrans >>> dtrans = ACCEnterDataTrans() >>> ltrans = ACCLoopTrans() >>> ptrans = ACCParallelTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> >>> # Apply the OpenACC Loop transformation to *every* loop in the schedule >>> for child in schedule.children[:]: ... ltrans.apply(child) @@ -1483,9 +1426,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): @@ -1612,19 +1552,20 @@ class ACCRoutineTrans(Transformation, MarkRoutineForGPUMixin, (causing it to be compiled for the OpenACC accelerator device). For example: - >>> from psyclone.parse.algorithm import parse - >>> from psyclone.psyGen import PSyFactory - >>> api = "gocean" - >>> ast, invokeInfo = parse(GOCEAN_SOURCE_FILE, api=api) - >>> psy = PSyFactory(api).create(invokeInfo) + >>> from psyclone.tests.utilities import get_psylayer_schedule + >>> filename = "test11_different_iterates_over_one_invoke.f90" + >>> schedule = get_psylayer_schedule(filename, api="gocean") >>> + >>> from psyclone.psyGen import CodedKern >>> from psyclone.transformations import ACCRoutineTrans >>> rtrans = ACCRoutineTrans() + >>> from psyclone.domain.common.transformations import ( + ... KernelModuleInlineTrans) + >>> itrans = KernelModuleInlineTrans() >>> - >>> schedule = psy.invokes.get('invoke_0').schedule - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) - >>> kern = schedule.children[0].children[0].children[0] + >>> kern = schedule.walk(CodedKern)[0] + >>> # Bring the kernel into the same module + >>> itrans.apply(kern) >>> # Transform the kernel >>> rtrans.apply(kern) @@ -1731,8 +1672,10 @@ class ACCDataTrans(RegionTrans): For example: - >>> from psyclone.psyir.frontend import FortranReader - >>> psyir = FortranReader().psyir_from_source(NEMO_SOURCE_FILE) + >>> from psyclone.psyir.frontend.fortran import FortranReader + >>> from psyclone.tests.utilities import get_examples_path + >>> filename = get_examples_path("nemo/code/tra_adv.F90") + >>> psyir = FortranReader().psyir_from_file(filename) >>> >>> from psyclone.transformations import ACCDataTrans >>> from psyclone.psyir.transformations import ACCKernelsTrans @@ -1740,15 +1683,13 @@ class ACCDataTrans(RegionTrans): >>> dtrans = ACCDataTrans() >>> >>> schedule = psyir.children[0] - >>> # Uncomment the following line to see a text view of the schedule - >>> # print(schedule.view()) >>> >>> # Add a kernels construct for execution on the device - >>> kernels = schedule.children[9] + >>> kernels = schedule.children[26] >>> ktrans.apply(kernels) >>> >>> # Enclose the kernels in a data construct - >>> kernels = schedule.children[9] + >>> kernels = schedule.children[26] >>> dtrans.apply(kernels) ''' 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.