Example #1
0
def test_goloop_field_accesses():
    ''' Check that for a GOcean kernel appropriate field accesses (based
    on the meta data) are added to the dependency analysis.

    '''
    _, invoke = get_invoke("large_stencil.f90",
                           "gocean1.0", name="invoke_large_stencil",
                           dist_mem=False)
    do_loop = invoke.schedule.children[0]

    assert isinstance(do_loop, Loop)
    var_accesses = VariablesAccessInfo(invoke.schedule)

    # cu_fld has a pointwise write access in the first loop:
    cu_fld = var_accesses[Signature("cu_fld")]
    assert len(cu_fld.all_accesses) == 1
    assert cu_fld.all_accesses[0].access_type == AccessType.WRITE
    assert cu_fld.all_accesses[0].component_indices == [["i", "j"]]

    # The stencil is defined to be GO_STENCIL(123,110,100)) for
    # p_fld. Make sure that these 9 accesses are indeed reported:
    p_fld = var_accesses[Signature("p_fld")]
    all_indices = [access.component_indices for access in p_fld.all_accesses]

    for test_index in [["i-1", "j+1"],
                       ["i", "j+1"], ["i", "j+2"],
                       ["i+1", "j+1"], ["i+2", "j+2"], ["i+3", "j+3"],
                       ["i-1", "j"],
                       ["i", "j"],
                       ["i-1", "j-1"]]:
        assert [test_index] in all_indices

    # Since we have 9 different indices found (above), the following
    # test guarantees that we don't get any invalid accesses reported.
    assert len(p_fld.all_accesses) == 9
Example #2
0
    def reference_accesses(self, var_accesses):
        '''Get all variable access information. It combines the data from
        the loop bounds (start, stop and step), as well as the loop body.
        The loop variable is marked as 'READ+WRITE' and references in start,
        stop and step are marked as 'READ'.

        :param var_accesses: VariablesAccessInfo instance that stores the \
            information about variable accesses.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`
        '''

        # It is important to first add the WRITE access, since this way
        # the dependency analysis for declaring openmp private variables
        # will automatically declare the loop variables to be private
        # (write access before read)
        var_accesses.add_access(Signature(self.variable.name),
                                AccessType.WRITE, self)
        var_accesses.add_access(Signature(self.variable.name), AccessType.READ,
                                self)

        # Accesses of the start/stop/step expressions
        self.start_expr.reference_accesses(var_accesses)
        self.stop_expr.reference_accesses(var_accesses)
        self.step_expr.reference_accesses(var_accesses)
        var_accesses.next_location()

        for child in self.loop_body.children:
            child.reference_accesses(var_accesses)
            var_accesses.next_location()
Example #3
0
def test_var_name():
    '''Test that the variable name is returned as expected.'''
    sig_a = Signature("a")
    assert sig_a.var_name == "a"
    sig_a_b = Signature(sig_a, Signature("b"))
    assert str(sig_a_b) == "a%b"
    assert sig_a_b.var_name == "a"
Example #4
0
    def append(self,
               var_name,
               var_accesses=None,
               var_access_name=None,
               mode=AccessType.READ):
        '''Appends the specified variable name to the list of all arguments.
        If var_accesses is given, it will also record the access to the
        variable. The name of the variable accessed can be overwritten by
        specifying var_access_name. By default it is assumed that access
        mode is READ (which can be set with mode).

        :param str var_name: the name of the variable.
        :param var_accesses: optional class to store variable access \
            information.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`
        :param str var_access_name: optional name of the variable for \
            which access information is stored (used e.g. when the \
            actual argument is field_proxy, but the access is to be \
            recorded for field).
        :param mode: optional access mode (defaults to READ).
        :type mode: :py:class:`psyclone.core.access_type.AccessType`

        '''
        self._arglist.append(var_name)
        if var_accesses is not None:
            if var_access_name:
                var_accesses.add_access(Signature(var_access_name), mode,
                                        self._kern)
            else:
                var_accesses.add_access(Signature(var_name), mode, self._kern)
