3

In my code, I have a subroutine that takes a 5th-rank array as argument and uses a local variable, which is a 4-th rank array sharing the first 4 indices.

I'm trying to find a more concise way to express the size declaration in

subroutine mysub(momentum)
  complex, intent(in) :: momentum(:,:,:,:,:)
  complex :: prefactor( &
      & size(momentum,1), size(momentum,2), size(momentum,4) &
      & size(momentum,5) )
  ...
end subroutine mysub

The verbosity of the size declaration harms readability, especially when variable names are even longer than here.

If this was octave/matlab I'd pre-allocate prefactor by writing

prefactor = zeros(size(momentum)([1 2 4 5]))

Does Fortran 90 support something similarly concise? I know that it could be solved using preprocessor macros, such as

#define XSIZE2(array,a,b) SIZE(array,a), SIZE(array,b)
#define XSIZE3(array,a,b,c) SIZE(array,a), SIZE(array,b), SIZE(array,c)
#define XSIZE4(array,a,b,c,d) SIZE(array,a), SIZE(array,b), SIZE(array,c), SIZE(array,d)

but introducing such definitions would probably harm the readability more than it helps.

kdb
  • 4,098
  • 26
  • 49

3 Answers3

3

Fortran 2008 added the mold specifier to the allocate statement. If you have access to a compiler that supports this feature, you can try

program main

  implicit none

  integer :: a(2,3,4,5,6)
  integer, allocatable :: b(:,:,:,:)

  print *, shape(a)

  allocate(b, mold=a(:,:,:,:,1))
  print *, shape(b)

end program main

This snippet worked with Intel Fortran 2016, Update 1.

pch
  • 72
  • 3
  • Sadly not applicable for me -- I have to stick with Fortran 90 in the project. I don't think there will be willingness to change the handwritten makefiles, just so I can use more convenient syntax in some places. Also, I'd prefer using the derived shapes without `allocatable` variables. I can write `integer :: b(size(a,1), size(a,2), size(a,3), 5)` if a is an assumed-shape array argument to the current subroutine. Is it possible to use the `mold` syntax there too? – kdb Dec 18 '15 at 10:46
  • I am not sure I understand your concern about makefiles. As long as you are using a newer version of the compiler, your makefile macro (FC, F77, F90) will pick it up. Also the `mold` specifier works only with `allocatable`. And I agree with @francescalus - the original code that you posted seems fine to me. – pch Dec 19 '15 at 02:15
1

Although this may be more of a comment, how about defining a macro like this...?

    subroutine mysub(momentum)
    complex, intent(in) :: momentum(:,:,:,:,:)
#define _(i) size( momentum, i )
    complex :: prefactor( _(1), _(2), _(4), _(5) )

It could also be defined repeatedly for different arguments, for example:

    subroutine mysub( momentum, coeff )
    complex, intent(in) :: momentum(:,:,:,:,:), coeff(:,:,:)
#define _(i) size( momentum, i )
    complex :: prefactor_momentum( _(1), _(2), _(4), _(5) )
#define _(i) size( coeff, i )
    complex :: prefactor_coeff( _(1), _(3) )

If it is OK to use an allocatable array, I may allocate it as follows:

subroutine sub( momentum )
    complex, intent(in) :: momentum(:,:,:,:,:)
    complex, allocatable :: prefactor(:,:,:,:)
    integer :: d( 5 )

    d = shape( momentum )
    allocate( prefactor( d(1), d(2), d(4), d(5) ) )

To obtain a combined macro for several different arguments, it might be useful to try this approach:

#define dim2(A,i1,i2)       size(A,i1), size(A,i2)
#define dim3(A,i1,i2,i3)    size(A,i1), size(A,i2), size(A,i3)
#define dim4(A,i1,i2,i3,i4) size(A,i1), size(A,i2), size(A,i3), size(A,i4)

#define _dims(A,_1,_2,_3,_4,NAME,...) NAME
#define getdims(A,...) _dims(A, __VA_ARGS__, dim4, dim3, dim2)(A,__VA_ARGS__)

subroutine mysub( momentum )
    complex, intent(in) :: momentum(:,:,:,:,:)
    complex :: prefactor2( getdims( momentum, 1, 5 ) )
    complex :: prefactor3( getdims( momentum, 1, 3, 5 ) )
    complex :: prefactor4( getdims( momentum, 1, 2, 4, 5 ) )

which translates (by cpp -P) to

   ...
   complex :: prefactor2( size(momentum,1), size(momentum,5) )
   complex :: prefactor3( size(momentum,1), size(momentum,3), size(momentum,5) )
   complex :: prefactor4( size(momentum,1), size(momentum,2), size(momentum,4), size(momentum,5) )
Community
  • 1
  • 1
roygvib
  • 7,218
  • 2
  • 19
  • 36
  • Thanks for the answer, but this still leaves me with added obscurity of the code. If I'm using macros, I may as well use the `XSIZE` macros from my question. – kdb Dec 18 '15 at 10:48
  • It is better undef the macro before redefining it. Otherwise you may get lots of warnings. – Vladimir F Героям слава Dec 18 '15 at 12:15
  • Yeah, I totally agree with your both comments. My "answer" above is essentially no more than a comment ;) (And yes, we have lots of warnings :) I also think the "mold" approach would be best (if your compiler supports it). – roygvib Dec 18 '15 at 13:05
1

If I cared about this conciseness then I would be tempted to go with the approach of using the mold= specifier with allocatable local variables. This syntax is reasonably well supported in all modern compilers and should easily slot in to the build process.

However, commenting on that answer you say you prefer "derived shapes" rather than allocatable local variables. Let's leave aside that there's little difference in the ultimate use (there are some) of these and explore that aspect.

By "derived shape" you mean an explicit shape automatic object. For such an object the extents of each rank must be specification expressions. You use SIZE(momentum,1) etc., as these expressions.

Where you suffer in the syntax is that each rank's extent must be given distinctly. So, there really is no prospect for anything shorter such as

complex prefactor(array_extents_spec)  ! As an array, say.

However, we can do other things if we again ignore the Fortran 90 requirement.

Consider an automatic object

complex, target :: prefactor_t(SIZE(momentum)/SIZE(momentum,3))  ! rank-1, of desired size

and the array

integer extents(4)
extents = [SIZE(momentum,1), SIZE(momentum,2), SIZE(momentum,4), SIZE(momentum,5)]

we can have a pointer object with bounds remapping

complex, pointer :: prefactor(:,:,:,:)
prefactor(1:extents(1), 1:extents(2), 1:extents(3), 1:extents(4)) => prefactor_t

which may be a little neater, and even shorter if we call that extents array e instead.

Using the same idea of using an array for an automatic object's extents, we can use a block construct which allows for automatic objects after executable statements

complex, intent(in) :: momentum(:,:,:,;,:)
integer e(5)
e = SHAPE(momentum)
block
  complex prefactor(e(1), e(2), e(4), e(5))  ! Automatic, of desired shape
end block

The danger of all of this is making things much more obscure just to make one declaration a little neater. In summary, mold= really is the way to go if you want something tidier than your original. But I don't see your original code as being particularly unclear. None of the other suggestions here seems better to me - but feel free to take your pick.

francescalus
  • 30,576
  • 16
  • 61
  • 96