Difference between revisions of "Guide for good coding"

From Planets
Jump to: navigation, search
(How to create a new module/subroutine)
 
(44 intermediate revisions by 5 users not shown)
Line 6: Line 6:
 
First you can define all the variable that belongs to the module and could be used at another place.
 
First you can define all the variable that belongs to the module and could be used at another place.
 
A good thing is also to create a subroutine that allocate and initialise module variables.
 
A good thing is also to create a subroutine that allocate and initialise module variables.
If the variable is only modified inside the module it should be taged as protected.
+
If the variable is only modified inside the module it should be tagged as protected.
 
If the variable belongs only to the module it can be specified to be private.
 
If the variable belongs only to the module it can be specified to be private.
  
You can now start the definition of the subroutine.
+
You can now start the writing of the subroutine.
Start by variable and function from other module if necessary.
+
 
Comment the purpose of the subroutine directly.
+
- Start by importing variable and function from other module if necessary.
Declare the variables, first the one pass in arguments.
+
 
 +
- Comment the purpose of the subroutine directly.
 +
 
 +
- Declare the variables, first the one pass in arguments.
 
Use the intent to specify the status of the variable inside the subroutine.
 
Use the intent to specify the status of the variable inside the subroutine.
Continue with the local and saved variables.
+
 
Then you can define local variables.
+
- Continue with the local and saved variables.
Remember to comment all of them, you can specify the dimension if the variable is an allocatable variable.
+
 
 +
- Then you can define local variables.
 +
 
 +
- Remember to comment all of them, you can specify the dimension if the variable is an allocatable variable as well as the units and everything that seems appropriate.
 +
 
 +
You can finally write the code.
  
 
<syntaxhighlight lang="Fortran" line>
 
<syntaxhighlight lang="Fortran" line>
      MODULE aquarium_mod
+
    MODULE aquarium_mod
  
 +
    IMPLICIT NONE
 +
 +
    INTEGER, protected :: water    ! Define what is the variable, the dimension, units (°) etc...
 +
    ! ideally all module variables should be "protected" i.e. can only be set or modified
 +
    ! by routines/functions in the same module but can be used outside of it (as "read-only")
 +
    REAL,SAVE,ALLOCATABLE,DIMENSION(:),PROTECTED :: kre1      ! Another comment
 +
 +
    CONTAINS
 +
 +
      SUBROUTINE aquarium(kre, grass, other_things
 +
    &                    really_anything)
 +
                                                       
 +
      use another_module_mod, only: some_routine, some_variable
 +
     
 +
!=======================================================================
 +
!  subject:
 +
!  --------
 +
!  What is the subroutine doing
 +
!
 +
!  author: Someone smart
 +
!  ------
 +
!  update: Someone smarter, march 2012:
 +
!        - I change something here
 +
 +
!=======================================================================
 +
     
 
       IMPLICIT NONE
 
       IMPLICIT NONE
  
       INTEGER :: water     ! Define what is the variable, the dimension, units etc..
+
!-----------------------------------------------------------------------
       REAL,SAVE,ALLOCATABLE,DIMENSION(:) :: kre1      ! Another comment (°)
+
!=======================================================================
 +
!    Declarations :
 +
!=======================================================================
 +
!
 +
!    Input/Output
 +
!    ------------
 +
       REAL, INTENT(IN)    ::  something(ngrid,nlayer) ! 2D tabular used as an input only  (W/m^2)
 +
      REAL, INTENT(OUT)    ::  kre(ngrid)              ! 1D tabular output by the subroutine 
 +
      REAL, INTENT(INOUT)  :: grass(ngrid,nlayer)     ! Variable that is modified by the subroutine
 +
                                                      ! it is an input and output
 +
      LOGICAL, INTENT(IN)  ::  really_anything        ! Boolean variable
 +
 
 +
!    Local saved variables
 +
!    ---------------------
 +
      INTEGER, PARAMETER :: nb_kre = 4                ! Number of kre
 +
      INTEGER, SAVE      :: rock                      ! Another variable
 +
      LOGICAL, SAVE      :: firstcall=.true.         ! Another variable
 +
 
 +
!    Local variables
 +
!    ---------------
 +
       REAL   tabular(ngrid)                          ! Local var
 +
      INTEGER i                                      ! Loop var
 +
 
 +
!=======================================================================
 +
!  Beginning of the code
 +
!=======================================================================
 +
 
 +
      IF (firstcall) THEN
 +
        ! initialize rock
 +
        rock = 2
 +
      ENDIF !end firstcall
 +
 
 +
      ! identify where are the kre
 +
      DO i=1,nb_kre
 +
        kre(i)=i
 +
        if (kre(i).gt.rock) then
 +
          write(*,*) "the Kre ", i, "is behind the rock"
 +
        else if (mod(kre(i),2).eq.0) then
 +
          grass(:,nlayer-1)=grass(:,nlayer-1)-grass(:,nlayer) ! every two kre are eating grass in a very complex way
 +
        endif !(kre(i).gt.rock)
 +
      ENDDO !end of the loop nb_kre
  
       CONTAINS
+
       END SUBROUTINE aquarium
  
c===== subroutine 1
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  
 
       SUBROUTINE ini_aquarium(ngrid)
 
       SUBROUTINE ini_aquarium(ngrid)
c=======================================================================
+
!=======================================================================
c  subject:
+
!   Initialise module's variable
c  --------
+
!=======================================================================
c   Initialise module s variable
 
c
 
c  author: Someone 
 
c=======================================================================
 
 
       IMPLICIT NONE
 
       IMPLICIT NONE
 
       INTEGER, INTENT(IN) :: ngrid
 
       INTEGER, INTENT(IN) :: ngrid
Line 45: Line 115:
 
       END SUBROUTINE ini_aquarium
 
       END SUBROUTINE ini_aquarium
  
c===== subroutine 2
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
  
 
       SUBROUTINE end_aquarium
 
       SUBROUTINE end_aquarium
c=======================================================================
+
!=======================================================================
c  subject:
+
!   Deallocate module's variable
c  --------
+
!=======================================================================
c   Deallocate module s variable
 
c
 
c  author: Someone 
 
c=======================================================================
 
  
 
       IMPLICIT NONE
 
       IMPLICIT NONE
Line 61: Line 127:
  
 
       END SUBROUTINE end_aquarium
 
       END SUBROUTINE end_aquarium
 
c===== subroutine 3
 
 
      SUBROUTINE aquarium(kre, grass, other_things
 
    &  really_anything)
 
                                                       
 
      use another_module_mod, only: fct, variable
 
     
 
c=======================================================================
 
c  subject:
 
c  --------
 
c  What is the subroutine doing
 
c
 
c  author: Someone smart
 
c  ------
 
c  update: Someone smarter, march 2012:
 
c        - I change something here
 
 
c=======================================================================
 
 
        
 
        
      IMPLICIT NONE
+
    END MODULE aquarium_mod
  
c-----------------------------------------------------------------------
+
</syntaxhighlight>
c
 
c    Declarations :
 
c    --------------
 
c
 
c    Input/Output
 
c    ------------
 
  
      REAL, INTENT(IN)    ::  something(ngrid,nlayer) ! 2D tabular used as an input only  (W/m^2)
+
== Naming subroutines convention ==
      REAL, INTENT(OUT)  ::  kre(ngrid)              ! 1D tabular output by the subroutine 
 
      REAL, INTENT(INOUT) ::  grass(ngrid,nlayer)    ! Variable that is modified by the subroutine
 
                                                      ! it is an input and output
 
      LOGICAL,INTENT(IN)  ::  really_anything        ! Boolean variable 
 
 
 
c    Local saved variables :
 
c    -----------------
 
      INTEGER, PARAMETER :: nb_kre = 6                ! Number of kre
 
      INTEGER, SAVE      :: rock                      ! Another variable
 
      LOGICAL,SAVE :: firstcall=.true.                ! Another variable
 
  
c    Local variables :
+
Un peu plus pour le PEM peut-être
c    -----------------
 
      REAL tabular(ngrid)                            ! Local var
 
      INTEGER i                                      ! Loop var
 
  
