def test_implicit_loop_different_rank(): ''' Test that we reject implicit loops if the index positions of the colons differs. This is a restriction that could be lifted by using e.g. SIZE(zvab, 1) as the upper loop limit or (with a lot more work) by interrogating the parsed code to figure out the loop bound. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "array_section_index_mismatch.f90"), api=API, line_length=False) psy = PSyFactory(API).create(invoke_info) sched = psy.invokes.invoke_list[0].schedule loop = sched.children[1] trans = TransInfo().get_trans_name('NemoExplicitLoopTrans') with pytest.raises(TransformationError) as err: _ = trans.apply(loop) assert ("implicit loops are restricted to cases where all array " "range specifications occur" in str(err)) loop = sched.children[2] with pytest.raises(InternalError) as err: _ = trans.apply(loop) assert ("Expecting a colon for index 3 but array only has 2 " "dimensions: zab(" in str(err))
def test_omp_parallel_errs(): ''' Check that we raise the expected errors when incorrectly attempting to add an OpenMP parallel region containing more than one node. ''' from psyclone.transformations import OMPParallelTrans otrans = OMPParallelTrans() _, 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.view() # Apply the OMP Parallel transformation so as to enclose the last two # loop nests (Python's slice notation is such that the expression below # gives elements 2-3). new_sched, _ = otrans.apply(schedule.children[0].loop_body[2:4]) directive = new_sched.children[0].loop_body[2] # Break the AST by deleting some of it _ = new_sched.children[0].ast.content.remove(directive.children[0].ast) with pytest.raises(InternalError) as err: _ = psy.gen assert ("Failed to find locations to insert begin/end directives" in str(err))
def test_operator_read_level1_halo(tmpdir): ''' Check that we raise an error if a kernel attempts to read from an operator beyond the level-1 halo. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "10.7_operator_read.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) schedule = psy.invokes.invoke_list[0].schedule loop = schedule.children[0] # Modify the loop bound so that we attempt to read from the L2 halo # (of the operator) loop.set_upper_bound("cell_halo", index=2) # Attempt to generate the code with pytest.raises(GenerationError) as excinfo: _ = psy.gen assert ("Kernel 'testkern_operator_read_code' reads from an operator and " "therefore cannot be used for cells beyond the level 1 halo. " "However the containing loop goes out to level 2" in str(excinfo.value))
def test_operator_deref(tmpdir, dist_mem): ''' Tests that we generate correct names for an operator in the PSy layer when obtained by de-referencing a derived type in the Algorithm layer ''' _, invoke_info = parse(os.path.join(BASE_PATH, "10.8_operator_deref.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=dist_mem).create(invoke_info) generated_code = str(psy.gen) assert Dynamo0p3Build(tmpdir).code_compiles(psy) assert ("SUBROUTINE invoke_0_testkern_operator_type(mm_w0_op, chi, a, qr)" in generated_code) assert "TYPE(operator_type), intent(inout) :: mm_w0_op" in generated_code assert "TYPE(operator_proxy_type) mm_w0_op_proxy" in generated_code assert "mm_w0_op_proxy = mm_w0_op%get_proxy()" in generated_code assert ("CALL testkern_operator_code(cell, nlayers, " "mm_w0_op_proxy%ncell_3d, mm_w0_op_proxy%local_stencil, " "chi_proxy(1)%data, chi_proxy(2)%data, chi_proxy(3)%data, a, " "ndf_w0, undf_w0, map_w0(:,cell), basis_w0_qr, " "diff_basis_w0_qr, np_xy_qr, np_z_qr, weights_xy_qr, " "weights_z_qr)" in generated_code)
def test_node_args(): '''Test that the Node class args method returns the correct arguments for Nodes that do not have arguments themselves''' _, invoke_info = parse(os.path.join(BASE_PATH, "4_multikernel_invokes.f90"), api="dynamo0.3") psy = PSyFactory("dynamo0.3", distributed_memory=False).create(invoke_info) invoke = psy.invokes.invoke_list[0] schedule = invoke.schedule loop1 = schedule.children[0] kern1 = loop1.loop_body[0] loop2 = schedule.children[1] kern2 = loop2.loop_body[0] # 1) Schedule (not that this is useful) all_args = kern1.arguments.args all_args.extend(kern2.arguments.args) schedule_args = schedule.args for idx, arg in enumerate(all_args): assert arg == schedule_args[idx] # 2) Loop1 loop1_args = loop1.args for idx, arg in enumerate(kern1.arguments.args): assert arg == loop1_args[idx] # 3) Loop2 loop2_args = loop2.args for idx, arg in enumerate(kern2.arguments.args): assert arg == loop2_args[idx] # 4) Loop fuse ftrans = DynamoLoopFuseTrans() ftrans.same_space = True schedule, _ = ftrans.apply(schedule.children[0], schedule.children[1]) loop = schedule.children[0] kern1 = loop.loop_body[0] kern2 = loop.loop_body[1] loop_args = loop.args kern_args = kern1.arguments.args kern_args.extend(kern2.arguments.args) for idx, arg in enumerate(kern_args): assert arg == loop_args[idx]
def test_stencil_information(tmpdir): '''Test that the GOStencil class provides the expected stencil information. This exercises the "pointwise" name and the stencil description ''' _, invoke_info = parse(os.path.join(BASE_PATH, "test28_invoke_kernel_stencil.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] schedule = invoke.schedule kernel = schedule.children[0].loop_body[0].loop_body[0] # args 1 and 3 specify pointwise as a stencil access for idx in [0, 2]: pointwise_arg = kernel.args[idx] assert pointwise_arg.stencil assert not pointwise_arg.stencil.has_stencil assert pointwise_arg.stencil.name == "go_pointwise" # arg 4 provides grid information so knows nothing about stencils grid_arg = kernel.args[3] with pytest.raises(AttributeError) as excinfo: _ = grid_arg.stencil assert "object has no attribute 'stencil'" in str(excinfo.value) # arg 2 has a stencil stencil_arg = kernel.args[1] assert stencil_arg.stencil.has_stencil for idx2 in range(-1, 2): for idx1 in range(-1, 2): if idx1 in [0, 1] and idx2 == 0: expected_depth = 1 else: expected_depth = 0 assert stencil_arg.stencil.depth(idx1, idx2) == expected_depth assert GOcean1p0Build(tmpdir).code_compiles(psy)
def test_itn_space_any_any_discontinuous(dist_mem, tmpdir): ''' Check that generated loop over cells has correct upper bound when a kernel writes to fields on any_space (continuous) and any_discontinuous_space. ''' _, invoke_info = parse(os.path.join( BASE_PATH, "1.5.3_single_invoke_write_any_anyd_space.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=dist_mem).create(invoke_info) generated_code = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) if dist_mem: output = (" !\n" " DO cell=1,mesh%get_last_halo_cell(1)\n") assert output in generated_code else: output = (" ! Call our kernels\n" " !\n" " DO cell=1,f1_proxy%vspace%get_ncell()\n") assert output in generated_code
def test_no_data_ref_read(parser): ''' Check that we reject code that reads from a derived type. This limitation will be addressed in #1028. ''' reader = FortranStringReader("program dtype_read\n" "use field_mod, only: fld_type\n" "real(kind=wp) :: sto_tmp(5)\n" "integer :: ji\n" "integer, parameter :: jpj = 10\n" "type(fld_type) :: fld\n" "do ji = 1,jpj\n" "sto_tmp(ji) = fld%data(ji) + 1._wp\n" "end do\n" "end program dtype_read\n") code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.invoke_list[0].schedule acc_trans = TransInfo().get_trans_name('ACCDataTrans') acc_trans.apply(schedule.children) with pytest.raises(NotImplementedError) as err: _ = str(psy.gen) assert ("derived-type references on the RHS of assignments are not yet " "supported" in str(err.value))
def test_no_copyin_intrinsics(parser): ''' Check that we don't generate a copyin/out for Fortran instrinsic functions (i.e. we don't mistake them for array accesses). ''' acc_trans = TransInfo().get_trans_name('ACCDataTrans') for intrinsic in [ "cos(ji)", "sin(ji)", "tan(ji)", "atan(ji)", "mod(ji, 5)" ]: reader = FortranStringReader( "program call_intrinsic\n" "integer :: ji, jpj\n" "real(kind=wp) :: sto_tmp(5)\n" "do ji = 1,jpj\n" "sto_tmp(ji) = {0}\n" "end do\n" "end program call_intrinsic\n".format(intrinsic)) code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.invoke_list[0].schedule acc_trans.apply(schedule.children[0:1]) gen_code = str(psy.gen) idx = intrinsic.index("(") assert "copyin({0})".format(intrinsic[0:idx]) not in gen_code.lower()
def test_no_kernels_error(parser): ''' Check that the transformation rejects an attempt to put things that aren't kernels inside a kernels region. ''' reader = FortranStringReader("program write_out\n" "integer :: ji, jpj\n" "real(kind=wp) :: sto_tmp(5)\n" "do ji = 1,jpj\n" "read(*,*) sto_tmp(ji)\n" "end do\n" "do ji = 1,jpj\n" "write(*,*) sto_tmp(ji)\n" "end do\n" "end program write_out\n") code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.invoke_list[0].schedule acc_trans = ACCKernelsTrans() with pytest.raises(TransformationError) as err: _, _ = acc_trans.apply(schedule.children[0:2], {"default_present": True}) assert ("cannot be enclosed by a ACCKernelsTrans transformation" in str(err.value))
def test_kern_load_errors(monkeypatch): ''' Check that the various load methods of the NemoKern class raise the expected errors. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "explicit_do.f90"), api=API, line_length=False) psy = PSyFactory(API, distributed_memory=False).create(invoke_info) invoke = psy.invokes.invoke_list[0] sched = invoke.schedule # The schedule should contain 3 loop objects kerns = sched.kern_calls() with pytest.raises(InternalError) as err: kerns[0].load("Not an fparser2 AST node") assert ("internal error: Expecting either Block_Nonlabel_Do_Construct " "or Assignment_Stmt but got " in str(err)) # TODO why haven't the Kernel or Loop objects got a valid _ast? loop = sched.children[0].children[0].children[0]._ast monkeypatch.setattr(loop, "content", ["not_a_loop"]) with pytest.raises(InternalError) as err: kerns[0]._load_from_loop(loop) assert ("Expecting Nonlabel_Do_Stmt as first child of " "Block_Nonlabel_Do_Construct but got" in str(err))
def test_parallel_repeat_update(parser): ''' Check that calling ACCParallelDirective.update() a 2nd time does not alter the fparser2 parse tree. ''' reader = FortranStringReader(SINGLE_LOOP) code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.invoke_list[0].schedule data_trans = TransInfo().get_trans_name('ACCDataTrans') acc_trans = TransInfo().get_trans_name('ACCParallelTrans') acc_trans.apply(schedule.children[0:1]) data_trans.apply(schedule[0]) accdir = schedule[0].dir_body[0] assert isinstance(accdir, ACCParallelDirective) assert accdir._ast is None # Generate the code in order to trigger the update of the fparser2 tree _ = str(psy.gen) # Store the content of a part of the fparser2 parse tree orig_content = accdir._ast.parent.content[:] # Call update() a second time and then check that nothing has changed accdir.update() for idx, item in enumerate(orig_content): assert item is accdir._ast.parent.content[idx]
def test_user_defined_variables(parser): ''' Test reading and writing to user defined variables. This is not supported atm because the dependence analysis for these PSyIR nodes has not yet been implemented (#1028). Also TODO #1028: is this a duplicate of test_derived_type in tests/psyir/dependency_tools_test.py? ''' reader = FortranStringReader('''program test_prog use some_mod, only: my_type type(my_type) :: a, e integer :: ji, jj, d a%b%c(ji, jj) = d e%f = d end program test_prog''') prog = parser(reader) psy = PSyFactory("nemo", distributed_memory=False).create(prog) loops = psy.invokes.get("test_prog").schedule var_accesses = VariablesAccessInfo(loops) assert var_accesses[Signature("a % b % c")].is_written assert var_accesses[Signature("e % f")].is_written
def test_dynbasisfns_compute(monkeypatch): ''' Check that the DynBasisFunctions._compute_basis_fns() method raises the expected InternalErrors if an unrecognised type or shape of basis function is encountered. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1.1.0_single_invoke_xyoz_qr.f90"), api=API) psy = PSyFactory(API, distributed_memory=False).create(invoke_info) dinf = DynBasisFunctions(psy.invokes.invoke_list[0]) mod = ModuleGen(name="testmodule") # First supply an invalid shape for one of the basis functions dinf._basis_fns[0]["shape"] = "not-a-shape" with pytest.raises(InternalError) as err: dinf._compute_basis_fns(mod) assert ("Unrecognised shape 'not-a-shape' specified for basis function. " "Should be one of: ['gh_quadrature_xyoz', " in str(err.value)) # Now supply an invalid type for one of the basis functions monkeypatch.setattr(dinf, "_basis_fns", [{'type': 'not-a-type'}]) with pytest.raises(InternalError) as err: dinf._compute_basis_fns(mod) assert ("Unrecognised type of basis function: 'not-a-type'. Expected " "one of 'basis' or 'diff-basis'" in str(err.value))
def test_single_node_ompparalleldo_gocean1p0(): ''' Test that applying Extract Transformation on a Node enclosed within an OMP Parallel DO Directive produces the correct result in GOcean1.0 API. ''' from psyclone.transformations import GOceanOMPParallelLoopTrans etrans = GOceanExtractRegionTrans() otrans = GOceanOMPParallelLoopTrans() # Test a Loop nested within the OMP Parallel DO Directive _, invoke_info = parse(os.path.join(GOCEAN_BASE_PATH, "single_invoke_three_kernels.f90"), api=GOCEAN_API) psy = PSyFactory(GOCEAN_API, distributed_memory=False).create(invoke_info) invoke = psy.invokes.invoke_list[0] schedule = invoke.schedule # Apply GOceanOMPParallelLoopTrans to the second Loop schedule, _ = otrans.apply(schedule.children[1]) # Now enclose the parallel region within an ExtractNode (inserted # at the previous location of the OMPParallelDoDirective schedule, _ = etrans.apply(schedule.children[1]) code = str(psy.gen) output = (" ! ExtractStart\n" " ! CALL write_extract_arguments(argument_list)\n" " !\n" " !$omp parallel do default(shared), private(i,j), " "schedule(static)\n" " DO j=2,jstop+1\n" " DO i=2,istop\n" " CALL compute_cv_code(i, j, cv_fld%data, " "p_fld%data, v_fld%data)\n" " END DO \n" " END DO \n" " !$omp end parallel do\n" " !\n" " ! ExtractEnd\n") assert output in code
def test_sched_getitem(): '''Test that Schedule has the [int] operator overloaded to return the given index child''' _, invoke_info = parse(os.path.join(BASE_PATH, "15.9.1_X_innerproduct_Y_builtin.f90"), api="dynamo0.3") psy = PSyFactory("dynamo0.3", distributed_memory=True).create(invoke_info) sched = psy.invokes.invoke_list[0].schedule for indx in range(len(sched._children)): assert sched[indx] is sched._children[indx] # Test range indexing children = sched[:] assert len(children) == 2 assert children[0] is sched._children[0] assert children[1] is sched._children[1] # Test index out-of-bounds Error with pytest.raises(IndexError) as err: _ = sched[len(sched._children)] assert "list index out of range" in str(err.value)
def test_node_dag_wrong_file_format(monkeypatch): ''' Test the handling of the error raised by graphviz when it is passed an invalid file format. We make this test independent of whether or not graphviz is actually available by monkeypatching the psyir.nodes.node._graphviz_digraph_class function to return a fake digraph class type that mimics the error. ''' class FakeDigraph(object): ''' Fake version of graphviz.Digraph class that raises a ValueError when instantiated. ''' # pylint: disable=redefined-builtin def __init__(self, format=None): raise ValueError(format) monkeypatch.setattr(node, "_graphviz_digraph_class", lambda: FakeDigraph) _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), api="dynamo0.3") psy = PSyFactory("dynamo0.3", distributed_memory=False).create(invoke_info) invoke = psy.invokes.invoke_list[0] with pytest.raises(GenerationError) as err: invoke.schedule.dag() assert "unsupported graphviz file format 'svg' provided" in str(err.value)
def test_data_ref(parser): '''Check code generation with an array accessed via a derived type. ''' reader = FortranStringReader('''subroutine data_ref() use some_mod, only: prof_type INTEGER, parameter :: n=16 INTEGER :: ji real :: a(n), fconst type(prof_type) :: prof do ji = 1, n prof%npind(ji) = 2.0*a(ji) + fconst end do END subroutine data_ref ''') code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.invoke_list[0].schedule acc_trans = TransInfo().get_trans_name('ACCDataTrans') acc_trans.apply(schedule.children) gen_code = str(psy.gen) assert "!$ACC DATA COPYIN(a) COPYOUT(prof,prof%npind)" in gen_code
def test_loop_gen_code(): ''' Check that the Loop gen_code method prints the proper loop ''' base_path = os.path.join( os.path.dirname( os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "test_files", "dynamo0p3") _, invoke_info = parse(os.path.join(base_path, "1.0.1_single_named_invoke.f90"), api="dynamo0.3") psy = PSyFactory("dynamo0.3", distributed_memory=True).create(invoke_info) # By default DynLoop has step = 1 and it is not printed in the Fortran DO gen = str(psy.gen) assert "DO cell=1,mesh%get_last_halo_cell(1)" in gen # Change step to 2 loop = psy.invokes.get('invoke_important_invoke').schedule[4] loop.step_expr = Literal("2", INTEGER_SINGLE_TYPE, parent=loop) # Now it is printed in the Fortran DO with the expression ",2" at the end gen = str(psy.gen) assert "DO cell=1,mesh%get_last_halo_cell(1),2" in gen
def test_kernels_around_where_construct(parser): ''' Check that we can put a WHERE construct inside a KERNELS region. ''' from psyclone.psyGen import Loop, ACCKernelsDirective reader = FortranStringReader("program where_test\n" " integer :: flag\n" " real :: a(:,:), b(:,:)\n" " where (a(:,:) < flag)\n" " b(:,:) = 0.0\n" " end where\n" "end program where_test\n") code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.invoke_list[0].schedule acc_trans = ACCKernelsTrans() sched, _ = acc_trans.apply(schedule) assert isinstance(sched[0], ACCKernelsDirective) assert isinstance(sched[0].dir_body[0], Loop) new_code = str(psy.gen) assert (" !$ACC KERNELS\n" " WHERE (a(:, :) < flag)" in new_code) assert (" END WHERE\n" " !$ACC END KERNELS\n" in new_code)
def test_lfricfields_call_err(): ''' Check that the LFRicFields constructor raises the expected internal error if it encounters an unrecognised intrinsic type of a field argument when generating a kernel call. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1.5_single_invoke_fs.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) invoke = psy.invokes.invoke_list[0] kernel = invoke.schedule.coded_kernels()[0] # Sabotage the field argument to make it have an invalid intrinsic type fld_arg = kernel.arguments.args[0] fld_arg._intrinsic_type = "triple-type" with pytest.raises(InternalError) as err: LFRicFields(invoke)._invoke_declarations(ModuleGen(name="my_mod")) test_str = str(err.value) if six.PY2: test_str = test_str.replace("u'", "'") assert ("Found unsupported intrinsic types for the field arguments " "['f1'] to Invoke 'invoke_0_testkern_fs_type'. Supported " "types are ['real', 'integer']." in test_str)
def test_scalar_invoke_uniq_declns_valid_intrinsic(): ''' Tests that all valid intrinsic types for user-defined scalar arguments ('real' and 'integer') are accepted by Invoke.unique_declarations(). ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1.7_single_invoke_2scalar.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=False).create(invoke_info) invoke = psy.invokes.invoke_list[0] # Test 'real' scalars scalars_real_args = invoke.unique_declarations( LFRicArgDescriptor.VALID_SCALAR_NAMES, intrinsic_type="real") scalars_real = [arg.declaration_name for arg in scalars_real_args] assert scalars_real == ["a"] # Test 'integer' scalars scalars_int_args = invoke.unique_declarations( LFRicArgDescriptor.VALID_SCALAR_NAMES, intrinsic_type="integer") scalars_int = [arg.declaration_name for arg in scalars_int_args] assert scalars_int == ["istep"]
def test_dynbasisfns_initialise(monkeypatch): ''' Check that the DynBasisFunctions.initialise() method raises the expected InternalErrors. ''' from psyclone.f2pygen import ModuleGen _, invoke_info = parse(os.path.join(BASE_PATH, "1.1.0_single_invoke_xyoz_qr.f90"), api=API) psy = PSyFactory(API, distributed_memory=False).create(invoke_info) dinf = DynBasisFunctions(psy.invokes.invoke_list[0]) mod = ModuleGen(name="testmodule") # Break the shape of the first basis function dinf._basis_fns[0]["shape"] = "not-a-shape" with pytest.raises(InternalError) as err: dinf.initialise(mod) assert ("Unrecognised evaluator shape: 'not-a-shape'. Should be " "one of " in str(err)) # Break the internal list of basis functions monkeypatch.setattr(dinf, "_basis_fns", [{'type': 'not-a-type'}]) with pytest.raises(InternalError) as err: dinf.initialise(mod) assert ("Unrecognised type of basis function: 'not-a-type'. Should be " "either 'basis' or 'diff-basis'" in str(err))
def test_dynloop_load_unexpected_func_space(): ''' The load function of an instance of the DynLoop class raises an error if an unexpected function space is found. This test makes sure this error works correctly. It's a little tricky to raise this error as it is unreachable. However, we can sabotage an earlier function to make it return an invalid value. ''' # first create a working instance of the DynLoop class _, invoke_info = parse(os.path.join(BASE_PATH, "19.1_single_stencil.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) # now get access to the DynLoop class, the associated kernel class # and the associated field. schedule = psy.invokes.invoke_list[0].schedule loop = schedule.children[4] kernel = loop.loop_body[0] field = kernel.arguments.iteration_space_arg() # break the fields function space field._function_spaces[0]._orig_name = "broken" # create a function which always returns the broken field def broken_func(): ''' Returns the above field no matter what ''' return field # Replace the iteration_space_arg method with our broke # function. This is required as iteration_space_arg currently # never returns a field with an invalid function space. kernel.arguments.iteration_space_arg = broken_func # We can now raise the exception. with pytest.raises(GenerationError) as err: loop.load(kernel) const = LFRicConstants() assert ("Generation Error: Unexpected function space found. Expecting " "one of " + str(const.VALID_FUNCTION_SPACES) + " but found 'broken'" in str(err.value))
def test_quad_rule_edge(): '''Test that the KernelInterface class quad_rule method adds the expected classes to the symbol table and the _arglist list for edge quadrature ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1.1.5_edge_qr.f90"), api="dynamo0.3") psy = PSyFactory("dynamo0.3", distributed_memory=False).create(invoke_info) schedule = psy.invokes.invoke_list[0].schedule kernel = schedule[0].loop_body[0] kernel_interface = KernelInterface(kernel) kernel_interface.quad_rule() # nedges declared and added to argument list nedges_symbol = kernel_interface._symbol_table.lookup("nedges") assert isinstance(nedges_symbol, lfric_psyir.NumberOfEdgesDataSymbol) assert isinstance(nedges_symbol.interface, ArgumentInterface) assert ( nedges_symbol.interface.access == kernel_interface._read_access.access) assert kernel_interface._arglist[-3] is nedges_symbol # nqp declared and added to argument list nqp_symbol = kernel_interface._symbol_table.lookup("nqp_edges") assert isinstance(nqp_symbol, lfric_psyir.NumberOfQrPointsInEdgesDataSymbol) assert isinstance(nqp_symbol.interface, ArgumentInterface) assert ( nqp_symbol.interface.access == kernel_interface._read_access.access) assert kernel_interface._arglist[-2] is nqp_symbol # weights declared and added to argument list weights_symbol = kernel_interface._symbol_table.lookup("weights_edges") assert isinstance(weights_symbol, lfric_psyir.QrWeightsInEdgesDataSymbol) assert isinstance(weights_symbol.interface, ArgumentInterface) assert (weights_symbol.interface.access == kernel_interface._read_access.access) assert kernel_interface._arglist[-1] is weights_symbol assert len(weights_symbol.shape) == 1 assert isinstance(weights_symbol.shape[0], Reference) assert weights_symbol.shape[0].symbol is nqp_symbol
def test_goschedule_view(capsys): ''' Test that the GOSchedule::view() method works as expected ''' from psyclone.psyGen import colored, SCHEDULE_COLOUR_MAP _, invoke_info = parse(os.path.join( os.path.dirname(os.path.abspath(__file__)), "test_files", "gocean1p0", "single_invoke_two_kernels.f90"), api=API) psy = PSyFactory(API).create(invoke_info) invoke = psy.invokes.invoke_list[0] invoke.schedule.view() # The view method writes to stdout and this is captured by py.test # by default. We have to query this captured output. out, _ = capsys.readouterr() # Ensure we check for the correct (colour) control codes in the output sched = colored("GOSchedule", SCHEDULE_COLOUR_MAP["Schedule"]) loop = colored("Loop", SCHEDULE_COLOUR_MAP["Loop"]) call = colored("KernCall", SCHEDULE_COLOUR_MAP["KernCall"]) expected_output = (sched + "[invoke='invoke_0',Constant loop bounds=True]\n" " " + loop + "[type='outer',field_space='cu'," "it_space='internal_pts']\n" " " + loop + "[type='inner',field_space='cu'," "it_space='internal_pts']\n" " " + call + " compute_cu_code(cu_fld,p_fld,u_fld) " "[module_inline=False]\n" " " + loop + "[type='outer',field_space='every'," "it_space='internal_pts']\n" " " + loop + "[type='inner',field_space='every'," "it_space='internal_pts']\n" " " + call + " time_smooth_code(u_fld,unew_fld,uold_fld) " "[module_inline=False]") assert expected_output in out
def test_ne_offset_cf_points(): ''' Test that we can generate code for a kernel that expects a NE offset and writes to a field on CF points ''' _, invoke_info = parse(os.path.join( os.path.dirname(os.path.abspath(__file__)), "test_files", "gocean1p0", "test14_ne_offset_cf_updated_one_invoke.f90"), api=API) psy = PSyFactory(API).create(invoke_info) generated_code = str(psy.gen) expected_output = ( " MODULE psy_single_invoke_test\n" " USE field_mod\n" " USE kind_params_mod\n" " IMPLICIT NONE\n" " CONTAINS\n" " SUBROUTINE invoke_0_compute_vort(vort_fld, p_fld, u_fld, v_fld)\n" " USE kernel_ne_offset_cf_mod, ONLY: compute_vort_code\n" " TYPE(r2d_field), intent(inout) :: vort_fld, p_fld, u_fld, " "v_fld\n" " INTEGER j\n" " INTEGER i\n" " INTEGER istop, jstop\n" " !\n" " ! Look-up loop bounds\n" " istop = vort_fld%grid%simulation_domain%xstop\n" " jstop = vort_fld%grid%simulation_domain%ystop\n" " !\n" " DO j=1,jstop-1\n" " DO i=1,istop-1\n" " CALL compute_vort_code(i, j, vort_fld%data, p_fld%data, " "u_fld%data, v_fld%data)\n" " END DO \n" " END DO \n" " END SUBROUTINE invoke_0_compute_vort\n" " END MODULE psy_single_invoke_test") assert generated_code.find(expected_output) != -1
def test_scalar_float_arg(): ''' Tests that an invoke containing a kernel call requiring a real, scalar argument produces correct code ''' _, invoke_info = parse(os.path.join( os.path.dirname(os.path.abspath(__file__)), "test_files", "gocean1p0", "single_invoke_scalar_float_arg.f90"), api=API) psy = PSyFactory(API).create(invoke_info) generated_code = str(psy.gen) expected_output = ( " MODULE psy_single_invoke_scalar_float_test\n" " USE field_mod\n" " USE kind_params_mod\n" " IMPLICIT NONE\n" " CONTAINS\n" " SUBROUTINE invoke_0_bc_ssh(a_scalar, ssh_fld)\n" " USE kernel_scalar_float, ONLY: bc_ssh_code\n" " TYPE(r2d_field), intent(inout) :: ssh_fld\n" " REAL(KIND=wp), intent(inout) :: a_scalar\n" " INTEGER j\n" " INTEGER i\n" " INTEGER istop, jstop\n" " !\n" " ! Look-up loop bounds\n" " istop = ssh_fld%grid%simulation_domain%xstop\n" " jstop = ssh_fld%grid%simulation_domain%ystop\n" " !\n" " DO j=1,jstop+1\n" " DO i=1,istop+1\n" " CALL bc_ssh_code(i, j, a_scalar, ssh_fld%data, " "ssh_fld%grid%tmask)\n" " END DO \n" " END DO \n" " END SUBROUTINE invoke_0_bc_ssh\n" " END MODULE psy_single_invoke_scalar_float_test") assert generated_code.find(expected_output) != -1
def test_openmp_loop_trans(): ''' test of the OpenMP transformation of an all-points loop ''' _, info = parse(os.path.join(os.path. dirname(os.path.abspath(__file__)), "test_files", "gocean0p1", "openmp_fuse_test.f90"), api=API) psy = PSyFactory(API).create(info) invokes = psy.invokes invoke = invokes.get('invoke_0') schedule = invoke.schedule ompf = GOceanOMPParallelLoopTrans() omp1_schedule, _ = ompf.apply(schedule.children[0]) # Replace the original loop schedule with the transformed one psy.invokes.get('invoke_0').schedule = omp1_schedule # Store the results of applying this code transformation as # a string gen = str(psy.gen) omp_do_idx = -1 # Iterate over the lines of generated code for idx, line in enumerate(gen.split('\n')): if '!$omp parallel do' in line: omp_do_idx = idx if 'DO j=' in line: outer_do_idx = idx if 'DO i=' in line: inner_do_idx = idx if omp_do_idx > -1: break # The OpenMP 'parallel do' directive must occur immediately before # the DO loop itself assert outer_do_idx-omp_do_idx == 1 and\ inner_do_idx-outer_do_idx == 1
def test_explicit_loop(parser): ''' Check that we can apply the transformation to an explicit loop. ''' reader = FortranStringReader("program do_loop\n" "integer :: ji\n" "integer, parameter :: jpj=13\n" "real :: sto_tmp(jpj), sto_tmp2(jpj)\n" "do ji = 1,jpj\n" " sto_tmp(ji) = 1.0d0\n" "end do\n" "do ji = 1,jpj\n" " sto_tmp2(ji) = 1.0d0\n" "end do\n" "end program do_loop\n") code = parser(reader) psy = PSyFactory(API, distributed_memory=False).create(code) schedule = psy.invokes.invoke_list[0].schedule acc_trans = TransInfo().get_trans_name('ACCLoopTrans') para_trans = TransInfo().get_trans_name('ACCParallelTrans') para_trans.apply(schedule.children) schedule, _ = acc_trans.apply(schedule[0].dir_body[0]) schedule, _ = acc_trans.apply(schedule[0].dir_body[1], {"independent": False}) code = str(psy.gen) assert ("PROGRAM do_loop\n" " INTEGER :: ji\n" " INTEGER, PARAMETER :: jpj = 13\n" " REAL :: sto_tmp(jpj), sto_tmp2(jpj)\n" " !$ACC PARALLEL\n" " !$ACC LOOP INDEPENDENT\n" " DO ji = 1, jpj\n" " sto_tmp(ji) = 1.0D0\n" " END DO\n" " !$ACC LOOP\n" " DO ji = 1, jpj\n" " sto_tmp2(ji) = 1.0D0\n" " END DO\n" " !$ACC END PARALLEL\n" "END PROGRAM do_loop" in code)