Example #5
0
def test_concatenate_signature():
    '''Tests that signature can be concatenated.'''
    sig_b = Signature("b")
    sig_a_b = Signature("a", sig_b)
    assert str(sig_a_b) == "a%b"
    sig_b_a_b = Signature(sig_b, sig_a_b)
    assert str(sig_b_a_b) == "b%a%b"
    sig_c_d_b_a_b = Signature(("c", "d"), sig_b_a_b)
    assert str(sig_c_d_b_a_b) == "c%d%b%a%b"
Example #6
0
def test_symbol_array_detection(fortran_reader):
    '''Verifies the handling of arrays together with access information.
    '''

    code = '''program test_prog
              use some_mod
              real, dimension(5,5) :: b, c
              integer :: i
              a = b(i) + c
              end program test_prog'''
    psyir = fortran_reader.psyir_from_source(code)
    scalar_assignment = psyir.children[0]
    symbol_table = scalar_assignment.scope.symbol_table
    sym_a = symbol_table.lookup("a")
    with pytest.raises(InternalError) as error:
        sym_a.is_array_access(index_variable="j")
    assert "In Symbol.is_array_access: index variable 'j' specified, but " \
           "no access information given." in str(error.value)

    vai = VariablesAccessInfo()
    scalar_assignment.reference_accesses(vai)

    # For 'a' we don't have access information, nor symbol table information
    access_info_a = vai[Signature("a")]
    with pytest.raises(ValueError) as error:
        sym_a.is_array_access(access_info=access_info_a)
    assert "No array information is available for the symbol 'a'" \
        in str(error.value)

    # For the access to 'b' we will find array access information:
    access_info_b = vai[Signature("b")]
    sym_b = symbol_table.lookup("b")
    b_is_array = sym_b.is_array_access(access_info=access_info_b)
    assert b_is_array

    # For the access to 'c' we don't have access information, but
    # have symbol table information.
    access_info_c = vai[Signature("c")]
    sym_c = symbol_table.lookup("c")
    c_is_array = sym_c.is_array_access(access_info=access_info_c)
    assert c_is_array

    # Test specifying the index variable. The access to 'b' is
    # considered an array access when ysing the index variable 'i'.
    access_info_b = vai[Signature("b")]
    sym_b = symbol_table.lookup("b")
    b_is_array = sym_b.is_array_access(access_info=access_info_b,
                                       index_variable="i")
    assert b_is_array

    # Verify that the access to 'b' is not considered to be an
    # array access regarding the loop variable 'j' (the access
    # is loop independent):
    b_is_array = sym_b.is_array_access(access_info=access_info_b,
                                       index_variable="j")
    assert not b_is_array
    def get_signature_and_indices(self):
        ''':returns: the Signature of this array of structure reference, \
            and a list of lists of the indices used for each component.
        :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \
            lists of indices)

        '''
        sub_sig, indices = self.children[0].get_signature_and_indices()
        sig = Signature(self.name)
        return (Signature(sig, sub_sig), [self.indices] + indices)
Example #8
0
def test_derived_type(parser):
    ''' Tests assignment to derived type variables. '''
    reader = FortranStringReader('''program test
                                 use my_mod, only: my_type
                                 type(my_type) :: a, b
                                 integer :: ji, jj, jpi, jpj
                                 do jj = 1, jpj   ! loop 0
                                    do ji = 1, jpi
                                       a%b(ji, jj) = a%b(ji, jj-1)+1
                                     end do
                                 end do
                                 do jj = 1, jpj   ! loop 0
                                    do ji = 1, jpi
                                       a%b(ji, jj) = a%b(ji, jj-1)+1
                                       b%b(ji, jj) = b%b(ji, jj-1)+1
                                     end do
                                 end do
                                 end program test''')
    prog = parser(reader)
    psy = PSyFactory("nemo", distributed_memory=False).create(prog)
    loops = psy.invokes.get("test").schedule
    dep_tools = DependencyTools(["levels", "lat"])

    parallel = dep_tools.can_loop_be_parallelised(loops[0], "jj")
    assert not parallel

    # Test that testing is stopped with the first unparallelisable statement
    parallel = dep_tools.can_loop_be_parallelised(loops[1], "jj")
    assert not parallel
    # Test that only one message is stored, i.e. no message for the
    # next assignment to a derived type.
    assert len(dep_tools.get_all_messages()) == 1

    parallel = dep_tools.can_loop_be_parallelised(loops[1],
                                                  "jj",
                                                  test_all_variables=True)
    assert not parallel
    # Now we must have two messages, one for each of the two assignments
    assert len(dep_tools.get_all_messages()) == 2

    # Test that variables are ignored as expected.
    parallel = dep_tools.\
        can_loop_be_parallelised(loops[1], "jj",
                                 signatures_to_ignore=[Signature(("a", "b"))])
    assert not parallel
    assert len(dep_tools.get_all_messages()) == 1

    # If both derived types are ignored, the loop should be marked
    # to be parallelisable
    parallel = dep_tools.\
        can_loop_be_parallelised(loops[1], "jj",
                                 signatures_to_ignore=[Signature(("a", "b")),
                                                       Signature(("b", "b"))])
    assert len(dep_tools.get_all_messages()) == 0
    assert parallel