c    Beginning of the code :
+
== When to create subroutines instead of line of code ==
c    -----------------
 
  
      IF (firstcall) THEN
+
Quand est ce qu'on décide que ca vaut le coup de faire une routine spécifique : exemple négativité des pdq dans physiq_mod
        ! identify where are the kre
 
        DO i=1,nb_kre
 
          kre(i)=i
 
          write(*,*) "the Kre ", i, "is behind the rock"
 
        ENDDO !end of the loop nb_kre
 
      ENDIF  !end firstcall
 
  
      END SUBROUTINE aeropacity
+
== How to pass variable to subroutine (by argument or by using a module) ==
     
 
      END MODULE aeropacity_mod
 
  
</syntaxhighlight>
+
Base rule: variables should be passed down to routines via arguments. It makes it much easier for readers to follow what's going on and/or do the reverse engineering...
  
== How to pass variable to subroutine (by argument or by module) ==
+
One should only use (import) a variable from a module if it used as "read-only" information. e.g. gravity constant, aerosol optical properties, etc.
  
 
== Naming variables convention ==
 
== Naming variables convention ==
 +
 +
What is the naming convention.
 +
In [[vdifc_mod]] for example.
  
 
== Loop index convention ==
 
== Loop index convention ==
 +
 +
Index name for loops over:
 +
* '''physical grid''' : ig (max ngrid)
 +
* ''' vertical levels''' : l (max nlayer)
 +
* '''tracer''' : iq (max nq)
 +
* '''subslopes''' : islope (max nslope)
 +
* '''aerosol''' : iaer (max naerkind)
 +
* '''longitude''' :
 +
* '''latitude''' :
  
 
== Efficient loop coding ==
 
== Efficient loop coding ==
  
== How to use the Intent ==
+
The outermost loop index should correspond to the last index of an array.
 +
 
 +
Example:
 +
<syntaxhighlight lang="Fortran">
 +
      DO l=1,nlayer
 +
        DO ig=1,ngrid
 +
          array(ig,l)=l*ig
 +
        ENDDO
 +
      ENDDO
 +
</syntaxhighlight>
 +
 
 +
== How to use the INTENT ==
 +
 
 +
Argument of a routine/function can come in 3 different way: IN, OUT, INOUT.
 +
All arguments should be specified as :
 +
* intent(in) : input only, i.e. not modified by the routine (or routines it passes the argument on to)
 +
* intent(out) : output only, i.e. the value is set/computed in the routine (or via a routine it calls)
 +
* intent(inout) : the combination of the two
  
 
== How to comment code ==
 
== How to comment code ==
 +
 +
* '''In english'''
 +
* '''As much as possible'''
 +
* '''By using <code>!</code>''' (also known as the "bang" character) rather than <code>c</code> (which is not interpreted as a comment if in free form, e.g. .F90 files)
 +
* '''Variable definition''' : explain what it is, the dimension, the units etc...
 +
* '''Subroutine''' : Explain what it does, who wrote it, who modify it and why if it is a big change
 +
* '''Loops''' : if the enddo is far away you can comment to which index it correspond to.
 +
* '''Chapter your code''' : you can add section to your code to make it clearer.
 +
 +
== To know when coding with a code parallelized with OpenMP ==
 +
In Fortran, any variable from a <code>module</code>, written in a <code>COMMON</code>, declared <code>SAVE</code> or initialized at the declaration (using <code>DATA</code> or <code>=</code>) is considered a ''static variable''.
 +
When your code is parallelized using OpenMP, these static variables are SHARED by default. '''For the PCM physics, in 90% cases, you want each thread on which the code is parallelized to have its own private version of the variable''', so you should add, after the declaration of your variable, the following line:
 +
<syntaxhighlight lang="Fortran">
 +
!$OMP THREADPRIVATE(variable)
 +
</syntaxhighlight>
  
 
== General guide ==
 
== General guide ==
 +
 +
* Always check your modifications and additions by compiling and running in "debug" mode!
 +
* Create .F90 (i.e. free form) instead of .F (i.e. fixed form); but this doesn't mean you should make lines too long!
 +
* Delete unused argument in a function/routine call if you see one
 +
