NEMO API

In contrast to the other APIs supported by PSyclone, the NEMO API is designed to work with source code that does not follow the PSyKAl separation of concerns. Instead, the NEMO source code is treated as if it were a manually written PSy layer with all kernels in-lined. This approach relies upon the NEMO Coding Conventions [nem13] in order to reason about the code being processed. Rather than construct an InvokeSchedule for the PSy layer from scratch (as is done for other APIs), the InvokeSchedule is constructed by parsing the supplied Fortran code and generating a higher-level representation.

Note

the NEMO API is currently only a prototype. The known issues are listed in Limitations.

Algorithm

Since NEMO source is treated as a pre-existing PSy layer, this API does not have the concept of an Algorithm layer.

Constructing the PSyIR

Transformations in PSyclone are applied to an Internal Representation, the “PSyIR.” In contrast to the other APIs where the PSyIR is constructed from scratch, for NEMO PSyclone must parse the existing Fortran and create a higher-level representation of it. This is done using rules based upon the NEMO Coding Conventions [nem13]. These rules are described in the following sections.

Loops

Explicit

PSyclone recognises the following loop types, based on the name of the loop variable:

Loop type

Loop variable

Vertical levels

jk

Latitude

ji

Longitude

jj

Tracer species

jn

PSyclone currently assumes that each of these loop types may be safely parallelised. In practice this will not always be the case (e.g. when performing a tri-diagonal solve) and this implementation will need to be refined.

Implicit

The use of Fortran array notation is encouraged in the NEMO Coding Conventions [nem13] (section 4.2) and is employed throughout the NEMO code base. The Coding Conventions mandate that the shape of every array in such expressions must be specified, e.g.:

onedarraya(:) = onedarrayb(:) + onedarrayc(:)
twodarray (:,:) = scalar * anothertwodarray(:,:)

PSyclone therefore also recognises the loops implied by this notation.

Note, not all uses of Fortran array notation in NEMO imply a loop. For instance:

ascalar = afunc(twodarray(:,:))

is actually a function call which is passed a reference to twodarray. However, if the quantity being assigned to is actually an array, e.g.:

twodarray2(:,:) = afunc(twodarray(:,:))

then this does represent a loop. However, currently PSyclone does not recognise any occurrences of array notation that are themselves within an array access or function call. It is therefore not yet possible to transform such implicit loops into explicit loops. It is hoped that this limitation will be removed in future releases of PSyclone by adding the ability to discover the interface to functions such as afunc and thus determining whether they return scalar or array quantities.

Example

A typical fragment of NEMO source code (taken from the traldf_iso routine) is shown below:

DO jn = 1, kjpt
 zdit (1,:,:) = 0._wp     ;     zdit (jpi,:,:) = 0._wp
 zdjt (1,:,:) = 0._wp     ;     zdjt (jpi,:,:) = 0._wp

 DO jk = 1, jpkm1
    DO jj = 1, jpjm1
       DO ji = 1, fs_jpim1
          zdit(ji,jj,jk) = ( ptb(ji+1,jj  ,jk,jn) - ptb(ji,jj,jk,jn) ) * umask(ji,jj,jk)
          zdjt(ji,jj,jk) = ( ptb(ji  ,jj+1,jk,jn) - ptb(ji,jj,jk,jn) ) * vmask(ji,jj,jk)
       END DO
    END DO
 END DO

PSyclone uses fparser2 to parse such source code and then generates the PSy Internal Representation of it:

Loop[type='tracers',field_space='None',it_space='None']
    Loop[type='None',field_space='None',it_space='None']
    Loop[type='None',field_space='None',it_space='None']
    Loop[type='None',field_space='None',it_space='None']
    Loop[type='None',field_space='None',it_space='None']
    Loop[type='levels',field_space='None',it_space='None']
        Loop[type='lat',field_space='None',it_space='None']
            Loop[type='lon',field_space='None',it_space='None']
                CodedKern[]

Transformations

The following transformations are specific to the NEMO API.


class psyclone.domain.nemo.transformations.NemoArrayRange2LoopTrans[source]

Transformation that given an assignment with an ArrayReference Range in the LHS (equivalent to an array assignment statement in Fortran), it converts it to an explicit loop doing each of the individual element assignments separately. For example:

