Arrays#

Arrays are a central object in Fortran. The creation of dynamic sized arrays is discussed in the allocatable arrays section.

To pass arrays to procedures four ways are available

  1. assumed-shape arrays

  2. assumed-rank arrays

  3. explicit-shape arrays

  4. assumed-size arrays

The preferred way to pass arrays to procedures is as assumed-shape arrays

subroutine f(r)
  real(dp), intent(out) :: r(:)
  integer :: n, i
  n = size(r)
  do i = 1, n
    r(i) = 1.0_dp / i**2
  end do
end subroutine f

Higher-dimensional arrays can be passed in a similar way.

subroutine g(A)
  real(dp), intent(in) :: A(:, :)
  ...
end subroutine g

The array is simply passed by

real(dp) :: r(5)
call f(r)

In this case no array copy is done, which has the advantage that the shape and size information is automatically passed along and checked at compile and optionally at runtime. Similarly, array strides can be passed without requiring a copy of the array but as assumed-shape descriptor:

real(dp) :: r(10)
call f(r(1:10:2))
call f(r(2:10:2))

This should always be your default way of passing arrays in and out of subroutines. Avoid passing arrays as whole slices, as it obfuscates the actual intent of the code:

real(dp) :: r(10)
call f(r(:))

In case more general arrays should be passed to a procedure the assumed-rank functionality introduced in the Fortran 2018 standard can be used

subroutine h(r)
  real(dp), intent(in) :: r(..)
  select rank(r)
  rank(1)
  ! ...
  rank(2)
  ! ...
  end select
end subroutine h

The actual rank can be queried at runtime using the select rank construct. This easily allows to create more generic functions that have to deal with different array ranks.

Explicit-shape arrays can be useful for returning data from functions. Most of their functionality can be provided by assumed-shape and assumed-rank arrays but they find frequent use for interfacing with C or in legacy Fortran procedures, therefore they will be discussed briefly here.

To use explicit-shape arrays, the dimension has to be passed explicitly as dummy argument like in the example below

subroutine f(n, r)
  integer, intent(in) :: n
  real(dp), intent(out) :: r(n)
  integer :: i
  do i = 1, n
    r(i) = 1.0_dp / i**2
  end do
end subroutine

For high-dimensional arrays additional indices have to be passed.

subroutine g(m, n, A)
  integer, intent(in) :: m, n
  real(dp), intent(in) :: A(m, n)
  ...
end subroutine

The routines can be invoked by

real(dp) :: r(5), s(3, 4)
call f(size(r), r)
call g(size(s, 1), size(s, 2), s)

Note that the shape is not checked, so the following would be legal code that will potentially yield incorrect results:

real(dp) :: s(3, 4)
call g(size(s), 1, s)  ! s(12, 1) in g
call g(size(s, 2), size(s, 1), s)  ! s(4, 3) in g

In this case the memory layout is preserved but the shape is changed. Also, explicit-shape arrays require contiguous memory and will create temporary arrays in case non-contiguous array strides are passed.

To return an array from a function with explicit-shape use

function f(n) result(r)
  integer, intent(in) :: n
  real(dp) :: r(n)
  integer :: i
  do i = 1, n
    r(i) = 1.0_dp / i**2
  end do
end function

Finally, there are assumed-size arrays, which provide the least compile-time and run-time checking and can be found frequently in legacy code. They should be avoided in favour of assumed-shape or assumed-rank arrays. An assumed-size array dummy argument is identified by an asterisk as the last dimension, this disables the usage of this array with many intrinsic functions, like size or shape.

To check for the correct size and shape of an assumed-shape array the size and shape intrinsic functions can be used to query for those properties

if (size(r) /= 4) error stop "Incorrect size of 'r'"
if (any(shape(r) /= [2, 2])) error stop "Incorrect shape of 'r'"

Note that size returns the total size of all dimensions. To obtain the shape of a specific dimension add it as second argument to the function.

Arrays can be initialized by using an array constructor

integer :: r(5)
r = [1, 2, 3, 4, 5]

The array constructor can be annotated with the type of the constructed array

real(dp) :: r(5)
r = [real(dp) :: 1, 2, 3, 4, 5]

Implicit do loops can be used inside an array constructor as well

integer :: i
real(dp) :: r(5)
r = [(real(i**2, dp), i = 1, size(r))]

In order for the array to start with different index than 1, do:

subroutine print_eigenvalues(kappa_min, lam)
  integer, intent(in) :: kappa_min
  real(dp), intent(in) :: lam(kappa_min:)

  integer :: kappa
  do kappa = kappa_min, ubound(lam, 1)
    print *, kappa, lam(kappa)
  end do
end subroutine print_eigenvalues