def test_omp_do_within_if(): ''' Check that we can insert an OpenMP parallel do within an if block. ''' from psyclone.transformations import OMPParallelLoopTrans otrans = OMPParallelLoopTrans() _, invoke_info = parse(os.path.join(BASE_PATH, "imperfect_nest.f90"), api=API, line_length=False) psy = PSyFactory(API, distributed_memory=False).create(invoke_info) schedule = psy.invokes.get('imperfect_nest').schedule loop = schedule[0].loop_body[1].else_body[0].else_body[0] assert isinstance(loop, nemo.NemoLoop) # Apply the transformation to a loop within an else clause schedule, _ = otrans.apply(loop) gen = str(psy.gen) expected = (" ELSE\n" " !$omp parallel do default(shared), private(ji,jj), " "schedule(static)\n" " DO jj = 1, jpj, 1\n" " DO ji = 1, jpi, 1\n" " zdkt(ji, jj) = (ptb(ji, jj, jk - 1, jn) - " "ptb(ji, jj, jk, jn)) * wmask(ji, jj, jk)\n" " END DO\n" " END DO\n" " !$omp end parallel do\n" " END IF\n") assert expected in gen
def test_loop_trans_validate(monkeypatch): ''' Test the validation checks on the loop node provided to the transformation. ''' # We have to use sub-class of LoopTrans as it itself is abstract. trans = OMPParallelLoopTrans() _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", idx=1, dist_mem=False) with pytest.raises(TransformationError) as err: trans.validate(invoke.schedule) assert ("Target of OMPParallelLoopTrans transformation must be a sub-" "class of Loop but got 'GOInvokeSchedule'" in str(err.value)) # Check that validate is OK with a valid loop loop = invoke.schedule.walk(Loop)[0] trans.validate(loop) # Pretend that the loop is of 'null' type monkeypatch.setattr(loop, "_loop_type", "null") with pytest.raises(TransformationError) as err: trans.validate(loop) assert ("Cannot apply a OMPParallelLoopTrans transformation to a " "'null' loop" in str(err.value)) monkeypatch.undo() # Break the contents of the loop loop.children = loop.pop_all_children()[0:1] with pytest.raises(TransformationError) as err: trans.validate(loop) assert ("Error in OMPParallelLoopTrans transformation. The target loop " "must have four children but found: ['Literal']" in str(err.value))
def test_loop_trans_name(): ''' Check that the name method works as expected. ''' # We have to use sub-classes of LoopTrans as it itself is abstract. trans1 = OMPParallelLoopTrans() assert trans1.name == "OMPParallelLoopTrans" trans2 = LoopFuseTrans() assert trans2.name == "LoopFuseTrans"
def test_loop_trans_validate_nemo_specific(monkeypatch): ''' Test the NEMO-specifc part of the validation routine. TODO #435 remove this test. ''' trans = OMPParallelLoopTrans() _, invoke_info = get_invoke("explicit_do.f90", api="nemo", idx=0) schedule = invoke_info.schedule loop = schedule.loops()[0] monkeypatch.setattr(loop, "_annotations", ["was_where"]) with pytest.raises(TransformationError) as err: trans.validate(loop) assert ("In the NEMO API a transformation cannot be applied to a PSyIR " "loop representing a WHERE construct." in str(err.value))
def test_parallellooptrans_refuse_codeblock(): ''' Check that ParallelLoopTrans.validate() rejects a loop nest that encloses a CodeBlock. We have to use OMPParallelLoopTrans as ParallelLoopTrans is abstract. ''' otrans = OMPParallelLoopTrans() # Construct a valid Loop in the PSyIR with a CodeBlock in its body parent = Loop.create(DataSymbol("ji", INTEGER_TYPE), Literal("1", INTEGER_TYPE), Literal("10", INTEGER_TYPE), Literal("1", INTEGER_TYPE), [CodeBlock([], CodeBlock.Structure.STATEMENT, None)]) with pytest.raises(TransformationError) as err: otrans.validate(parent) assert ("Nodes of type 'CodeBlock' cannot be enclosed " "by a OMPParallelLoopTrans transformation" in str(err.value))
def test_omp_do_missing_parent(monkeypatch): ''' Check that we raise the expected error when we cannot find the parent node in the fparser2 AST. ''' from psyclone.transformations import OMPParallelLoopTrans otrans = OMPParallelLoopTrans() _, invoke_info = parse(os.path.join(BASE_PATH, "imperfect_nest.f90"), api=API, line_length=False) psy = PSyFactory(API, distributed_memory=False).create(invoke_info) schedule = psy.invokes.get('imperfect_nest').schedule schedule, _ = otrans.apply(schedule.children[0]) # Remove the reference to the fparser2 AST from the Schedule node monkeypatch.setattr(schedule, "_ast", None) with pytest.raises(InternalError) as err: _ = psy.gen assert ("Failed to find parent node in which to insert OpenMP parallel " "do directive" in str(err))
def test_omp_do_children_err(): ''' Tests that we raise the expected error when an OpenMP parallel do directive has more than one child. ''' from psyclone.transformations import OMPParallelLoopTrans from psyclone.psyGen import OMPParallelDoDirective otrans = OMPParallelLoopTrans() psy, invoke_info = get_invoke("imperfect_nest.f90", api=API, idx=0) schedule = invoke_info.schedule otrans.apply(schedule[0].loop_body[2]) directive = schedule[0].loop_body[2] assert isinstance(directive, OMPParallelDoDirective) # Make the schedule invalid by adding a second child to the # OMPParallelDoDirective directive.dir_body.children.append(Statement()) with pytest.raises(GenerationError) as err: _ = psy.gen assert ("An OpenMP PARALLEL DO can only be applied to a single loop but " "this Node has 2 children:" in str(err.value))
def test_loop_trans_validate_options(monkeypatch): ''' Test the options argument to the validate method. ''' trans = OMPParallelLoopTrans() _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", idx=1, dist_mem=False) loop = invoke.schedule.walk(Loop)[0] with pytest.raises(TransformationError) as err: trans.validate(loop, options="hello") assert ("method 'options' argument must be a dictionary but found 'str'" in str(err.value)) # Monkeypatch the transformation to make it appear that we wish to # exclude CodedKern nodes. monkeypatch.setattr(trans, "excluded_node_types", (CodedKern, )) with pytest.raises(TransformationError) as err: trans.validate(loop) assert ("Nodes of type 'GOKern' cannot be enclosed by a " "OMPParallelLoopTrans transformation" in str(err.value)) # Now disable this check on excluded node types trans.validate(loop, options={"node-type-check": False})
def test_omp_do_children_err(): ''' Tests that we raise the expected error when an OpenMP parallel do directive has more than one child. ''' from psyclone.transformations import OMPParallelLoopTrans from psyclone.psyGen import OMPParallelDoDirective otrans = OMPParallelLoopTrans() _, invoke_info = parse(os.path.join(BASE_PATH, "imperfect_nest.f90"), api=API, line_length=False) psy = PSyFactory(API, distributed_memory=False).create(invoke_info) schedule = psy.invokes.get('imperfect_nest').schedule new_sched, _ = otrans.apply(schedule.children[0].children[2]) directive = new_sched.children[0].children[2] assert isinstance(directive, OMPParallelDoDirective) # Make the schedule invalid by adding a second child to the # OMPParallelDoDirective directive.children.append(new_sched.children[0].children[3]) with pytest.raises(GenerationError) as err: _ = psy.gen assert ("An OpenMP PARALLEL DO can only be applied to a single loop but " "this Node has 2 children:" in str(err))
def test_invalid_apply(): '''Test the exceptions that should be raised by ReadOnlyVerifyTrans. ''' _, invoke = get_invoke("test11_different_iterates_over_one_invoke.f90", "gocean1.0", idx=0) read_only = ReadOnlyVerifyTrans() omp = OMPParallelLoopTrans() _, _ = omp.apply(invoke.schedule[0]) with pytest.raises(TransformationError) as err: _, _ = read_only.apply(invoke.schedule[0].dir_body[0], options={"region_name": ("a", "b")}) assert "Error in ReadOnlyVerifyTrans: Application to a Loop without its "\ "parent Directive is not allowed." in str(err.value) with pytest.raises(TransformationError) as err: _, _ = read_only.apply(invoke.schedule[0].dir_body[0].loop_body[0], options={"region_name": ("a", "b")}) assert "Error in ReadOnlyVerifyTrans: Application to Nodes enclosed " \ "within a thread-parallel region is not allowed." in str(err.value)
def test_loop_trans_validate(): ''' Test the validation checks on the loop node provided to the transformation. ''' trans = OMPParallelLoopTrans() _, invoke = get_invoke("test27_loop_swap.f90", "gocean1.0", idx=1, dist_mem=False) with pytest.raises(TransformationError) as err: trans.validate(invoke.schedule) assert ("Target of OMPParallelLoopTrans transformation must be a sub-" "class of Loop but got 'GOInvokeSchedule'" in str(err.value)) # Check that validate is OK with a valid loop loop = invoke.schedule.walk(Loop)[0] trans.validate(loop) # Break the loop loop.children = loop.children[0:1] with pytest.raises(TransformationError) as err: trans.validate(loop) assert ("Error in OMPParallelLoopTrans transformation. The target loop " "must have four children but found: ['Literal']" in str(err.value))
def test_omp_do_within_if(): ''' Check that we can insert an OpenMP parallel do within an if block. ''' from psyclone.transformations import OMPParallelLoopTrans otrans = OMPParallelLoopTrans() psy, invoke_info = get_invoke("imperfect_nest.f90", api=API, idx=0) schedule = invoke_info.schedule loop = schedule[0].loop_body[1].else_body[0].else_body[0] assert isinstance(loop, nemo.NemoLoop) # Apply the transformation to a loop within an else clause otrans.apply(loop) gen = str(psy.gen).lower() expected = (" else\n" " !$omp parallel do default(shared), private(ji,jj), " "schedule(static)\n" " do jj = 1, jpj, 1\n" " do ji = 1, jpi, 1\n" " zdkt(ji, jj) = (ptb(ji, jj, jk - 1, jn) - " "ptb(ji, jj, jk, jn)) * wmask(ji, jj, jk)\n" " end do\n" " end do\n" " !$omp end parallel do\n" " end if\n") assert expected in gen
def test_profile_nemo_openmp(parser): ''' Check that the automatic kernel-level profiling handles a tightly-nested loop that has been parallelised using OpenMP. ''' omptrans = OMPParallelLoopTrans() Profiler.set_options([Profiler.KERNELS]) psy, schedule = get_nemo_schedule( parser, "program do_loop\n" "integer, parameter :: jpi=5, jpj=5\n" "integer :: ji, jj\n" "real :: sto_tmp(jpi,jpj)\n" "do jj = 1, jpj\n" " do ji = 1,jpi\n" " sto_tmp(ji,jj) = 1.0d0\n" " end do\n" "end do\n" "end program do_loop\n") omptrans.apply(schedule[0]) Profiler.add_profile_nodes(schedule, Loop) code = str(psy.gen).lower() assert (" type(profile_psydatatype), target, save :: profile_psy_data0\n" " call profile_psy_data0 % prestart('do_loop', 'r0', 0, 0)\n" " !$omp parallel do default(shared), private(ji,jj), " "schedule(static)\n" " do jj = 1, jpj" in code)
Once you have PSyclone installed, this script may be used by doing: >>> psyclone -api "nemo" -s ./omp_trans.py my_file.F90 This should produce a lot of output, ending with generated Fortran. ''' from psyclone.psyir.nodes import Loop from psyclone.psyGen import Directive from psyclone.transformations import OMPParallelLoopTrans, OMPLoopTrans, \ OMPParallelTrans, TransformationError # Get the transformation we will apply OMP_TRANS = OMPParallelLoopTrans() OMP_LOOP_TRANS = OMPLoopTrans() OMP_PARALLEL_TRANS = OMPParallelTrans() def trans(psy): ''' Transform a specific Schedule by making all loops over vertical levels OpenMP parallel. :param psy: the object holding all information on the PSy layer \ to be modified. :type psy: :py:class:`psyclone.psyGen.PSy` :returns: the transformed PSy object :rtype: :py:class:`psyclone.psyGen.PSy`