>>> from psyclone.parse.algorithm import parse
>>> from psyclone.psyGen import PSyFactory
>>> api = "nemo"
>>> filename = "tra_adv.F90" # examples/nemo/code
>>> ast, invoke_info = parse(filename, api=api)
>>> psy = PSyFactory(api).create(invoke_info)
>>> schedule = psy.invokes.invoke_list[0].schedule
>>> print(schedule.view())
>>>
>>> from psyclone.psyir.nodes import Range
>>> from psyclone.domain.nemo.transformations import             NemoArrayRange2LoopTrans
>>> from psyclone.transformations import TransformationError
>>>
>>> trans = NemoArrayRange2LoopTrans()
>>> for my_range in reversed(schedule.walk(Range)):
>>>     try:
>>>         trans.apply(my_range)
>>>     except TransformationError:
>>>         pass
>>> print(schedule.view())

The specified Range node must be the outermost Range (specifying an access to an array index) within an Array Reference and the array reference must be on the left-hand-side of an Assignment node. This is required for correctness and if not satisfied the transformation will raise an exception.

By default the transformation will reject character arrays, though this can be overriden by setting the allow_string option to True. Note that PSyclone expresses syntax such as character(LEN=100) as UnsupportedFortranType, and this transformation will convert unknown or unsupported types to loops.

apply(node, options=None)[source]

Apply the transformation such that, given an assignment with an ArrayReference Range in the LHS (equivalent to an array assignment statement in Fortran), it converts it to an explicit loop doing each of the individual element assignments separately.

The Range node is provided to the apply method of the transformation to indicate which array index should be transformed. This can only be applied to the outermost Range of the ArrayReference.

This is currently specific to the ‘nemo’ API in that it will create NemoLoops.

Parameters:
  • node (psyclone.psyir.nodes.Range) – a Range node.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

  • options["allow_string"] (bool) – whether to allow the transformation on a character type array range. Defaults to False.

validate(node, options=None)[source]

Perform various checks to ensure that it is valid to apply the NemoArrayRange2LoopTrans transformation to the supplied PSyIR Node.

By default the validation will reject character arrays that PSyclone understand as such, though this can be overriden by setting the allow_string option to True. Note that PSyclone expresses syntax such as character(LEN=100) as UnsupportedFortranType, and this transformation will convert unknown or unsupported types to loops.

Parameters:
  • node (psyclone.psyir.nodes.Range) – the node that is being checked.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

  • options["allow_string"] (bool) – whether to allow the transformation on a character type array range. Defaults to False.

Raises:
  • TransformationError – if the node argument is not a Range, if the Range node is not part of an ArrayReference, if the Range node is not the outermost Range node of the ArrayReference or if that ArrayReference does not constitute the left hand side of an Assignment node.

  • TransformationError – if the node argument has nested array expressions with Ranges or is an invalid tree with ranges in multiple locations of a structure of arrays.

  • TransformationError – if the node argument contains a non-elemental Operation or Call.

  • TransformationError – if node contains a character type child and the allow_strings option is not set.


class psyclone.domain.nemo.transformations.NemoOuterArrayRange2LoopTrans[source]

Provides a transformation from the outermost PSyIR ArrayReference Range to a PSyIR NemoLoop. For example:

>>> from psyclone.parse.algorithm import parse
>>> from psyclone.psyGen import PSyFactory
>>> api = "nemo"
>>> filename = "tra_adv.F90" # examples/nemo/code
>>> ast, invoke_info = parse(filename, api=api)
>>> psy = PSyFactory(api).create(invoke_info)
>>> schedule = psy.invokes.invoke_list[0].schedule
>>>
>>> from psyclone.psyir.nodes import Assignment
>>> from psyclone.domain.nemo.transformations import             NemoOuterArrayRange2LoopTrans
>>> from psyclone.transformations import TransformationError
>>>
>>> print(schedule.view())
>>> trans = NemoOuterArrayRange2LoopTrans()
>>> for assignment in schedule.walk(Assignment):
>>>     while True:
>>>         try:
>>>             trans.apply(assignment)
>>>         except TransformationError:
>>>             break
>>> print(schedule.view())
apply(node, options=None)[source]

Apply the NemoOuterArrayRange2Loop transformation to the specified node if the node is an Assignment and the left-hand-side of the assignment is an Array Reference containing at least one Range node specifying an access to an array index. If this is the case then the outermost Range nodes within array references within the assignment are replaced with references to a loop index. A NemoLoop loop (with the same loop index) is also placed around the modified assignment statement.

