Difference between revisions of "Guide for good coding"
(→How to create a new module/subroutine) |
|||
(31 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 | + | 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 | + | 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 as well as the units and everything that seems appropriate. | + | |
+ | - 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. | You can finally write the code. | ||
Line 25: | Line 31: | ||
IMPLICIT NONE | IMPLICIT NONE | ||
− | INTEGER :: water ! Define what is the variable, the dimension, units (°) etc... | + | INTEGER, protected :: water ! Define what is the variable, the dimension, units (°) etc... |
− | REAL,SAVE,ALLOCATABLE,DIMENSION(:) :: kre1 ! Another comment | + | ! 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 | CONTAINS | ||
Line 33: | Line 41: | ||
& really_anything) | & really_anything) | ||
− | use another_module_mod, only: | + | use another_module_mod, only: some_routine, some_variable |
!======================================================================= | !======================================================================= | ||
Line 80: | Line 88: | ||
! initialize rock | ! initialize rock | ||
rock = 2 | rock = 2 | ||
− | + | ENDIF !end firstcall | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | ENDDO !end of the loop nb_kre | + | ! 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 | ||
END SUBROUTINE aquarium | END SUBROUTINE aquarium | ||
Line 124: | Line 132: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == | + | == 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 == | == Naming variables convention == | ||
What is the naming convention. | What is the naming convention. | ||
− | In vdifc_mod for | + | In [[vdifc_mod]] for example. |
== Loop index convention == | == Loop index convention == | ||
− | Index name for loops | + | Index name for loops over: |
* '''physical grid''' : ig (max ngrid) | * '''physical grid''' : ig (max ngrid) | ||
* ''' vertical levels''' : l (max nlayer) | * ''' vertical levels''' : l (max nlayer) | ||
* '''tracer''' : iq (max nq) | * '''tracer''' : iq (max nq) | ||
* '''subslopes''' : islope (max nslope) | * '''subslopes''' : islope (max nslope) | ||
− | iaer | + | * '''aerosol''' : iaer (max naerkind) |
+ | * '''longitude''' : | ||
+ | * '''latitude''' : | ||
== Efficient loop coding == | == Efficient loop coding == | ||
− | The | + | The outermost loop index should correspond to the last index of an array. |
Example: | Example: | ||
Line 155: | Line 175: | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == How to use the | + | == How to use the INTENT == |
− | Argument of a function can come in 3 different way: IN, OUT, INOUT. | + | 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 == | ||
Line 163: | Line 187: | ||
* '''In english''' | * '''In english''' | ||
* '''As much as possible''' | * '''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... | * '''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 | * '''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. | * '''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. | * '''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 == | ||
− | * Create .F90 instead of .F | + | * Always check your modifications and additions by compiling and running in "debug" mode! |
− | * Delete unused argument in a function call if you see one | + | * 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 | * Delete unused variables | ||
− | * Choose meaningful names | + | * Choose meaningful names for variables and mention physical unit where they are declared |
− | * Don't hesitate to comment | + | * Don't hesitate to comment as much as possible, a code is never too commented |
* Delete unused commented code lines | * 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
Contents
- 1 How to create a new module/subroutine
- 2 Naming subroutines convention
- 3 When to create subroutines instead of line of code
- 4 How to pass variable to subroutine (by argument or by using a module)
- 5 Naming variables convention
- 6 Loop index convention
- 7 Efficient loop coding
- 8 How to use the INTENT
- 9 How to comment code
- 10 To know when coding with a code parallelized with OpenMP
- 11 General guide
- 12 External resources
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 thanc
(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: