Elemental statement provides a simple and generic interface for a procedure regarding its rank -dimensions-.

Due to Fortran strong typing programming style, It was a bit challenging to provide generic functions. It required to implement all possible forms (type and rank-dimensions-). Elemental focus in the rank issue with a simple interface.

Note an elemental procedure must be declared as scalar. So no arrays are allowed as input or output. The compiler takes care to deal with the output.

Furthermore, a function/subroutine becomes pure once elemental is defined. However it can also be set to impure (Useful when debugging as it allows to print :D).

Before

Check below how it used to be done to create a common interface for multiple dimensions -rank-. For instance, calculate procedure has implementations for scalar and arrays(…) to cover overall cases:

interface calculate
  module procedure calculate_scalar, calculate_i1, calculate_i2, ..., calculate_iX
end interface

function calculate_scalar(val1, val2)
  !< integer scalar implementation
  integer, intent(in) :: val1
  integer, intent(in) :: val2
  ...
end function calculate_scalar

function calculate_i1(val1, val2)
  !< integer array rank 1 implementation
  integer, intent(in) :: val1(:)
  integer, intent(in) :: val2(:)
  ...
end function calculate_i1

function calculate_i2(val1, val2)
  !< integer array rank 2 implementation
  integer, intent(in) :: val1(:,:)
  integer, intent(in) :: val2(:,:)
  ...
end function calculate_i2

...

Overall each form is manually written. In consequence, it is not practical at all.

Example

Let’s move on the next example. It shows how to implement elemental as pure (default) as well as impure.

program elemtest

  implicit none

  !< declare interface
  interface
    elemental integer function calculate(a, b)
      integer, intent(in) :: a, b
    end function calculate

    elemental impure integer function calculateImpure(a, b)
      integer, intent(in) :: a, b
    end function calculateImpure
  end interface

  integer :: val1(3), val2(3)
  !< compiler takes care to properly allocate 'results'
  integer, allocatable :: results(:)

  !< initialize values
  val1 = [1,2,3]
  val2 = [4,5,6]

  results = calculate(val1, val2)
  write (*,*) "results =", results

  results = calculateImpure(val1, val2)
  write (*,*) "(impure) results =", results
end program elemtest

elemental integer function calculate(a, b)
  !< apply naive operation
  !< interface implementation 
  integer, intent(in) :: a, b
  calculate = a + b - 1
end function calculate

elemental impure integer function calculateImpure(a, b)
  !< apply naive operation
  !< interface implementation for impure 
  integer, intent(in) :: a, b
  write(*,*) "(Impure) a, b = ", a, b
  calculateImpure = a + b - 1
end function calculateImpure

As expected, the output values between both functions are the same. On the other hand, the impure statement it allows to print some values when executed.

results =           4           6           8
(Impure) a, b =            1           4
(Impure) a, b =            2           5
(Impure) a, b =            3           6
(impure) results =           4           6           8

Careful

Finally, always provide the same length for the arguments. See below what happens in case the dimensions are different:

...
integer :: val1(3), val2(2)
!< compiler takes care to properly allocate 'results'
integer, allocatable :: results(:)

!< initialize values
val1 = [1,2,3]
val2 = [4,5] !< different size
...

Compiler error:

elem.f90:20:28:

20 | results = calculate(val1, val2)
   |                          1
Error: Different shape for elemental procedure at (1) on dimension 1 (2 and 3)

Source: Intel, IBM

Leave a Reply

Your email address will not be published. Required fields are marked *