The name of the loop index is taken from the PSyclone configuration file if a name exists for the particular array index, otherwise a new name is generated. The bounds of the loop are taken from the Range node if they are provided. If not, the loop bounds are taken from the PSyclone configuration file if bounds values are supplied. If not, the LBOUND or UBOUND intrinsics are used as appropriate. The type of the NemoLoop is also taken from the configuration file if it is supplied for that index, otherwise it is specified as being “unknown”.

Parameters:
  • node (psyclone.psyir.nodes.Assignment) – an Assignment node.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

  • options["allow_string"] (bool) – whether to allow the transformation on a character type array range. Defaults to False.

validate(node, options=None)[source]

Perform various checks to ensure that it is valid to apply the NemoOuterArrayRange2LoopTrans transformation to the supplied PSyIR Node.

Parameters:
  • node (psyclone.psyir.nodes.Assignment) – the node that is being checked.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

Raises:

TransformationError – if the supplied node is not an Assignment node, if the Assignment node does not have an Array-type Reference node on its left hand side or if the Array-type node does not contain at least one Range node.


class psyclone.domain.nemo.transformations.NemoAllArrayRange2LoopTrans[source]

Provides a transformation for all PSyIR Array Ranges in an assignment to PSyIR NemoLoops. For example:

>>> from psyclone.parse.algorithm import parse
>>> from psyclone.psyGen import PSyFactory
>>> api = "nemo"
>>> filename = "tra_adv.F90" # examples/nemo/code
>>> ast, invoke_info = parse(filename, api=api)
>>> psy = PSyFactory(api).create(invoke_info)
>>> schedule = psy.invokes.invoke_list[0].schedule
>>>
>>> from psyclone.psyir.nodes import Assignment
>>> from psyclone.domain.nemo.transformations import             NemoAllArrayRange2LoopTrans
>>>
>>> print(schedule.view())
>>> trans = NemoAllArrayRange2LoopTrans()
>>> for assignment in schedule.walk(Assignment):
>>>     trans.apply(assignment)
>>> print(schedule.view())
apply(node, options=None)[source]

Apply the NemoAllArrayRange2Loop transformation to the specified node if the node is an Assignment and the left-hand-side of the assignment is an Array Reference containing at least one Range node specifying an access to an array index. If this is the case then all Range nodes within array references within the assignment are replaced with references to the appropriate loop indices. The appropriate number of NemoLoop loops are also placed around the modified assignment statement.

The name of each loop index is taken from the PSyclone configuration file if a name exists for the particular array index, otherwise a new name is generated. The bounds of each loop are taken from the Range node if they are provided. If not, the loop bounds are taken from the PSyclone configuration file if a bounds value is supplied. If not, the LBOUND or UBOUND intrinsics are used as appropriate. The type of the NemoLoop is also taken from the configuration file if it is supplied for that index, otherwise it is specified as being “unknown”.

Parameters:
  • node (psyclone.psyir.nodes.Assignment) – an Assignment node.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

  • options["verbose"] (bool) – whether to print out the reason why the inner transformation was not applied. This is useful because this transfomation succeeds even if one of the inner transformations fails, and therefor the reason why the inner transformation failed is not propagated.

  • options["allow_string"] (bool) – whether to allow the transformation on a character type array range. Defaults to False.

validate(node, options=None)[source]

Perform various checks to ensure that it is valid to apply the NemoArrayRange2LoopTrans transformation to the supplied PSyIR Node.

Parameters:
  • node (psyclone.psyir.nodes.Assignment) – the node that is being checked.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

Raises:

TransformationError – if the supplied node is not an Assignment.


class psyclone.domain.nemo.transformations.NemoArrayAccess2LoopTrans[source]

Provides a transformation to transform a constant index access to an array (i.e. one that does not contain a loop iterator) to a single trip loop. For example:

>>> from psyclone.domain.nemo.transformations import \
...     NemoArrayAccess2LoopTrans
>>> from psyclone.psyir.backend.fortran import FortranWriter
>>> from psyclone.psyir.frontend.fortran import FortranReader
>>> from psyclone.psyir.nodes import Assignment
>>> code = ("program example\n"
...         "  real a(10)\n"
...         "  a(1) = 0.0\n"
...         "end program example\n")
>>> psyir = FortranReader().psyir_from_source(code)
>>> assignment = psyir.walk(Assignment)[0]
>>> NemoArrayAccess2LoopTrans().apply(assignment.lhs.children[0])
>>> print(FortranWriter()(psyir))
program example
  real, dimension(10) :: a
  integer :: ji

  do ji = 1, 1, 1
    a(ji) = 0.0
  enddo