* Delete unused variables
 +
* Choose meaningful names for variables and mention physical unit where they are declared
 +
* Don't hesitate to comment as much as possible, a code is never too commented
 +
* Delete unused commented code lines
 +
 +
== External resources ==
 +
 +
You can check these pages for some guidelines and advice:
 +
* https://www.fortran90.org/src/best-practices.html
 +
* https://fortran-lang.org/en/learn/best_practices/
 +
* https://wvuhpc.github.io/Modern-Fortran/11-Best-Practices/index.html
 +
 +
[[Category:Generic-Model]]
 +
[[Category:Mars-Model]]
 +
[[Category:Venus-Model]]
 +
[[Category:Titan-Model]]

Latest revision as of 12:17, 23 October 2023

How to create a new module/subroutine

Subroutines should be integrated inside a module. A good writing habit is to add the line "IMPLICIT NONE", in the beginning of the module and subroutine.

First you can define all the variable that belongs to the module and could be used at another place. A good thing is also to create a subroutine that allocate and initialise module variables. If the variable is only modified inside the module it should be tagged as protected. If the variable belongs only to the module it can be specified to be private.

You can now start the writing of the subroutine.

- Start by importing variable and function from other module if necessary.

- Comment the purpose of the subroutine directly.

- Declare the variables, first the one pass in arguments. Use the intent to specify the status of the variable inside the subroutine.

- Continue with the local and saved variables.

- Then you can define local variables.

- Remember to comment all of them, you can specify the dimension if the variable is an allocatable variable as well as the units and everything that seems appropriate.

You can finally write the code.

  1     MODULE aquarium_mod
  2 
  3     IMPLICIT NONE
  4 
  5     INTEGER, protected :: water     ! Define what is the variable, the dimension, units (°) etc...
  6     ! ideally all module variables should be "protected" i.e. can only be set or modified
  7     ! by routines/functions in the same module but can be used outside of it (as "read-only")
  8     REAL,SAVE,ALLOCATABLE,DIMENSION(:),PROTECTED :: kre1      ! Another comment 
  9 
 10     CONTAINS
 11 
 12       SUBROUTINE aquarium(kre, grass, other_things
 13      &                    really_anything)
 14                                                          
 15       use another_module_mod, only: some_routine, some_variable
 16       
 17 !=======================================================================
 18 !   subject:
 19 !   --------
 20 !   What is the subroutine doing
 21 !
 22 !   author: Someone smart 
 23 !   ------
 24 !   update: Someone smarter, march 2012:
 25 !         - I change something here
 26 !   
 27 !=======================================================================
 28       
 29       IMPLICIT NONE
 30 
 31 !-----------------------------------------------------------------------
 32 !=======================================================================
 33 !    Declarations :
 34 !=======================================================================
 35 !
 36 !    Input/Output
 37 !    ------------
 38       REAL, INTENT(IN)     ::  something(ngrid,nlayer) ! 2D tabular used as an input only  (W/m^2)
 39       REAL, INTENT(OUT)    ::  kre(ngrid)              ! 1D tabular output by the subroutine  
 40       REAL, INTENT(INOUT)  ::  grass(ngrid,nlayer)     ! Variable that is modified by the subroutine
 41                                                        ! it is an input and output
 42       LOGICAL, INTENT(IN)  ::  really_anything         ! Boolean variable  
 43 
 44 !    Local saved variables
 45 !    ---------------------
 46       INTEGER, PARAMETER :: nb_kre = 4                ! Number of kre
 47       INTEGER, SAVE      :: rock                      ! Another variable
 48       LOGICAL, SAVE      :: firstcall=.true.          ! Another variable
 49 
 50 !    Local variables
 51 !    ---------------
 52       REAL    tabular(ngrid)                          ! Local var
 53       INTEGER i                                       ! Loop var
 54 
 55 !=======================================================================
 56 !   Beginning of the code
 57 !=======================================================================
 58 
 59       IF (firstcall) THEN
 60         ! initialize rock
 61         rock = 2
 62       ENDIF !end firstcall
 63 
 64       ! identify where are the kre
 65       DO i=1,nb_kre
 66         kre(i)=i
 67         if (kre(i).gt.rock) then
 68           write(*,*) "the Kre ", i, "is behind the rock"
 69         else if (mod(kre(i),2).eq.0) then
 70           grass(:,nlayer-1)=grass(:,nlayer-1)-grass(:,nlayer) ! every two kre are eating grass in a very complex way
 71         endif !(kre(i).gt.rock)
 72       ENDDO !end of the loop nb_kre
 73 
 74       END SUBROUTINE aquarium
 75 
 76 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 77 
 78       SUBROUTINE ini_aquarium(ngrid)
 79 !=======================================================================
 80 !   Initialise module's variable
 81 !=======================================================================
 82       IMPLICIT NONE
 83       INTEGER, INTENT(IN) :: ngrid
 84 
 85       allocate(kre1(ngrid+1))
 86 
 87       END SUBROUTINE ini_aquarium
 88 
 89 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
 90 
 91       SUBROUTINE end_aquarium
 92 !=======================================================================
 93 !   Deallocate module's variable
 94 !=======================================================================
 95 
 96       IMPLICIT NONE
 97 
 98       IF (allocated(kre1)) deallocate(kre1)
 99 
