def test_mesh_gen(tmpdir): ''' Basic test for code-generation for an invoke containing a single kernel requiring reference-element properties. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "24.1_mesh_prop_invoke.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=False).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen = str(psy.gen).lower() # In order to provide the mesh property we need the reference element assert "use reference_element_mod, only: reference_element_type" in gen assert "integer(kind=i_def) nfaces_re_h" in gen assert ("integer(kind=i_def), pointer :: adjacent_face(:,:) => null()" in gen) assert ("class(reference_element_type), pointer :: reference_element " "=> null()" in gen) # We need a mesh object in order to get a reference_element object assert "mesh => f1_proxy%vspace%get_mesh()" in gen assert "reference_element => mesh%get_reference_element()" in gen assert ("nfaces_re_h = reference_element%get_number_horizontal_faces()" in gen) assert "adjacent_face => mesh%get_adjacent_face()" in gen assert "nfaces_re_v" not in gen # The kernel call assert ("call testkern_mesh_prop_code(nlayers, a, f1_proxy%data, " "ndf_w1, undf_w1, map_w1(:,cell), nfaces_re_h, " "adjacent_face(:,cell))" in gen)
def test_prolong_vector(tmpdir): ''' Check that we generate correct code when an inter-grid kernel takes a field vector as argument ''' _, invoke_info = parse(os.path.join(BASE_PATH, "22.4_intergrid_prolong_vec.f90"), api=API) psy = PSyFactory(API, distributed_memory=True).create(invoke_info) output = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) assert "TYPE(field_type), intent(in) :: field1(3)" in output assert "TYPE(field_proxy_type) field1_proxy(3)" in output # Make sure we always index into the field arrays assert " field1%" not in output assert " field2%" not in output assert ("ncpc_field1_field2, ncell_field1, field1_proxy(1)%data, " "field1_proxy(2)%data, field1_proxy(3)%data, field2_proxy(1)%data," " field2_proxy(2)%data, field2_proxy(3)%data, ndf_w1" in output) for idx in [1, 2, 3]: assert (" IF (field2_proxy({0})%is_dirty(depth=1)) THEN\n" " CALL field2_proxy({0})%halo_exchange(depth=1)\n" " END IF\n".format(idx) in output) assert ("field1_proxy({0}) = field1({0})%get_proxy()".format(idx) in output) assert "CALL field1_proxy({0})%set_dirty()".format(idx) in output assert "CALL field1_proxy({0})%set_clean(1)".format(idx) in output
def test_op_orient_different_space(tmpdir): ''' Tests that an operator on different spaces requiring orientation information is implemented correctly in the PSy layer. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "10.4_operator_orient_different_" "space.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) gen_str = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) assert ( "INTEGER(KIND=i_def), pointer :: orientation_w1(:) => null(), " "orientation_w2(:) => null()" in gen_str) assert "ndf_w2 = my_mapping_proxy%fs_from%get_ndf()" in gen_str assert "ndf_w1 = my_mapping_proxy%fs_to%get_ndf()" in gen_str assert "dim_w1 = my_mapping_proxy%fs_to%get_dim_space()" in gen_str assert ("CALL qr%compute_function(BASIS, my_mapping_proxy%fs_to, " "dim_w1, ndf_w1, basis_w1_qr)" in gen_str) assert ( "orientation_w2 => my_mapping_proxy%fs_from%get_cell_orientation(" "cell)" in gen_str) assert ( "orientation_w1 => my_mapping_proxy%fs_to%get_cell_orientation(cell)" in gen_str) assert ("(cell, nlayers, my_mapping_proxy%ncell_3d, " "my_mapping_proxy%local_stencil, coord_proxy(1)%data, " "coord_proxy(2)%data, coord_proxy(3)%data, ndf_w1, basis_w1_qr, " "orientation_w1, ndf_w2, orientation_w2, ndf_w0, undf_w0, " "map_w0(:,cell), diff_basis_w0_qr, np_xy_qr, np_z_qr, " "weights_xy_qr, weights_z_qr)" in gen_str)
def test_psy_gen_domain_two_kernel(dist_mem, tmpdir): ''' Check the generation of the PSy layer for an invoke consisting of a kernel with operates_on=domain and another with operates_on=cell_column. ''' _, info = parse(os.path.join(BASE_PATH, "25.1_2kern_domain.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=dist_mem).create(info) gen_code = str(psy.gen).lower() assert "mesh => f2_proxy%vspace%get_mesh()" in gen_code assert "integer(kind=i_def) ncell_2d" in gen_code expected = (" end do\n" " !\n") if dist_mem: expected += ( " ! set halos dirty/clean for fields modified in the above " "loop\n" " !\n" " call f2_proxy%set_dirty()\n" " !\n" " !\n") expected += ( " call testkern_domain_code(nlayers, ncell_2d, b, f1_proxy%data, " "ndf_w3, undf_w3, map_w3)\n") assert expected in gen_code if dist_mem: assert (" ! set halos dirty/clean for fields modified in the " "above kernel\n" " !\n" " call f1_proxy%set_dirty()\n" in gen_code) assert LFRicBuild(tmpdir).code_compiles(psy)
def test_operator_nofield_scalar_deref(tmpdir, dist_mem): ''' Tests that an operator with no field and a scalar argument is implemented correctly in the PSy layer when both are obtained by dereferencing derived type objects. ''' _, invoke_info = parse(os.path.join( BASE_PATH, "10.6.1_operator_no_field_scalar_deref.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=dist_mem).create(invoke_info) gen = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) if dist_mem: assert "mesh => opbox_my_mapping_proxy%fs_from%get_mesh()" in gen assert "nlayers = opbox_my_mapping_proxy%fs_from%get_nlayers()" in gen assert "ndf_w2 = opbox_my_mapping_proxy%fs_from%get_ndf()" in gen assert ("qr_init_quadrature_symmetrical%compute_function(BASIS, " "opbox_my_mapping_proxy%fs_from, dim_w2, ndf_w2, " "basis_w2_qr_init_quadrature_symmetrical)" in gen) if dist_mem: assert "DO cell=1,mesh%get_last_halo_cell(1)" in gen else: assert ("DO cell=1,opbox_my_mapping_proxy%fs_from%get_ncell()" in gen) assert ("(cell, nlayers, opbox_my_mapping_proxy%ncell_3d, " "opbox_my_mapping_proxy%local_stencil, box_b, ndf_w2, " "basis_w2_qr_init_quadrature_symmetrical, " "np_xy_qr_init_quadrature_symmetrical, " "np_z_qr_init_quadrature_symmetrical, " "weights_xy_qr_init_quadrature_symmetrical, " "weights_z_qr_init_quadrature_symmetrical)" in gen)
def test_operator_nofield(tmpdir): ''' Tests that an operator with no field on the same space is implemented correctly in the PSy layer. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "10.1_operator_nofield.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) gen_code_str = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) assert ( "SUBROUTINE invoke_0_testkern_operator_nofield_type(mm_w2, coord, qr)" in gen_code_str) assert "TYPE(operator_type), intent(in) :: mm_w2" in gen_code_str assert "TYPE(operator_proxy_type) mm_w2_proxy" in gen_code_str assert "mm_w2_proxy = mm_w2%get_proxy()" in gen_code_str assert "undf_w2" not in gen_code_str assert "map_w2" not in gen_code_str assert ("CALL testkern_operator_nofield_code(cell, nlayers, " "mm_w2_proxy%ncell_3d, mm_w2_proxy%local_stencil, " "coord_proxy(1)%data, coord_proxy(2)%data, coord_proxy(3)%data, " "ndf_w2, basis_w2_qr, ndf_w0, undf_w0, " "map_w0(:,cell), diff_basis_w0_qr, np_xy_qr, np_z_qr, " "weights_xy_qr, weights_z_qr)" in gen_code_str)
def test_union_refelem_gen(tmpdir): ''' Check that code generation works for an invoke with kernels that only have a sub-set of reference-element properties in common. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "23.3_shared_ref_elem_invoke.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=False).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen = str(psy.gen).lower() assert ( " reference_element => mesh%get_reference_element()\n" " nfaces_re_h = reference_element%get_number_horizontal_faces()\n" " nfaces_re_v = reference_element%get_number_vertical_faces()\n" " call reference_element%get_normals_to_horizontal_faces(" "normals_to_horiz_faces)\n" " call reference_element%get_outward_normals_to_horizontal_faces(" "out_normals_to_horiz_faces)\n" " call reference_element%get_normals_to_vertical_faces(" "normals_to_vert_faces)\n" " call reference_element%get_outward_normals_to_vertical_faces(" "out_normals_to_vert_faces)\n" in gen) assert ("call testkern_ref_elem_code(nlayers, a, f1_proxy%data, " "f2_proxy%data, m1_proxy%data, m2_proxy%data, ndf_w1, undf_w1, " "map_w1(:,cell), ndf_w2, undf_w2, map_w2(:,cell), ndf_w3, undf_w3," " map_w3(:,cell), nfaces_re_h, nfaces_re_v, " "normals_to_horiz_faces, normals_to_vert_faces)" in gen) assert ("call testkern_ref_elem_out_code(nlayers, a, f3_proxy%data, " "f4_proxy%data, m3_proxy%data, m4_proxy%data, ndf_w1, undf_w1, " "map_w1(:,cell), ndf_w2, undf_w2, map_w2(:,cell), ndf_w3, undf_w3," " map_w3(:,cell), nfaces_re_v, nfaces_re_h, " "out_normals_to_vert_faces, normals_to_vert_faces, " "out_normals_to_horiz_faces)" in gen)
def test_edge_qr(tmpdir, dist_mem): ''' Check that we generate correct code when a kernel specifies that it requires edge quadrature. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1.1.5_edge_qr.f90"), api=API) psy = PSyFactory(API, distributed_memory=dist_mem).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen_code = str(psy.gen).lower() assert ("use quadrature_edge_mod, only: quadrature_edge_type, " "quadrature_edge_proxy_type\n" in gen_code) assert "type(quadrature_edge_type), intent(in) :: qr\n" in gen_code assert "integer(kind=i_def) np_xyz_qr, nedges_qr" in gen_code assert (" qr_proxy = qr%get_quadrature_proxy()\n" " np_xyz_qr = qr_proxy%np_xyz\n" " nedges_qr = qr_proxy%nedges\n" " weights_xyz_qr => qr_proxy%weights_xyz\n" in gen_code) assert (" ! compute basis/diff-basis arrays\n" " !\n" " call qr%compute_function(basis, f1_proxy%vspace, dim_w1, " "ndf_w1, basis_w1_qr)\n" " call qr%compute_function(diff_basis, f2_proxy%vspace, " "diff_dim_w2, ndf_w2, diff_basis_w2_qr)\n" " call qr%compute_function(basis, m2_proxy%vspace, dim_w3, " "ndf_w3, basis_w3_qr)\n" " call qr%compute_function(diff_basis, m2_proxy%vspace, " "diff_dim_w3, ndf_w3, diff_basis_w3_qr)\n" in gen_code) assert ("call testkern_qr_edges_code(nlayers, f1_proxy%data, " "f2_proxy%data, m1_proxy%data, a, m2_proxy%data, istp, " "ndf_w1, undf_w1, map_w1(:,cell), basis_w1_qr, ndf_w2, undf_w2, " "map_w2(:,cell), diff_basis_w2_qr, ndf_w3, undf_w3, " "map_w3(:,cell), basis_w3_qr, diff_basis_w3_qr, nedges_qr, " "np_xyz_qr, weights_xyz_qr)" in gen_code)
def test_duplicate_refelem_gen(tmpdir): ''' Test for code-generation for an invoke containing two kernels that require the same properties of the reference-element. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "23.2_multi_ref_elem_invoke.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=False).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen = str(psy.gen).lower() assert gen.count( "real(kind=r_def), allocatable :: normals_to_horiz_faces(:,:)" ", normals_to_vert_faces(:,:)") == 1 assert gen.count( "reference_element => mesh%get_reference_element") == 1 assert gen.count( "nfaces_re_h = reference_element%get_number_horizontal_faces()") == 1 assert gen.count( "nfaces_re_v = reference_element%get_number_vertical_faces()") == 1 assert gen.count("call reference_element%get_normals_to_horizontal_faces(" "normals_to_horiz_faces)") == 1 assert gen.count("call reference_element%get_normals_to_vertical_faces(" "normals_to_vert_faces)") == 1 assert ("call testkern_ref_elem_code(nlayers, a, f1_proxy%data, " "f2_proxy%data, m1_proxy%data, m2_proxy%data, ndf_w1, undf_w1, " "map_w1(:,cell), ndf_w2, undf_w2, map_w2(:,cell), ndf_w3, " "undf_w3, map_w3(:,cell), nfaces_re_h, nfaces_re_v, " "normals_to_horiz_faces, normals_to_vert_faces)" in gen) assert ("call testkern_ref_elem_code(nlayers, a, f3_proxy%data, " "f4_proxy%data, m3_proxy%data, m4_proxy%data, ndf_w1, undf_w1, " "map_w1(:,cell), ndf_w2, undf_w2, map_w2(:,cell), ndf_w3, " "undf_w3, map_w3(:,cell), nfaces_re_h, nfaces_re_v, " "normals_to_horiz_faces, normals_to_vert_faces)" in gen)
def test_refelem_gen(tmpdir): ''' Basic test for code-generation for an invoke containing a single kernel requiring reference-element properties. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "23.1_ref_elem_invoke.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=False).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen = str(psy.gen).lower() assert "use reference_element_mod, only: reference_element_type" in gen assert "integer(kind=i_def) nfaces_re_h, nfaces_re_v" in gen assert ("real(kind=r_def), allocatable :: normals_to_horiz_faces(:,:), " "normals_to_vert_faces(:,:)" in gen) assert ("class(reference_element_type), pointer :: reference_element " "=> null()" in gen) # We need a mesh object in order to get a reference_element object assert "mesh => f1_proxy%vspace%get_mesh()" in gen assert "reference_element => mesh%get_reference_element()" in gen assert ("nfaces_re_h = reference_element%get_number_horizontal_faces()" in gen) assert "nfaces_re_v = reference_element%get_number_vertical_faces()" in gen assert ("call reference_element%get_normals_to_horizontal_faces(" "normals_to_horiz_faces)" in gen) assert ("call reference_element%get_normals_to_vertical_faces(" "normals_to_vert_faces)" in gen) # The kernel call assert ("call testkern_ref_elem_code(nlayers, a, f1_proxy%data, " "f2_proxy%data, m1_proxy%data, m2_proxy%data, ndf_w1, undf_w1, " "map_w1(:,cell), ndf_w2, undf_w2, map_w2(:,cell), ndf_w3, " "undf_w3, map_w3(:,cell), nfaces_re_h, nfaces_re_v, " "normals_to_horiz_faces, normals_to_vert_faces)" in gen)
def test_halo_for_discontinuous_2(tmpdir, monkeypatch, annexed): '''This test checks the case when our loop iterates over owned cells (e.g. it writes to a discontinuous field), we read from a continuous field, there are no stencil accesses, and the previous writer iterates over ndofs or nannexed. When the previous writer iterates over ndofs we have dirty annexed dofs so need to add a halo exchange. This is the case when api_config.compute_annexed_dofs is False. When the previous writer iterates over nannexed we have clean annexed dofs so do not need to add a halo exchange. This is the case when api_config.compute_annexed_dofs is True ''' api_config = Config.get().api_conf(TEST_API) monkeypatch.setattr(api_config, "_compute_annexed_dofs", annexed) _, info = parse(os.path.join(BASE_PATH, "14.7_halo_annexed.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(info) result = str(psy.gen) if annexed: assert "halo_exchange" not in result else: assert "IF (f1_proxy%is_dirty(depth=1)) THEN" not in result assert "CALL f1_proxy%halo_exchange(depth=1)" in result assert "IF (f2_proxy%is_dirty(depth=1)) THEN" not in result assert "CALL f2_proxy%halo_exchange(depth=1)" in result assert "IF (m1_proxy%is_dirty(depth=1)) THEN" in result assert "CALL m1_proxy%halo_exchange(depth=1)" in result assert LFRicBuild(tmpdir).code_compiles(psy)
def test_setval_x_then_user(tmpdir, monkeypatch): ''' Check that the correct halo exchanges are added if redundant computation is enabled for a built-in kernel called before a user-supplied kernel. ''' api_config = Config.get().api_conf(API) monkeypatch.setattr(api_config, "_compute_annexed_dofs", True) _, invoke_info = parse(os.path.join( BASE_PATH, "15.7.3_setval_X_before_user_kern.f90"), api=API) psy = PSyFactory(API, distributed_memory=True).create(invoke_info) first_invoke = psy.invokes.invoke_list[0] # Since (redundant) computation over annexed dofs is enabled, there # should be no halo exchange before the first (builtin) kernel call assert isinstance(first_invoke.schedule[0], DynLoop) # There should be a halo exchange for field f1 before the second # kernel call assert isinstance(first_invoke.schedule[1], DynHaloExchange) assert first_invoke.schedule[1].field.name == "f1" # Now transform the first loop to perform redundant computation out to # the level-1 halo rtrans = Dynamo0p3RedundantComputationTrans() _, _ = rtrans.apply(first_invoke.schedule[0], options={"depth": 1}) # There should now be a halo exchange for f1 before the first # (builtin) kernel call assert isinstance(first_invoke.schedule[0], DynHaloExchange) assert first_invoke.schedule[0].field.name == "f1" assert isinstance(first_invoke.schedule[1], DynLoop) # There should only be one halo exchange for field f1 assert len([ node for node in first_invoke.schedule.walk(DynHaloExchange) if node.field.name == "f1" ]) == 1 assert LFRicBuild(tmpdir).code_compiles(psy)
def test_psy_gen_domain_kernel(dist_mem, tmpdir): ''' Check the generation of the PSy layer for an invoke consisting of a single kernel with operates_on=domain. ''' _, info = parse(os.path.join(BASE_PATH, "25.0_domain.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=dist_mem).create(info) gen_code = str(psy.gen).lower() # A domain kernel needs the number of columns in the mesh. Therefore # we require a mesh object. assert "type(mesh_type), pointer :: mesh => null()" in gen_code assert "mesh => f1_proxy%vspace%get_mesh()" in gen_code assert "integer(kind=i_def) ncell_2d" in gen_code assert "ncell_2d = mesh%get_ncells_2d()" in gen_code # Kernel call should include whole dofmap and not be within a loop if dist_mem: expected = " ! call kernels and communication routines\n" else: expected = " ! call our kernels\n" assert (expected + " !\n" " !\n" " call testkern_domain_code(nlayers, ncell_2d, b, " "f1_proxy%data, ndf_w3, undf_w3, map_w3)" in gen_code) assert LFRicBuild(tmpdir).code_compiles(psy)
def test_operator_orientation(tmpdir): ''' Tests that an operator requiring orientation information is implemented correctly in the PSy layer. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "10.2_operator_orient.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) gen_str = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) assert ( "SUBROUTINE invoke_0_testkern_operator_orient_type(mm_w1, coord, qr)" in gen_str) assert "TYPE(operator_type), intent(in) :: mm_w1" in gen_str assert "TYPE(operator_proxy_type) mm_w1_proxy" in gen_str assert "mm_w1_proxy = mm_w1%get_proxy()" in gen_str assert ( "orientation_w1 => mm_w1_proxy%fs_from%get_cell_orientation" "(cell)" in gen_str) assert ("CALL testkern_operator_orient_code(cell, nlayers, " "mm_w1_proxy%ncell_3d, mm_w1_proxy%local_stencil, " "coord_proxy(1)%data, coord_proxy(2)%data, coord_proxy(3)%data, " "ndf_w1, basis_w1_qr, orientation_w1, ndf_w0, undf_w0, " "map_w0(:,cell), diff_basis_w0_qr, np_xy_qr, np_z_qr, " "weights_xy_qr, weights_z_qr)" in gen_str)
def test_mesh_plus_face_quad_gen(tmpdir): ''' Test that we generate correct code when a kernel requires both a mesh property and face quadrature. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "24.4_mesh_plus_face_qr_invoke.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=False).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen = str(psy.gen).lower() assert (" qr_proxy = qr%get_quadrature_proxy()\n" " np_xyz_qr = qr_proxy%np_xyz\n" " nfaces_qr = qr_proxy%nfaces\n" " weights_xyz_qr => qr_proxy%weights_xyz\n" " !\n" " ! allocate basis/diff-basis arrays\n" " !\n" " dim_w1 = f1_proxy%vspace%get_dim_space()\n" " allocate (basis_w1_qr(dim_w1, ndf_w1, np_xyz_qr, " "nfaces_qr))" in gen) assert (" reference_element => mesh%get_reference_element()\n" " nfaces_re_h = reference_element%" "get_number_horizontal_faces()\n" " !\n" " ! initialise mesh properties\n" " !\n" " adjacent_face => mesh%get_adjacent_face()" in gen) assert ("call testkern_mesh_prop_face_qr_code(nlayers, a, f1_proxy%data, " "ndf_w1, undf_w1, map_w1(:,cell), basis_w1_qr, " "nfaces_re_h, adjacent_face(:,cell), " "nfaces_qr, np_xyz_qr, weights_xyz_qr)" in gen)
def test_2kern_trans(kernel_outputdir): ''' Check that we generate correct code when we transform two kernels within a single invoke. ''' psy, invoke = get_invoke("4.5.2_multikernel_invokes.f90", api="dynamo0.3", idx=0) sched = invoke.schedule kernels = sched.walk(Kern) assert len(kernels) == 5 ktrans = Dynamo0p3KernelConstTrans() _, _ = ktrans.apply(kernels[1], {"number_of_layers": 100}) _, _ = ktrans.apply(kernels[2], {"number_of_layers": 100}) # Generate the code (this triggers the generation of new kernels) code = str(psy.gen).lower() # Find the tags added to the kernel/module names for match in re.finditer('use testkern_any_space_2(.+?)_mod', code): tag = match.group(1) assert ("use testkern_any_space_2{0}_mod, only: " "testkern_any_space_2{0}_code".format(tag) in code) assert "call testkern_any_space_2{0}_code(".format(tag) in code filepath = os.path.join(str(kernel_outputdir), "testkern_any_space_2{0}_mod.f90".format(tag)) assert os.path.isfile(filepath) assert "nlayers = 100" in open(filepath).read() assert "use testkern_any_space_2_mod, only" not in code assert "call testkern_any_space_2_code(" not in code assert LFRicBuild(kernel_outputdir).code_compiles(psy)
def test_1kern_trans(kernel_outputdir): ''' Check that we generate the correct code when an invoke contains the same kernel more than once but only one of them is transformed. ''' psy, invoke = get_invoke("4_multikernel_invokes.f90", api="dynamo0.3", idx=0) sched = invoke.schedule kernels = sched.coded_kernels() # We will transform the second kernel but not the first kern = kernels[1] rtrans = ACCRoutineTrans() _, _ = rtrans.apply(kern) # Generate the code (this triggers the generation of a new kernel) code = str(psy.gen).lower() tag = re.search('use testkern(.+?)_mod', code).group(1) # We should have a USE for the original kernel and a USE for the new one assert "use testkern{0}_mod, only: testkern{0}_code".format(tag) in code assert "use testkern_mod, only: testkern_code" in code # Similarly, we should have calls to both the original and new kernels assert "call testkern_code(" in code assert "call testkern{0}_code(".format(tag) in code first = code.find("call testkern_code(") second = code.find("call testkern{0}_code(".format(tag)) assert first < second assert LFRicBuild(kernel_outputdir).code_compiles(psy)
def test_halo_for_discontinuous(tmpdir, monkeypatch, annexed): '''This test checks the case when our loop iterates over owned cells (e.g. it writes to a discontinuous field), we read from a continuous field, there are no stencil accesses, but we do not know anything about the previous writer. As we don't know anything about the previous writer we have to assume that it may have been over dofs. If so, we could have dirty annexed dofs so need to add a halo exchange (for the three continuous fields being read (f1, f2 and m1). This is the case when api_config.compute_annexed_dofs is False. If we always iterate over annexed dofs by default, our annexed dofs will always be clean. Therefore we do not need to add a halo exchange. This is the case when api_config.compute_annexed_dofs is True. ''' api_config = Config.get().api_conf(TEST_API) monkeypatch.setattr(api_config, "_compute_annexed_dofs", annexed) _, info = parse(os.path.join(BASE_PATH, "1_single_invoke_w3.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(info) result = str(psy.gen) if annexed: assert "halo_exchange" not in result else: assert "IF (f1_proxy%is_dirty(depth=1)) THEN" in result assert "CALL f1_proxy%halo_exchange(depth=1)" in result assert "IF (f2_proxy%is_dirty(depth=1)) THEN" in result assert "CALL f2_proxy%halo_exchange(depth=1)" in result assert "IF (m1_proxy%is_dirty(depth=1)) THEN" in result assert "CALL m1_proxy%halo_exchange(depth=1)" in result assert LFRicBuild(tmpdir).code_compiles(psy)
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 LFRicBuild(tmpdir).code_compiles(psy) assert ( "SUBROUTINE invoke_0_testkern_operator_type(mm_w0_op, coord, a, qr)" in generated_code) assert "TYPE(operator_type), intent(in) :: 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, " "coord_proxy(1)%data, coord_proxy(2)%data, coord_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_gh_inc_nohex_1(tmpdir, monkeypatch): '''If COMPUTE_ANNEXED_DOFS is True, then a gh_inc access to a field in a kernel (iterating to the l1 halo) does not require a halo exchange when the previous writer is known and iterates over dofs to nannexed, halo(1), or halo max depth ''' # ensure that COMPUTE_ANNEXED_DOFS is True config = Config.get() dyn_config = config.api_conf(API) monkeypatch.setattr(dyn_config, "_compute_annexed_dofs", True) # parse and get psy schedule _, info = parse(os.path.join(BASE_PATH, "14.12_halo_wdofs_to_inc.f90"), api=API) psy = PSyFactory(API, distributed_memory=True).create(info) schedule = psy.invokes.invoke_list[0].schedule def check_schedule(schedule): '''Check this schedule has expected structure (loop, haloexchange, loop). In paricular there should be no halo exchange for the write-to-gh_inc dependence. :param schedule: a dynamo0.3 API schedule object :type schedule: :py:class:`psyclone.dynamo0p3.DynInvokeSchedule`. ''' assert len(schedule.children) == 3 loop1 = schedule.children[0] haloex = schedule.children[1] loop2 = schedule.children[2] assert isinstance(loop1, DynLoop) assert isinstance(haloex, DynHaloExchange) assert haloex.field.name == "f2" assert haloex.required() == (True, False) assert isinstance(loop2, DynLoop) # 1st loop should iterate over dofs to nannexed. Check output assert schedule.children[0].upper_bound_name == "nannexed" check_schedule(schedule) # just check compilation here (not later in this test) as # compilation of redundant computation is checked separately assert LFRicBuild(tmpdir).code_compiles(psy) # make 1st loop iterate over dofs to the level 1 halo and check output rc_trans = Dynamo0p3RedundantComputationTrans() rc_trans.apply(schedule.children[0], {"depth": 1}) assert schedule.children[0].upper_bound_name == "dof_halo" assert schedule.children[0].upper_bound_halo_depth == 1 check_schedule(schedule) # make 1st loop iterate over dofs to the maximum halo depth and # check output rc_trans.apply(schedule.children[0]) assert schedule.children[0].upper_bound_name == "dof_halo" assert not schedule.children[0].upper_bound_halo_depth check_schedule(schedule)
def test_int_inc_X_times_Y(tmpdir, monkeypatch, annexed, dist_mem): ''' Test that 1) the str method of LFRicIntIncXTimesYKern returns the expected string and 2) we generate correct code for the built-in operation X = X*Y where X and Y are integer-valued fields. Test with and without annexed dofs being computed as this affects the generated code. ''' api_config = Config.get().api_conf(API) monkeypatch.setattr(api_config, "_compute_annexed_dofs", annexed) _, invoke_info = parse( os.path.join(BASE_PATH, "15.23.2_int_inc_X_times_Y_builtin.f90"), api=API) psy = PSyFactory(API, distributed_memory=dist_mem).create(invoke_info) # Test string method first_invoke = psy.invokes.invoke_list[0] kern = first_invoke.schedule.children[0].loop_body[0] assert str(kern) == ("Built-in: Multiply one integer-valued field " "by another") # Test code generation code = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) if not dist_mem: output = ( " f1_proxy = f1%get_proxy()\n" " f2_proxy = f2%get_proxy()\n" " !\n" " ! Initialise number of DoFs for aspc1_f1\n" " !\n" " undf_aspc1_f1 = f1_proxy%vspace%get_undf()\n" " !\n" " ! Call our kernels\n" " !\n" " DO df=1,undf_aspc1_f1\n" " f1_proxy%data(df) = f1_proxy%data(df) * " "f2_proxy%data(df)\n" " END DO") assert output in code else: output_dm_2 = ( " !\n" " ! Call kernels and communication routines\n" " !\n" " DO df=1,f1_proxy%vspace%get_last_dof_annexed()\n" " f1_proxy%data(df) = f1_proxy%data(df) * " "f2_proxy%data(df)\n" " END DO\n" " !\n" " ! Set halos dirty/clean for fields modified in the " "above loop\n" " !\n" " CALL f1_proxy%set_dirty()\n" " !\n") if not annexed: output_dm_2 = output_dm_2.replace("dof_annexed", "dof_owned") assert output_dm_2 in code
def test_face_and_edge_qr(dist_mem, tmpdir): ''' Check that we can handle a kernel that requires two types of quadrature. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1.1.7_face_and_edge_qr.f90"), api=API) psy = PSyFactory(API, distributed_memory=dist_mem).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen_code = str(psy.gen) # Check that the qr-related variables are all declared assert (" TYPE(quadrature_face_type), intent(in) :: qr_face\n" " TYPE(quadrature_edge_type), intent(in) :: qr_edge\n" in gen_code) assert ("REAL(KIND=r_def), allocatable :: basis_w1_qr_face(:,:,:,:), " "basis_w1_qr_edge(:,:,:,:), diff_basis_w2_qr_face(:,:,:,:), " "diff_basis_w2_qr_edge(:,:,:,:), basis_w3_qr_face(:,:,:,:), " "diff_basis_w3_qr_face(:,:,:,:), basis_w3_qr_edge(:,:,:,:), " "diff_basis_w3_qr_edge(:,:,:,:)" in gen_code) assert (" REAL(KIND=r_def), pointer :: weights_xyz_qr_edge(:,:) " "=> null()\n" " INTEGER(KIND=i_def) np_xyz_qr_edge, nedges_qr_edge\n" " REAL(KIND=r_def), pointer :: weights_xyz_qr_face(:,:) " "=> null()\n" " INTEGER(KIND=i_def) np_xyz_qr_face, nfaces_qr_face\n" in gen_code) assert (" TYPE(quadrature_edge_proxy_type) qr_edge_proxy\n" " TYPE(quadrature_face_proxy_type) qr_face_proxy\n" in gen_code) # Allocation and computation of (some of) the basis functions assert (" ALLOCATE (basis_w3_qr_face(dim_w3, ndf_w3, np_xyz_qr_face," " nfaces_qr_face))\n" " ALLOCATE (diff_basis_w3_qr_face(diff_dim_w3, ndf_w3, " "np_xyz_qr_face, nfaces_qr_face))\n" " ALLOCATE (basis_w3_qr_edge(dim_w3, ndf_w3, np_xyz_qr_edge, " "nedges_qr_edge))\n" " ALLOCATE (diff_basis_w3_qr_edge(diff_dim_w3, ndf_w3, " "np_xyz_qr_edge, nedges_qr_edge))\n" in gen_code) assert (" CALL qr_face%compute_function(BASIS, m2_proxy%vspace, " "dim_w3, ndf_w3, basis_w3_qr_face)\n" " CALL qr_face%compute_function(DIFF_BASIS, m2_proxy%vspace, " "diff_dim_w3, ndf_w3, diff_basis_w3_qr_face)\n" " CALL qr_edge%compute_function(BASIS, m2_proxy%vspace, " "dim_w3, ndf_w3, basis_w3_qr_edge)\n" " CALL qr_edge%compute_function(DIFF_BASIS, m2_proxy%vspace, " "diff_dim_w3, ndf_w3, diff_basis_w3_qr_edge)\n" in gen_code) # Check that the kernel call itself is correct assert ( "CALL testkern_2qr_code(nlayers, f1_proxy%data, f2_proxy%data, " "m1_proxy%data, m2_proxy%data, " "ndf_w1, undf_w1, map_w1(:,cell), basis_w1_qr_face, basis_w1_qr_edge, " "ndf_w2, undf_w2, map_w2(:,cell), diff_basis_w2_qr_face, " "diff_basis_w2_qr_edge, " "ndf_w3, undf_w3, map_w3(:,cell), basis_w3_qr_face, basis_w3_qr_edge, " "diff_basis_w3_qr_face, diff_basis_w3_qr_edge, " "nfaces_qr_face, np_xyz_qr_face, weights_xyz_qr_face, " "nedges_qr_edge, np_xyz_qr_edge, weights_xyz_qr_edge)" in gen_code)
def test_no_halo_for_discontinuous(tmpdir): ''' Test that we do not create halo exchange calls when our loop only iterates over owned cells (e.g. it writes to a discontinuous field), we only read from a discontinuous field and there are no stencil accesses ''' _, info = parse(os.path.join(BASE_PATH, "1_single_invoke_w2v.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(info) result = str(psy.gen) assert "halo_exchange" not in result assert LFRicBuild(tmpdir).code_compiles(psy)
def test_int_a_plus_X(tmpdir, monkeypatch, annexed, dist_mem): ''' Test that 1) the str method of LFRicIntAPlusXKern returns the expected string and 2) we generate correct code for the built-in operation Y = a + X where 'a' is an integer scalar and X and Y are integer-valued fields. Test with and without annexed dofs being computed as this affects the generated code. ''' api_config = Config.get().api_conf(API) monkeypatch.setattr(api_config, "_compute_annexed_dofs", annexed) _, invoke_info = parse(os.path.join(BASE_PATH, "15.21.3_int_a_plus_X_builtin.f90"), api=API) psy = PSyFactory(API, distributed_memory=dist_mem).create(invoke_info) # Test string method first_invoke = psy.invokes.invoke_list[0] kern = first_invoke.schedule.children[0].loop_body[0] assert str(kern) == "Built-in: int_a_plus_X (integer-valued fields)" # Test code generation code = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) if not dist_mem: output = (" ! Call our kernels\n" " !\n" " DO df=1,undf_aspc1_f2\n" " f2_proxy%data(df) = a + f1_proxy%data(df)\n" " END DO\n" " !\n" " END SUBROUTINE invoke_0\n") assert output in code else: output_dm_2 = ( " !\n" " ! Call kernels and communication routines\n" " !\n" " DO df=1,f2_proxy%vspace%get_last_dof_annexed()\n" " f2_proxy%data(df) = a + f1_proxy%data(df)\n" " END DO\n" " !\n" " ! Set halos dirty/clean for fields modified in the " "above loop\n" " !\n" " CALL f2_proxy%set_dirty()\n" " !\n") if not annexed: output_dm_2 = output_dm_2.replace("dof_annexed", "dof_owned") assert output_dm_2 in code
def test_operator_nofield_scalar(tmpdir): ''' Tests that an operator with no field and a scalar argument is implemented correctly in the PSy layer ''' _, invoke_info = parse(os.path.join(BASE_PATH, "10.6_operator_no_field_scalar.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) gen = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) assert "mesh => my_mapping_proxy%fs_from%get_mesh()" in gen assert "nlayers = my_mapping_proxy%fs_from%get_nlayers()" in gen assert "ndf_w2 = my_mapping_proxy%fs_from%get_ndf()" in gen assert "DO cell=1,mesh%get_last_halo_cell(1)" in gen assert ("(cell, nlayers, my_mapping_proxy%ncell_3d, my_mapping_proxy%" "local_stencil, b, ndf_w2, basis_w2_qr, np_xy_qr, np_z_qr, " "weights_xy_qr, weights_z_qr)" in gen)
def test_node_list_error(tmpdir): ''' Test that applying Extract Transformation on objects which are not Nodes or a list of Nodes raises a TransformationError. Also raise transformation errors when the Nodes do not have the same parent if they are incorrectly ordered. ''' etrans = LFRicExtractTrans() # First test for f1 readwrite to read dependency psy, _ = get_invoke("3.2_multi_functions_multi_named_invokes.f90", DYNAMO_API, idx=0, dist_mem=False) invoke0 = psy.invokes.invoke_list[0] invoke1 = psy.invokes.invoke_list[1] # Supply an object which is not a Node or a list of Nodes with pytest.raises(TransformationError) as excinfo: etrans.apply(invoke0) assert ("Error in LFRicExtractTrans: Argument must be " "a single Node in a Schedule, a Schedule or a list of Nodes in a " "Schedule but have been passed an object of type: " "<class 'psyclone.dynamo0p3.DynInvoke'>") in str(excinfo.value) # Supply Nodes in incorrect order or duplicate Nodes node_list = [ invoke0.schedule.children[0], invoke0.schedule.children[0], invoke0.schedule.children[1] ] with pytest.raises(TransformationError) as excinfo: etrans.apply(node_list) assert "Children are not consecutive children of one parent:" \ in str(excinfo.value) assert "has position 0, but previous child had position 0."\ in str(excinfo.value) # Supply Nodes which are not children of the same parent node_list = [ invoke0.schedule.children[1], invoke1.schedule.children[0], invoke0.schedule.children[2] ] with pytest.raises(TransformationError) as excinfo: etrans.apply(node_list) assert ("supplied nodes are not children of the same " "parent.") in str(excinfo.value) assert LFRicBuild(tmpdir).code_compiles(psy)
def test_kerncallarglist_quad_rule_error(dist_mem, tmpdir): ''' Check that we raise the expected exception if we encounter an unsupported quadrature shape in the quad_rule() method. ''' psy, _ = get_invoke("6_multiple_QR_per_invoke.f90", TEST_API, dist_mem=dist_mem, idx=0) assert LFRicBuild(tmpdir).code_compiles(psy) schedule = psy.invokes.invoke_list[0].schedule loop = schedule.walk(DynLoop)[0] create_arg_list = KernCallArgList(loop.loop_body[0]) # Add an invalid shape to the dict of qr rules create_arg_list._kern.qr_rules["broken"] = None with pytest.raises(NotImplementedError) as err: create_arg_list.quad_rule() assert ("no support implemented for quadrature with a shape of 'broken'" in str(err.value))
def test_field_qr_deref(tmpdir): ''' Tests that a call, with a set of fields requiring quadrature, produces correct code when the quadrature is supplied as the component of a derived type. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "1.1.1_single_invoke_qr_deref.f90"), api="dynamo0.3") for dist_mem in [True, False]: psy = PSyFactory("dynamo0.3", distributed_memory=dist_mem).create(invoke_info) assert LFRicBuild(tmpdir).code_compiles(psy) gen = str(psy.gen) assert ( " SUBROUTINE invoke_0_testkern_qr_type(f1, f2, m1, a, m2, istp," " unit_cube_qr_xyoz)\n" in gen) assert ("TYPE(quadrature_xyoz_type), intent(in) :: unit_cube_qr_xyoz" in gen)
def test_operator_nofield_different_space(tmpdir): ''' Tests that an operator with no field on different spaces is implemented correctly in the PSy layer. ''' _, invoke_info = parse(os.path.join( BASE_PATH, "10.5_operator_no_field_different_" "space.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) gen = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) assert "mesh => my_mapping_proxy%fs_from%get_mesh()" in gen assert "nlayers = my_mapping_proxy%fs_from%get_nlayers()" in gen assert "ndf_w3 = my_mapping_proxy%fs_from%get_ndf()" in gen assert "ndf_w2 = my_mapping_proxy%fs_to%get_ndf()" in gen # We compute operators redundantly (out to the L1 halo) assert "DO cell=1,mesh%get_last_halo_cell(1)" in gen assert ("(cell, nlayers, my_mapping_proxy%ncell_3d, my_mapping_proxy%" "local_stencil, ndf_w2, ndf_w3)" in gen)
def test_operator(tmpdir): ''' Tests that an LMA operator is implemented correctly in the PSy layer. ''' _, invoke_info = parse(os.path.join(BASE_PATH, "10_operator.f90"), api=TEST_API) psy = PSyFactory(TEST_API, distributed_memory=True).create(invoke_info) generated_code = str(psy.gen) assert LFRicBuild(tmpdir).code_compiles(psy) assert ("SUBROUTINE invoke_0_testkern_operator_type(mm_w0, coord, a, qr)" in generated_code) assert "TYPE(operator_type), intent(in) :: mm_w0" in generated_code assert "TYPE(operator_proxy_type) mm_w0_proxy" in generated_code assert "mm_w0_proxy = mm_w0%get_proxy()" in generated_code assert ("CALL testkern_operator_code(cell, nlayers, mm_w0_proxy%ncell_3d, " "mm_w0_proxy%local_stencil, coord_proxy(1)%data, " "coord_proxy(2)%data, coord_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