Example #9
0
def test_const_argument():
    '''Check that using a const scalar as parameter works, i.e. is not
    listed as input variable.'''
    _, invoke = get_invoke("test00.1_invoke_kernel_using_const_scalar.f90",
                           api="gocean1.0",
                           idx=0)
    dep_tools = DependencyTools()
    input_list = dep_tools.get_input_parameters(invoke.schedule)
    # Make sure the constant '0' is not listed
    assert input_list == [
        Signature('p_fld'),
        Signature(('p_fld', 'grid', 'subdomain', 'internal', 'xstop')),
        Signature(('p_fld', 'grid', 'tmask'))
    ]
Example #10
0
def test_signature_errors():
    '''Tests error handling of Signature class.
    '''
    with pytest.raises(InternalError) as err:
        _ = Signature(1)

    assert "Got unexpected type 'int' in Signature" in str(err.value)
Example #11
0
def test_if_statement(parser):
    ''' Tests handling an if statement
    '''
    reader = FortranStringReader('''program test_prog
                                 integer :: a, b, i
                                 real, dimension(5) :: p, q, r
                                 if (a .eq. b) then
                                    p(i) = q(i)
                                 else
                                   q(i) = r(i)
                                 endif
                                 end program test_prog''')
    ast = parser(reader)
    psy = PSyFactory(API).create(ast)
    schedule = psy.invokes.get("test_prog").schedule

    if_stmt = schedule.children[0]
    assert isinstance(if_stmt, IfBlock)
    var_accesses = VariablesAccessInfo(if_stmt)
    assert str(var_accesses) == "a: READ, b: READ, i: READ, p: WRITE, "\
                                "q: READ+WRITE, r: READ"
    # Test that the two accesses to 'q' indeed show up as
    q_accesses = var_accesses[Signature("q")].all_accesses
    assert len(q_accesses) == 2
    assert q_accesses[0].access_type == AccessType.READ
    assert q_accesses[1].access_type == AccessType.WRITE
    assert q_accesses[0].location < q_accesses[1].location
Example #12
0
def test_derived_type_array(array, indices, fortran_writer, fortran_reader):
    '''This function tests the handling of derived array types.
    '''
    code = '''module test
        contains
        subroutine tmp()
          use my_mod
          !use my_mod, only: something
          !type(something) :: a, b, c
          integer :: i, j, k
          c(i)%e(j,k) = {0}
        end subroutine tmp
        end module test'''.format(array)

    schedule = fortran_reader.psyir_from_source(code).children[0]
    node1 = schedule.children[0][0]
    vai1 = VariablesAccessInfo(node1)
    assert isinstance(node1, Assignment)
    assert str(vai1) == "a%b%c: READ, c%e: WRITE, i: READ, j: READ, k: READ"

    # Verify that the index expression is correct. Convert the index
    # expression to a list of list of strings to make this easier:
    sig = Signature(("a", "b", "c"))
    access = vai1[sig][0]
    assert to_fortran(fortran_writer, access.component_indices) == indices
Example #13
0
 def get_signature_and_indices(self):
     ''':returns: the Signature of this reference, and \
         an empty list of lists as 'indices' since this reference does \
         not represent an array access.
     :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \
         list of indices)
     '''
     return (Signature(self.name), [[]])