end program example
apply(node, options=None)[source]

Apply the NemoArrayAccess2Loop transformation if the supplied node is an access to an array index within an Array Reference that is on the left-hand-side of an Assignment node. The access must be a scalar (i.e. not a range) and must not include a loop variable (as we are transforming a single access to a loop).

These constraints are required for correctness and an exception will be raised if they are not satisfied. If the constraints are satisfied then the array access is replaced with a loop iterator and a single trip loop.

The new loop will be placed immediately around the assignment i.e. it will not take into account any expected nesting (ji, jj, jk etc) constraints. Loop re-ordering should be performed by a separate transformation.

The name of the loop index is taken from the PSyclone configuration file if a name exists for the particular array index, otherwise a new name is generated.

Parameters:
  • node (psyclone.psyir.nodes.Node) – an array index.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

validate(node, options=None)[source]

Perform various checks to ensure that it is valid to apply the NemoArrayAccess2LoopTrans transformation to the supplied PSyIR Node.

Parameters:
  • node (psyclone.psyir.nodes.Node) – the node that is being checked.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.


class psyclone.domain.nemo.transformations.NemoAllArrayAccess2LoopTrans[source]

Provides a transformation from a PSyIR Assignment containing constant index accesses to an array into single trip loops: For example:

>>> from psyclone.domain.nemo.transformations import \
...     NemoAllArrayAccess2LoopTrans
>>> from psyclone.psyir.backend.fortran import FortranWriter
>>> from psyclone.psyir.frontend.fortran import FortranReader
>>> from psyclone.psyir.nodes import Assignment
>>> code = ("program example\n"
...         "  real a(10,10), b(10,10)\n"
...         "  integer :: n\n"
...         "  a(1,n-1) = b(1,n-1)\n"
...         "end program example\n")
>>> psyir = FortranReader().psyir_from_source(code)
>>> assignment = psyir.walk(Assignment)[0]
>>> NemoAllArrayAccess2LoopTrans().apply(assignment)
>>> print(FortranWriter()(psyir))
program example
  real, dimension(10,10) :: a
  real, dimension(10,10) :: b
  integer :: n
  integer :: ji
  integer :: jj

  do ji = 1, 1, 1
    do jj = n - 1, n - 1, 1
      a(ji,jj) = b(ji,jj)
    enddo
  enddo

end program example
apply(node, options=None)[source]

Apply the NemoAllArrayAccess2Loop transformation if the supplied node is an Assignment with an Array Reference on its left-hand-side. Each constant array index access (i.e. one not containing a loop iterator or a range) is then transformed into an iterator and the assignment placed within a single trip loop, subject to any constraints in the NemoArrayAccess2Loop transformation.

If any of the NemoAllArrayAccess2Loop constraints are not satisfied for a loop index then this transformation does nothing for that index and silently moves to the next.

Parameters:
  • node (psyclone.psyir.nodes.Assignment) – an assignment.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

validate(node, options=None)[source]

Perform any checks to ensure that it is valid to apply the NemoAllArrayAccess2LoopTrans transformation to the supplied PSyIR Node.

Parameters:
  • node (psyclone.psyir.nodes.Node) – the node that is being checked.

  • options (Optional[Dict[str, Any]]) – a dictionary with options for transformations. No options are used in this transformation. This is an optional argument that defaults to None.

Limitations

The NEMO API is currently under development. Here we list the current, known limitations/issues:

  1. Scalar variables inside loops are not made private when parallelising using OpenMP;

  2. Labelled do-loops are not handled (i.e. they will be put inside a ‘CodeBlock’ in the PSyIR);

  3. Loops are currently only permitted to contain one kernel. This restriction will have to be lifted in order to permit loop fusion;

  4. The psyir.nodes.Node base class now has an _ast property to hold a pointer into the associated fparser2 AST. However, the psyGen.Kern class already has an _fp2_ast property that points to the whole fparser2 AST of the kernel code. This will be rationalised in #241;