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.

2
  • You say "first 4 indices" but use 1, 2, 4 and 5. Is this correct? Commented Dec 18, 2015 at 17:29
  • I would just keep the original. Especially if you have a reason to prefer the automatic array. Commented Dec 18, 2015 at 19:36

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

2 Comments

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?
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.
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) )

3 Comments

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.
It is better undef the macro before redefining it. Otherwise you may get lots of warnings.
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).
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.