Example #14
0
 def get_signature_and_indices(self):
     ''':returns: the Signature of this member access, and a list \
     of list of the indices used for each component, which is empty \
     in this case since it is not an array access.
     :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \
         lists of indices)
     '''
     return (Signature(self.name), [[]])
Example #15
0
def test_user_defined_variables(parser):
    ''' Test reading and writing to user defined variables.
    '''
    reader = FortranStringReader('''program test_prog
                                       use some_mod, only: my_type
                                       type(my_type) :: a, e
                                       integer :: ji, jj, d
                                       a%b(ji)%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
Example #16
0
def test_assignment(parser):
    ''' Check that assignments set the right read/write accesses.
    '''
    reader = FortranStringReader('''program test_prog
                                 use some_mod, only: f
                                 integer :: i, j
                                 real :: a, b, e, x, y
                                 real, dimension(5,5) :: c, d
                                 a = b
                                 c(i,j) = d(i,j+1)+e+f(x,y)
                                 c(i) = c(i) + 1
                                 d(i,j) = sqrt(e(i,j))
                                 end program test_prog''')
    ast = parser(reader)
    psy = PSyFactory(API).create(ast)
    schedule = psy.invokes.get("test_prog").schedule

    # Simple scalar assignment:  a = b
    scalar_assignment = schedule.children[0]
    assert isinstance(scalar_assignment, Assignment)
    var_accesses = VariablesAccessInfo(scalar_assignment)
    # Test some test functions explicitly:
    assert var_accesses.is_written(Signature("a"))
    assert not var_accesses.is_read(Signature("a"))
    assert not var_accesses.is_written(Signature("b"))
    assert var_accesses.is_read(Signature("b"))

    # Array element assignment: c(i,j) = d(i,j+1)+e+f(x,y)
    array_assignment = schedule.children[1]
    assert isinstance(array_assignment, Assignment)
    var_accesses = VariablesAccessInfo(array_assignment)
    assert str(var_accesses) == "c: WRITE, d: READ, e: READ, f: READ, "\
                                "i: READ, j: READ, x: READ, y: READ"
    # Increment operation: c(i) = c(i)+1
    increment_access = schedule.children[2]
    assert isinstance(increment_access, Assignment)
    var_accesses = VariablesAccessInfo(increment_access)
    assert str(var_accesses) == "c: READ+WRITE, i: READ"

    # Using an intrinsic:
    sqrt_access = schedule.children[3]
    assert isinstance(sqrt_access, Assignment)
    var_accesses = VariablesAccessInfo(sqrt_access)
    assert str(var_accesses) == "d: WRITE, e: READ, i: READ, j: READ"
Example #17
0
 def get_signature_and_indices(self):
     ''':returns: the Signature of this structure member, and \
         a list of the indices used for each component (empty list \
         for this component, since the access is not an array - but \
         other components might have indices).
     :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \
         list of indices)
     '''
     sub_sig, indices = self.children[0].get_signature_and_indices()
     return (Signature(self.name, sub_sig), [[]] + indices)
Example #18
0
def test_signature_dict():
    '''Test that Signature instances work as expected as dictionary keys.
    '''
    sig1 = Signature("a")
    sig2 = Signature("a")
    assert sig1 is not sig2

    # Make sure that different instances representing the same signature
    # will have the same hash:
    test_dict = {}
    test_dict[sig1] = "a"
    assert test_dict[sig2] == "a"

    sig3 = Signature(("a", "b"))
    test_dict[sig3] = "ab"
    sig4 = Signature(("a", "c"))
    test_dict[sig4] = "ac"

    assert len(test_dict) == 3
Example #19
0
def test_signature():
    '''Test the Signature class.
    '''

    assert str(Signature("a")) == "a"
    assert str(Signature(("a", ))) == "a"
    assert str(Signature(("a", "b", "c"))) == "a%b%c"
    assert repr(Signature("a")) == "Signature(a)"
    assert repr(Signature(("a", ))) == "Signature(a)"
    assert repr(Signature(("a", "b", "c"))) == "Signature(a%b%c)"
    assert Signature("a") != "a"
Example #20
0
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
Example #21
0
def test_reference_accesses():
    ''' Test the reference_accesses method.
    '''
    dref = nodes.StructureReference.create(
        symbols.DataSymbol(
            "grid", symbols.DataTypeSymbol("grid_type",
                                           symbols.DeferredType())), ["data"])
    var_access_info = VariablesAccessInfo()
    dref.reference_accesses(var_access_info)

    assert var_access_info.all_signatures == [Signature(("grid", "data"))]
    # By default all accesses are marked as read
    assert str(var_access_info) == "grid%data: READ"
Example #22
0
    def get_signature_and_indices(self):
        ''':returns: the Signature of this structure reference, and \
            a list of the indices used for each component (empty list \
            if an access is not an array).
        :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \
            list of indices)

        '''
        # Get the signature of self:
        my_sig, my_index = \
            super(StructureReference, self).get_signature_and_indices()
        # Then the sub-signature of the member, and indices used:
        sub_sig, indices = self.children[0].get_signature_and_indices()
        # Combine signature and indices
        return (Signature(my_sig, sub_sig), my_index + indices)
Example #23
0
    def reference_accesses(self, var_accesses):
        '''Get all variable access information from this node, i.e.
        it sets this variable to be read.

        :param var_accesses: VariablesAccessInfo instance that stores the \
            information about variable accesses.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`

        '''
        if (self.parent and isinstance(self.parent, Operation) and
                self.parent.operator in [BinaryOperation.Operator.LBOUND,
                                         BinaryOperation.Operator.UBOUND] and
                self.parent.children.index(self) == 0):
            # This reference is the first argument to a lbound or
            # ubound intrinsic. These intrinsics do not access the
            # array elements, they determine the array
            # bounds. Therefore there is no data dependence.
            return
        sig = Signature(self.name)
        var_accesses.add_access(sig, AccessType.READ, self)
Example #24
0
    def extend(self, list_var_name, var_accesses=None, mode=AccessType.READ):
        '''Appends all variable names in the argument list to the list of
        all arguments. If var_accesses is given, it will also record the
        access to the variables. By default any access will be recorded as a
        read-only access, but this can be changed (for all variables
        included) using mode.

        :param list_var_name: the list with name of the variables to append.
        :type list_var_name: list of str.
        :param var_accesses: optional class to store variable access \
            information.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`
        :param mode: optional access mode (defaults to READ).
        :type mode: :py:class:`psyclone.core.access_type.AccessType`

        '''
        self._arglist.extend(list_var_name)
        if var_accesses:
            for var_name in list_var_name:
                var_accesses.add_access(Signature(var_name), mode, self._kern)
Example #25
0
def test_signature_sort():
    '''Test that signatures can be sorted.'''

    sig_list = [
        Signature("c"),
        Signature("a"),
        Signature("b"),
        Signature(("b", "a")),
        Signature(("a", "c")),
        Signature(("a", "b"))
    ]

    assert str(sig_list) == "[Signature(c), Signature(a), Signature(b), " \
                            "Signature(b%a), Signature(a%c), Signature(a%b)]"

    sig_list.sort()
    assert str(sig_list) == "[Signature(a), Signature(a%b), Signature(a%c), " \
                            "Signature(b), Signature(b%a), Signature(c)]"
Example #26
0
    def reference_accesses(self, var_accesses):
        '''Get all variable access information. All variables used as indices
        in the access of the array will be added as READ.

        :param var_accesses: variable access information.
        :type var_accesses: \
            :py:class:`psyclone.core.access_info.VariablesAccessInfo`

        '''
        # This will set the array-name as READ
        super(ArrayMixin, self).reference_accesses(var_accesses)
        # Now add all children: Note that the class Reference
        # does not recurse to the children (which store the indices), so at
        # this stage no index information has been stored:
        list_indices = []
        for child in self.children:
            child.reference_accesses(var_accesses)
            list_indices.append(child)

        if list_indices:
            var_info = var_accesses[Signature(self.name)]
            # The last entry in all_accesses is the one added above
            # in super(ArrayReference...). Add the indices to that entry.
            var_info.all_accesses[-1].indices = list_indices
Example #27
0
def test_inout_parameters_nemo(parser):
    '''Test detection of input and output parameters in NEMO.
    '''
    reader = FortranStringReader('''program test
                         integer :: ji, jj, jpi, jpj
                         real :: a(5,5), c(5,5), b
                         do jj = 1, jpj   ! loop 0
                            do ji = 1, jpi
                               a(ji, jj) = b+c(ji, jj)
                             end do
                         end do
                         end program test''')
    prog = parser(reader)
    psy = PSyFactory("nemo", distributed_memory=False).create(prog)
    loops = psy.invokes.get("test").schedule

    dep_tools = DependencyTools()
    input_list = dep_tools.get_input_parameters(loops)
    # Use set to be order independent
    input_set = set(input_list)
    assert input_set == set(
        [Signature("b"),
         Signature("c"),
         Signature("jpi"),
         Signature("jpj")])

    output_list = dep_tools.get_output_parameters(loops)
    # Use set to be order independent
    output_set = set(output_list)
    assert output_set == set(
        [Signature("jj"), Signature("ji"),
         Signature("a")])

    in_list1, out_list1 = dep_tools.get_in_out_parameters(loops)

    assert in_list1 == input_list
    assert out_list1 == output_list
Example #28
0
def test_signature_comparison():
    ''' Test that two Signatures can be compared for equality and not
    equality.
    '''
    # pylint: disable=unneeded-not
    assert Signature(("a", "b")) == Signature(("a", "b"))
    assert not Signature(("a", "b")) == Signature(("a", "c"))

    assert Signature(("a", "b")) != Signature(("a", "c"))
    assert not Signature(("a", "b")) != Signature(("a", "b"))
    assert Signature(("a", "c")) >= Signature(("a", "b"))
    assert not Signature(("a", "b")) >= Signature(("a", "c"))
    assert Signature(("a", "c")) > Signature(("a", "b"))
    assert not Signature(("a", "b")) > Signature(("a", "c"))
    assert Signature(("a", "b")) <= Signature(("a", "c"))
    assert not Signature(("a", "c")) <= Signature(("a", "b"))
    assert Signature(("a", "b")) < Signature(("a", "c"))
    assert not Signature(("a", "c")) < Signature(("a", "b"))

    # Comparison with other types should work for == and !=:
    assert not Signature(("a", "b")) == 2
    assert Signature(("a", "b")) != 2
    # pylint: enable=unneeded-not

    # Error cases: comparison of signature with other type.
    with pytest.raises(TypeError) as err:
        _ = Signature(("a", "b")) < 1
    assert "'<' not supported between instances of 'Signature' and 'int'" \
        in str(err.value)

    with pytest.raises(TypeError) as err:
        _ = Signature(("a", "b")) <= "a"
    assert "'<=' not supported between instances of 'Signature' and 'str'" \
        in str(err.value)

    with pytest.raises(TypeError) as err:
        _ = Signature(("a", "b")) > [1]
    assert "'>' not supported between instances of 'Signature' and 'list'" \
        in str(err.value)

    with pytest.raises(TypeError) as err:
        _ = Signature(("a", "b")) >= (1, 2)
    assert "'>=' not supported between instances of 'Signature' and 'tuple'" \
        in str(err.value)
Example #29
0
def test_signature():
    '''Test the Signature class.
    '''

    assert str(Signature("a")) == "a"
    assert str(Signature(("a", ))) == "a"
    assert str(Signature(("a", "b", "c"))) == "a%b%c"
    assert repr(Signature("a")) == "Signature(a)"
    assert repr(Signature(("a", ))) == "Signature(a)"
    assert repr(Signature(("a", "b", "c"))) == "Signature(a%b%c)"
    assert repr(Signature(["a", "b", "c"])) == "Signature(a%b%c)"
    assert Signature("a") != "a"
    sig = Signature(("a", "b", "c"))
    assert sig.is_structure
    assert sig[0] == "a"
    assert sig[2] == "c"
    assert sig[-1] == "c"
    assert sig[0:2] == ("a", "b")
    assert Signature(["a", "b", "c"]).is_structure
    assert not Signature(("a")).is_structure
Example #30
0
    def create_driver(self, input_list, output_list):
        # pylint: disable=too-many-locals, too-many-statements
        '''This function creates a driver that can read the
        output created by the extraction code. This is a stand-alone
        program that will read the input data, calls the kernels/
        instrumented region, and then compares the results with the
        stored results in the file.

        TODO: #644: we need type information here.

        :param input_list: list of variables that are input parameters.
        :type input_list: list of str
        :param output_list: list of variables that are output parameters.
        :type output_list: list or str
        '''

        all_sigs = list(set(input_list).union(set(output_list)))
        all_sigs.sort()

        module_name, region_name = self.region_identifier
        module = ModuleGen(name=module_name)
        prog = SubroutineGen(parent=module,
                             name=module_name + "_code",
                             implicitnone=True)
        module.add(prog)
        use = UseGen(prog,
                     self.add_psydata_class_prefix("psy_data_mod"),
                     only=True,
                     funcnames=[self.add_psydata_class_prefix("PSyDataType")])
        prog.add(use)

        # Use a symbol table to make sure all variable names are unique
        sym_table = GOSymbolTable()
        sym = Symbol("PSyDataType")
        sym_table.add(sym)

        root_name = self.add_psydata_class_prefix("psy_data")
        psy_data = sym_table.new_symbol(root_name).name
        var_decl = TypeDeclGen(
            prog,
            datatype=self.add_psydata_class_prefix("PSyDataType"),
            entity_decls=[psy_data])
        prog.add(var_decl)

        call = CallGen(
            prog,
            "{0}%OpenRead(\"{1}\", \"{2}\")".format(psy_data, module_name,
                                                    region_name))
        prog.add(call)

        post_suffix = self._post_name

        # Variables might need to be renamed in order to guarantee unique
        # variable names in the driver: An example of this would be if the
        # user code contains a variable 'dx', and the kernel takes a
        # property 'dx' as well. In the original code that is no problem,
        # since the property is used via field%grid%dx. But the stand-alone
        # driver renames field%grid%dx to dx, which can cause a name clash.
        # Similar problems can exist with any user defined type, since all
        # user defined types are rewritten to just use the field name.
        # We use a mapping to support renaming of variables: it takes as
        # key the variable as used in the original program (e.g. 'dx' from
        # an expression like field%grid%dx), and maps it to a unique local
        # name (e.g. dx_0).

        rename_variable = {}
        for signature in all_sigs:
            # TODO #644: we need to identify arrays!!

            # Support GOcean properties, which are accessed via a
            # derived type (e.g. 'fld%grid%dx'). In this stand-alone
            # driver we don't have the derived type, instead we create
            # variable based on the field in the derived type ('dx'
            # in the example above), and pass this variable to the
            # instrumented code.
            var_name = str(signature)

            # In case of a derived type, this will only leave the last
            # component, which is used as the local variable name. For
            # non-derived type this is just the name of the variable anyway.
            local_name = signature[-1]

            unique_local_name = sym_table.new_symbol(local_name).name
            rename_variable[local_name] = unique_local_name
            local_name = unique_local_name

            # TODO: #644 - we need to identify arrays!!
            # Any variable used needs to be defined. We also need
            # to handle the kind property better and not rely on
            # a hard-coded value.
            decl = DeclGen(prog,
                           "real", [local_name],
                           kind="8",
                           dimension=":,:",
                           allocatable=True)
            prog.add(decl)
            is_input = signature in input_list
            is_output = signature in output_list

            if is_input and not is_output:
                # We only need the pre-variable, and we can read
                # it from the file (this call also allocates space for it).
                call = CallGen(
                    prog, "{0}%ReadVariable(\"{1}\", {2})".format(
                        psy_data, var_name, local_name))
                prog.add(call)
            elif is_input:
                # Now must be input and output:
                # First read the pre-variable (which also allocates it):
                call = CallGen(
                    prog, "{0}%ReadVariable(\"{1}\", {2})".format(
                        psy_data, var_name, local_name))
                prog.add(call)
                # Then declare the post variable, and and read its values
                # (ReadVariable will also allocate it)
                sym = Symbol(local_name + post_suffix)
                sym_table.add(sym)
                decl = DeclGen(prog,
                               "real", [local_name + post_suffix],
                               dimension=":,:",
                               kind="8",
                               allocatable=True)
                prog.add(decl)
                call = CallGen(
                    prog, "{0}%ReadVariable(\"{1}{3}\", {2}{3})".format(
                        psy_data, var_name, local_name, post_suffix))
                prog.add(call)
            else:
                # Now the variable is output only. We need to read the
                # post variable in, and create and allocate a pre variable
                # with the same size as the post
                sym = Symbol(local_name + post_suffix)
                sym_table.add(sym)
                decl = DeclGen(prog,
                               "real", [local_name + post_suffix],
                               dimension=":,:",
                               kind="8",
                               allocatable=True)
                prog.add(decl)
                call = CallGen(
                    prog, "{0}%ReadVariable(\"{1}{3}\", {2}{3})".format(
                        psy_data, var_name, local_name, post_suffix))
                prog.add(call)
                decl = DeclGen(prog,
                               "real", [local_name],
                               kind="8",
                               dimension=":,:",
                               allocatable=True)
                prog.add(decl)
                alloc = AllocateGen(prog, [var_name],
                                    mold="{0}".format(local_name +
                                                      post_suffix))
                prog.add(alloc)
                # Initialise the variable with 0, since it might contain
                # values that are not set at all (halo regions, or a
                # kernel might not set all values). This way the array
                # comparison with the post value works as expected
                # TODO #644 - create the right "0.0" type here (e.g.
                # 0.0d0, ...)
                assign = AssignGen(prog, local_name, "0.0d0")
                prog.add(assign)

        # Now add the region that was extracted here:
        prog.add(CommentGen(prog, ""))
        prog.add(CommentGen(prog, " RegionStart"))

        # For the driver we have to re-create the code of the
        # instrumented region, but in this stand-alone driver the
        # arguments are not dl_esm_inf fields anymore, but simple arrays.
        # Similarly, for properties we cannot use e.g. 'fld%grid%dx'
        # anymore, we have to use e.g. a local variable 'dx' that has
        # been created. Since we are using the existing way of creating
        # the code for the instrumented region, we need to modify how
        # these variables are created. We do this by temporarily
        # modifying the properties in the config file.
        api_config = Config.get().api_conf("gocean1.0")
        all_props = api_config.grid_properties
        # Keep a copy of the original values, so we can restore
        # them later
        orig_props = dict(all_props)

        # 1) A grid property is defined like "{0}%grid%dx". This is
        #    changed to be just 'dx', i.e. the final component of
        #    the current value (but we also take renaming into account,
        #    so 'dx' might become 'dx_0').
        #    If a property is not used, it doesn't matter if we modify
        #    its definition, so we just change all properties.
        for name, prop in all_props.items():
            new_sig = Signature(prop.fortran.split("%"))
            if new_sig.is_structure:
                # Get the last field name, which will be the
                # local variable name
                local_name = new_sig[-1]
                unique_name = rename_variable.get(local_name, local_name)
                all_props[name] = GOceanConfig.make_property(
                    unique_name, prop.type, prop.intrinsic_type)

        # 2) The property 'grid_data' is a reference to the data on the
        #    grid (i.e. the actual field) , and it is defined as "{0}%data".
        #    This just becomes {0} ('a_fld%data' in the original program
        #    becomes just 'a_fld', and 'a_fld' is declared to be a plain
        #    Fortran 2d-array)
        all_props["go_grid_data"] = GOceanConfig.make_property(
            "{0}", "array", "real")

        # Each kernel caches the argument code, so we also
        # need to clear this cached data to make sure the new
        # value for "go_grid_data" is actually used.
        for kernel in self.psy_data_body.walk(CodedKern):
            kernel.clear_cached_data()

        # Recreate the instrumented region. Due to the changes in the
        # config files, fields and properties will now become local
        # plain arrays and variables:
        for child in self.psy_data_body:
            child.gen_code(prog)

        # Now reset all properties back to the original values:
        for name in all_props.keys():
            all_props[name] = orig_props[name]

        prog.add(CommentGen(prog, " RegionEnd"))
        prog.add(CommentGen(prog, ""))

        for var_name in output_list:
            prog.add(CommentGen(prog, " Check {0}".format(var_name)))

        code = str(module.root)

        with open("driver-{0}-{1}.f90".format(module_name, region_name),
                  "w") as out:
            out.write(code)