100       END SUBROUTINE end_aquarium
101       
102     END MODULE aquarium_mod

Naming subroutines convention

Un peu plus pour le PEM peut-être

When to create subroutines instead of line of code

Quand est ce qu'on décide que ca vaut le coup de faire une routine spécifique : exemple négativité des pdq dans physiq_mod

How to pass variable to subroutine (by argument or by using a module)

Base rule: variables should be passed down to routines via arguments. It makes it much easier for readers to follow what's going on and/or do the reverse engineering...

One should only use (import) a variable from a module if it used as "read-only" information. e.g. gravity constant, aerosol optical properties, etc.

Naming variables convention

What is the naming convention. In vdifc_mod for example.

Loop index convention

Index name for loops over:

  • physical grid : ig (max ngrid)
  • vertical levels : l (max nlayer)
  • tracer : iq (max nq)
  • subslopes : islope (max nslope)
  • aerosol : iaer (max naerkind)
  • longitude :
  • latitude :

Efficient loop coding

The outermost loop index should correspond to the last index of an array.

Example:

      DO l=1,nlayer
        DO ig=1,ngrid
          array(ig,l)=l*ig
        ENDDO
      ENDDO

How to use the INTENT

Argument of a routine/function can come in 3 different way: IN, OUT, INOUT. All arguments should be specified as :

  • intent(in) : input only, i.e. not modified by the routine (or routines it passes the argument on to)
  • intent(out) : output only, i.e. the value is set/computed in the routine (or via a routine it calls)
  • intent(inout) : the combination of the two

How to comment code

  • In english
  • As much as possible
  • By using ! (also known as the "bang" character) rather than c (which is not interpreted as a comment if in free form, e.g. .F90 files)
  • Variable definition : explain what it is, the dimension, the units etc...
  • Subroutine : Explain what it does, who wrote it, who modify it and why if it is a big change
  • Loops : if the enddo is far away you can comment to which index it correspond to.
  • Chapter your code : you can add section to your code to make it clearer.

To know when coding with a code parallelized with OpenMP

In Fortran, any variable from a module, written in a COMMON, declared SAVE or initialized at the declaration (using DATA or =) is considered a static variable. When your code is parallelized using OpenMP, these static variables are SHARED by default. For the PCM physics, in 90% cases, you want each thread on which the code is parallelized to have its own private version of the variable, so you should add, after the declaration of your variable, the following line:

!$OMP THREADPRIVATE(variable)

General guide

  • Always check your modifications and additions by compiling and running in "debug" mode!
  • Create .F90 (i.e. free form) instead of .F (i.e. fixed form); but this doesn't mean you should make lines too long!
  • Delete unused argument in a function/routine call if you see one
  • Delete unused variables
  • Choose meaningful names for variables and mention physical unit where they are declared
  • Don't hesitate to comment as much as possible, a code is never too commented
  • Delete unused commented code lines

External resources

You can check these pages for some guidelines and advice: