Search code examples
fortran

Create a constant based on subroutine argument


I have the following piece of code that splits the vector x into several arrays.

subroutine split(n, x, r, v, p, t)
    implicit none
    integer, intent(in) :: n
    double precision, intent(in) :: x(n)

    integer, parameter :: m = (n + 6) / 10
    ! problem here             1
    double precision, intent(out) :: r(3, m - 1)
    double precision, intent(out) :: v(3, m - 1)
    double precision, intent(out) :: p(3, m)
    double precision, intent(out) :: t(m)
    ! code
end subroutine split

This code does not compile with the message

Error: Parameter 'n' at (1) has not been declared or is a variable, which does not reduce to a constant expression

The code compiles fine if I manually change all m to (n + 6) / 10 but I am seeking a more elegant approach.

As an alternative approach I've rewritten the code as

subroutine splitcore(n, m, x, r, v, p, t)
    implicit none
    integer, intent(in) :: n, m
    double precision, intent(in) :: x(n)
    double precision, intent(out) :: r(3, m - 1)
    double precision, intent(out) :: v(3, m - 1)
    double precision, intent(out) :: p(3, m)
    double precision, intent(out) :: t(m)
    ! code
end subroutine splitcore

subroutine split(n, x, r, v, p, t)
    implicit none
    integer, intent(in) :: n
    double precision, intent(in) :: x(n)

    integer :: m
    double precision, intent(out) :: r(3, *)
    double precision, intent(out) :: v(3, *)
    double precision, intent(out) :: p(3, *)
    double precision, intent(out) :: t(*)

    m = (n + 6) / 10

    call splitcore(n, m, x, r, v, p, t)
end subroutine split

Solution

  • Array specifications for array declarations in subprograms are permitted to be specification expressions.

    A specification expression can include a reference to a pure function. You can use such a pure function to factor out the effective calculation of m.

    To be considered pure in its scope of use, an explicit interface for a pure function must be accessible. The simplest way of providing such an explicit interface is to put the function in a module (which, if split was already in such a module, could be the same module).

    module m_mod
      implicit none
    contains
      pure function m(n)
        integer, intent(in) :: n
        integer :: m
        m = (n + 6) / 10
      end function m
    end module m_mod
    
    subroutine split(n, x, r, v, p, t)
      use m_mod
      implicit none
      integer, intent(in) :: n
      double precision, intent(in) :: x(n)
    
      double precision, intent(out) :: r(3, m(n) - 1)
      double precision, intent(out) :: v(3, m(n) - 1)
      double precision, intent(out) :: p(3, m(n))
      double precision, intent(out) :: t(m(n))
      ...
    

    In terms of the original code - the initializer for a variable, constant or type parameter has to be a constant expression - effectively something that the compiler can evaluate at compile time. A constant expression has more restrictions on it than a specification expression - for example it cannot reference the value of a variable - because variables are not constants.

    Rather than an explicit shape array, the dummy variables in the split subroutine could perhaps be made assumed shape (declared with (:) or (:,:), as appropriate for the rank. The specification of the shape of the array is then taken "(assumed") from the shape of the actual arguments, no shape calculations need be done in subroutine split at all.

    Use of a subroutine with assumed shape dummy arguments requires an explicit interface for the procedure to be accessible in the scope where the procedure is referenced.