SlapGas Unseen
SlapGas Unseen

Reputation: 41

Fortran - Allocatable array of allocatable derived type

So I've been searching for the past 3-4 days and I could not find the answer to this problem. It has to do with allocatable arrays which are of a specific derived type. This is all part of a Computational Fluid Dynamics solver. However, the actual application does not matter. Let me provide some (quick) programming context.

Let's say we have a simple module that defines a derived type of fixed size and the main program that allocates an array of a number of types:

module types

   integer, parameter  :: equations = 10

   type array_t
      double precision :: variable(equations) ! variables to solve
      double precision :: gradient(equations,3) ! gradient of variables in x,y,z direction
      double precision :: limiter(equations) ! limiter of variables
   end type

end module

program test

   use types

   implicit none

   type(array_t), allocatable :: array(:)
   integer :: elements

   elements = 100
   allocate(array(elements))

end program

This code snippet can, of course, be compiled using every compiler. Since the array_t size is fixed and known in compile time, we only need to allocate the struct array in a single line (defining the number of array_t repetitions inside the struct).

When it comes to memory locations, the variables will be stored as follows:

array(1)%variable(1) ! element 1
array(1)%variable(2)
...
...
array(1)%gradient(1,1) ! the rest of this 2D array will be written column-major in fortran
array(1)%gradient(2,1)
array(1)%gradient(3,1)
...
...
array(1)%limiter(1)
array(1)%limiter(2)
...
...
array(2)%variable(1) ! element 2
array(2)%variable(2)
...
...

In this example, we set the parameter equations=10. In the solver, we always leave this size at the maximum (10): all derived types have the maximum dimension that the solver may require. Unfortunately, this means that we may allocate more memory than we actually need -some simulations may only need 5 or 6 equations instead of 10-. In addition, the fact that the derived type dimension stays fixed at size 10 makes the solver slower when we solve for fewer equations -the non-utilized memory locations will reduce memory bandwidth-.

What I want to do is utilize derived types that have the allocatable attribute. This way, I can allocate the structs using only the required number of equations (i.e. dimensions of array_t) which will be defined at runtime (not at compile time) and will change based on the simulation parameters.

Take a look at the following code snippet:

module types

   integer, save:: equations

   type array_t
      double precision, allocatable :: variable(:) ! variables to solve
      double precision, allocatable :: gradient(:,:) ! gradient
      double precision, allocatable :: limiter(:) ! limiter of variables
   end type

end module

program test

   use types

   implicit none

   type(array_t), allocatable :: array(:)
   integer :: i,elements

   equations = 10
   elements = 100
   allocate(array(elements))
   do i=1,elements
      allocate(array(i)%variable(equations))
      allocate(array(i)%gradient(equations,3))
      allocate(array(i)%limiter(equations))
   enddo

end program

This is, thus far, the only way I managed to make it work. The solver runs and converges, which means that syntax is not only compilable but also equivalent to using a fixed size.

However, the solver is significantly slower with this approach, even for the same number of equations.

This means that there is memory misalignment. Based on measured runtimes, it seems like the variables are not stored in the same manner as when using the fixed size.

In the second approach, how are variables stored in global memory? I want to achieve the same pattern as the first approach. I feel like the first line which allocates the struct

allocate(array(elements))

does not know what to allocate so either it allocates a large chunk of memory (to fit the allocatable type that will come later on) or just allocates the indexes array(1) to array(elements) and nothing else (which means that the actual content of the struct is stored later, inside the loop).

How can I make the second approach store the variables like the first approach?

EDIT #1

Since the parameterized derived types got some traction, I figured it would be useful to post some additional details.

Parameterized derived types will work in scenarios where the array is allocated inside the main program (like the sample code I posted).

However, my "real world" case is more like the following:

(file_modules.f90)
module types

   integer, parameter  :: equations = 10

   type array_t
      double precision :: variable(equations) ! variables to solve
      double precision :: gradient(equations,3) ! gradient pf variables
      double precision :: limiter(equations) ! limiter of variables
   end type

end module

module flow_solution
   use types
   type (array_t), allocatable, save :: cell_solution(:)
end module

(file_main.f90)
program test

   use flow_solution

   implicit none
   integer :: elements

   elements = 100
   allocate(cell_solution(elements))

end program

These (as you'd expect) are compiled and linked separately via a Makefile. If I used a parameterized derived type, the module file cannot be compiled because the size 'n' of the type is not known at compile time.

EDIT #2

I was advised to provide examples of working and non-working code with parameterized derived types.

Working example:

module types

   integer, parameter  :: equations=10

   type array_t(n)
      integer, len     :: n
      double precision :: variable(n) ! variables to solve
      double precision :: gradient(n,n) ! gradient
      double precision :: limiter(n) ! limiter of variables
   end type

end module

module flow_solution
    use types
    type(array_t(equations)), allocatable, save :: flowsol(:)
end module

program test

   use flow_solution

   implicit none

   integer :: i,elements

   elements = 100 
   allocate(flowsol(elements))

end program

Non-working example:

module types

   integer, save       :: equations

   type array_t(n)
      integer, len     :: n
      double precision :: variable(n) ! variables to solve
      double precision :: gradient(n,n) ! gradient
      double precision :: limiter(n) ! limiter of variables
   end type

end module

module flow_solution
    use types
    type(array_t(equations)), allocatable, save :: flowsol(:)
end module

program test

   use flow_solution

   implicit none

   integer :: i,elements

   equations = 10
   elements = 100 
   allocate(flowsol(elements))

end program

Compiler (ifort) error:

test.f90(16): error #6754: An automatic object must not appear in a SAVE statement or be declared with the SAVE attribute.   [FLOWSOL]
    type(array_t(equations)), allocatable, save :: flowsol(:)
---------------------------------------------------^
test.f90(16): error #6841: An automatic object must not appear in the specification part of a module.   [FLOWSOL]
    type(array_t(equations)), allocatable, save :: flowsol(:)
---------------------------------------------------^
compilation aborted for test.f90 (code 1)

Should I declare/allocate the arrays in a different way?

Upvotes: 4

Views: 2326

Answers (1)

francescalus
francescalus

Reputation: 32366

The troublesome line is

type(array_t(equations)), allocatable, save :: flowsol(:)

Here, you want a length-parameterized type to be a saved object. As the compiler objects, a saved object cannot be an automatic object. Also, you cannot have an automatic object in the module's scope.

Now, why is flowsol an automatic object? It's automatic because its type is array_t(equations): equations is not a constant. Instead, you want the type parameter to be deferred (much as the array's shape is):

type(array_t(:)), allocatable, save :: flowsol(:)

When you come to allocate such an object you need to provide the length type parameter value:

allocate(array_t(equations) :: flowsol(elements))

As you see, when equations is a (named) constant, things are happier. In such a case an automatic object is not declared.

Upvotes: 2

Related Questions