Smart way to pass arguments in Fortran 90

15,885

Solution 1

Your solution, making "o" an intent(out) argument is just fine. Without "o" being an argument there was no connection between the variable "o" in the subroutine and the variable "o" in the main program, and therefore there was no declaration or allocation of the one in the main program. Yet another solution (besides the one provided by @ja72) would be to alter your method: make "o" an intent(inout) argument of the subroutine and allocate it in the main program. Possible advantage: the allocate and deallocate are closer together in the code and paired. Possible disadvantage: depending on the the program logic and design, the array dimensions might be best known to the subroutine.

P.S. If you allocate the array in the main program, and don't actually use allocatable properties of the array in the subroutine (i.e., you don't allocate or deallocate it), then you don't have to declare it with the allocatable attribute in the subroutine -- a useful simplification. In which case "intent (out)" might be appropriate. But if you allocate the array in the main program and wish to pass that status to a subroutine, then the argument status can't be "intent (out)". "intent (out)" automatically deallocates the argument upon entry to the procedure.

Solution 2

It is not clear from your question if you know in advance the size of the array or not, but if you do, it is usually a good practice to allocate and deallocate the array in the same place, or even better is to leave the compiler allocate memory when possible.

How I would do:

  • if the size is known at compile time: Declare your array in the main program, use intent(out) in a subroutine.

  • if the size is known only at runtime: Allocate in the main program, use intent(out) in a subroutine and deallocate in the main program.

Functions are best suited for small outputs, since the outputs needs to be copied.

Solution 3

If you want to return an array you can a) add it to the arguments with INTENT(OUT) like your example #2 with allocation within the sub, or b) create a function and allocate the array externally:

FUNCTION myfun(w,x,y,z,n,m)
IMPLICIT NONE
INTEGER, INTENT(IN) :: n,m
REAL, DIMENSION(n,m) :: myfun
REAL, INTENT(IN) :: w,x,y,z

  myfun(1,1)=w
  myfun(1,2)=x
  myfun(2,1)=y
  myfun(2,2)=z

END FUNCTION
END MODULE testsubs

PROGRAM test
  USE testsubs
  IMPLICIT NONE
  REAL :: a=1.1, b=2.2, c=3.3, d=4.4
  REAL, DIMENSION(:,:), ALLOCATABLE :: o !...

  ALLOCATE(o(2,2))
  o  = myfun(a,b,c,d,2,2)

  PRINT *, o(1,1), o(1,2)
  PRINT *, o(2,1), o(2,2)
  DEALLOCATE(o) !...
END PROGRAM test

Actually I think your solution is cleaner.

Share:
15,885
Andrew
Author by

Andrew

Updated on June 25, 2022

Comments

  • Andrew
    Andrew almost 2 years

    I am a Fortran novice. I am trying to write a subroutine that will take in four arguments from the main program, and then outputs to the main program an array that involves the four arguments that were originally passed in. What is a good/smart way to do this?

    For example, in my test program below, I create four real variables (a, b, c, and d) in the main program. Then I pass these real variables to a subroutine called mysub. I would like mysub to be able to take in a, b, c, and d, use them to populate a 2-by-2 array called o, and then send o to the main program for displaying (and possible modification) there. So, I tried the following:

    SUBROUTINE mysub(w,x,y,z)
      IMPLICIT NONE
      REAL, INTENT(IN) :: w, x, y, z
      REAL, DIMENSION(:,:), ALLOCATABLE, INTENT(OUT) :: o
    
      ALLOCATE(o(2,2))
      o(1,1)=w
      o(1,2)=x
      o(2,1)=y
      o(2,2)=z
    END SUBROUTINE mysub
    END MODULE testsubs
    
    PROGRAM test
      USE testsubs
      IMPLICIT NONE
      REAL :: a=1.1, b=2.2, c=3.3, d=4.4
    
      CALL mysub(a, b, c, d)
    
      PRINT *, o(1,1), o(1,2)
      PRINT *, o(2,1), o(2,2)
    END PROGRAM test
    

    But, I get the following error:

    test.f90:10.53:
    
      REAL, DIMENSION(:,:), ALLOCATABLE, INTENT(OUT) :: o
                                                         1
    Error: Symbol at (1) is not a DUMMY variable
    

    I interpret this as, the compiler doesn't know what o is, because o is not in the list of arguments in the subroutine header: SUBROUTINE mysub(w,x,y,z). So I probably need to include o in that header. So, I next try the following (where I have denoted changes or additions using !...):

    SUBROUTINE mysub(w,x,y,z,o) !...
      IMPLICIT NONE
      REAL, INTENT(IN) :: w, x, y, z
      REAL, DIMENSION(:,:), ALLOCATABLE, INTENT(OUT) :: o
    
      ALLOCATE(o(2,2))
      o(1,1)=w
      o(1,2)=x
      o(2,1)=y
      o(2,2)=z
    END SUBROUTINE mysub
    END MODULE testsubs
    
    PROGRAM test
      USE testsubs
      IMPLICIT NONE
      REAL :: a=1.1, b=2.2, c=3.3, d=4.4
      REAL, DIMENSION(:,:), ALLOCATABLE :: o !...
    
      CALL mysub(a, b, c, d, o) !...
    
      PRINT *, o(1,1), o(1,2)
      PRINT *, o(2,1), o(2,2)
      DEALLOCATE(o) !...
    END PROGRAM test
    

    This seems to work fine, and I get the correct output:

       1.1000000       2.2000000
       3.3000000       4.4000001
    

    But, my question is, is this a good way to do this? In this working example, I'm declaring the array o both in the subroutine and in the main program. This seems potentially confusing, because I think that this means that I need to take care that either the subroutine or the main program allocates o (but not both, I think, in order to avoid error messages). Is there a smarter way to do this--to send an array from a subroutine to the main program? Thank you for your time.

  • Herman Toothrot
    Herman Toothrot over 3 years
    Does the order of the arguments inside the subroutine have to match the order in the way dummy arguments are passed? Example: REAL, INTENT(IN) :: z, y, x, w is acceptable?