Date post: | 11-Jan-2016 |
Category: |
Documents |
Upload: | joella-francis |
View: | 218 times |
Download: | 0 times |
Guidelines for the CMM coding project
5 October 2006
(or, “How to make your life easier in the long run”)
Guiding philosophies of this advice…
I. A good model code is easy to read and trouble shoot… you can find what you’re looking for quickly and you can see what each line of code is supposed to be doing
II. A good model code preserves forward compatibility, so that as we add new features, we don’t have to reinvent the wheel
1. Start each and every program and subroutine with…
IMPLICIT NONE
2. Define physical constants and model configurations in parameter statements, not in the body of the code!
INTEGER NTPARAMETER(NT=1000)
This prevents accidental overwriting of a variable somewhere else in the code
3. Determine whether a variable needs to be an integer, a single precision real, or a double precision real, and declare it properly
INTEGER NT
PARAMETER(NT=1000)
REAL DT
PARAMETER(DT=2.0)
DOUBLE PRECISION PI
PARAMETER(PI=3.14159265359D+00)
4. Along with this, remember that for many compilers, the product of two kinds of variables is restricted by the less precise variable
INTEGER I
REAL A, B
I=2
A=2.2
B=A*I
B=A*REAL(I)
B may be = 4 on some compilers
(On many compilers, IMPLICIT NONE alleviates this problem)
5. Comment your code heavily!
Our goal in this class is to understand every line of code in our model… believe it or not, in a week or a month, you will forget why you coded something in a particular way, or you will forget what a particular line of code is meant to do.
6. Call your variables and subroutines by names that bring to mind what they represent!
EPS
ASSELINCOEF
We all like to save keystrokes and keep our code compact, but which of these is unambiguous?
EPSILON
7. Similarly, make your calculations look like the equations you’re trying to represent
Often it’s possible to really combine and condense a lot of coefficients into one compact variable…
Models do this to save on the number of calculations… but, when you are trying to modify that line (or debug it, or base another line off of it) you will have to go back and figure out where the compact variable is defined, whether it is defined correctly, whether it needs to be modified for a new application, etc.
GAMMA = K * DT / DX**2.
8. Continuing the theme… don’t use a bunch of temporary/dummy variables for multiple things at various points in the code
Some models do this to save on RAM… but, when you are trying to modify a line (or debug it, or base another line off of it) you will have the problems previously mentioned. In addition, when you look at a random line of code, you can’t be sure of what the dummy variable means!
! Check for parcel buoyancy
DUM=PTPAR-PTENV
IF(DUM.GE.0.0) CAPE = CAPE + G*DZ*DUM/PTENV
And later…
! Condense excess vapor from parcel
DUM = QVS*LV*17.27*237.0/(CP * (TEMP-36.0)**2)
DUM = (QVPAR-QVS)/(1+DUM)
QVPAR = QVPAR - MAX(DUM,0.0)
9. Cosmetics: avoid IF statements when possible (they run slowly and look ugly!)
! Upstream scheme depending upon flow direction
IF(C.GE.0.0) THEN
PHI(I,N+1)=PHI(I,N)-C*DT*(PHI(I,N)-PHI(I-1,N))/DX
ELSE
PHI(I,N+1)=PHI(I,N)-C*DT*(PHI(I+1,N)-PHI(I,N))/DX
ENDIF
! Upstream scheme depending upon flow direction
PHI(I,N+1)=PHI(I,N) &
-0.5*(C+ABS(C))*DT*(PHI(I,N)-PHI(I-1,N))/DX &
-0.5*(C-ABS(C))*DT*(PHI(I+1,N)-PHI(I,N))/DX
10. A three-level time scheme (e.g. leapfrog) only requires the time dimension of the arrays to be 3 (not NT)
INTEGER NX, NTPARAMETER(NX=100, NT=10000)
REAL PSI(NX,NT)
INTEGER I, N
! Omit the initial condition statements for now
DO N=2,NT
DO I=2,NX
PSI(I,N+1)=PSI(I,N-1) + forcing[PSI(I,N)]
ENDDO
ENDDO
10. A three-level time scheme (e.g. leapfrog) only requires the time dimension of the arrays to be 3 (not NT)
INTEGER NX, NTPARAMETER(NX=100, NT=10000)
REAL PSIP(NX), PSIC(NX), PSIF(NX) ! Past, current, future
INTEGER I, N
! Omit the initial condition statements for now
DO N=2,NT
PSIP=PSIC
PSIC=PSIF
DO I=2,NX
PSIF(I)=PSIP(I) + forcing[PSIC(I)]
ENDDO
ENDDO
Only practical approach for long simulations! (RAM)
11. Obviously, this requires writing output during the run (not waiting until the very end)
! Omit the declaration statements for now
! Omit the initial condition statements for now
DO N=2,NT
TIMENOW=REAL(N-1)*DT
PSIP=PSIC
PSIC=PSIF
DO I=2,NX
PSIF(I)=PSIP(I) + forcing[PSIC(I)]
ENDDO
! if it's time, dump output
IF(TIMENOW.GE.THISDUMP) THEN
CALL GRADSHISTORYDUMP
! set next time for output
THISDUMP = THISDUMP + DTOUTPUT
ENDIF
ENDDO
12. In the atmosphere, all processes happen simultaneously… in the model, they must happen in a certain order.
In other words, you can’t just put things anywhere you want! Example… what is the correct order for these processes in a model?
Predict “dry” change in temperature
Compute saturation mixing ratio
Update water vapor for evap/cond
Apply boundary conditions
Update temperature for evap/cond
5
4
13
2
13. Use modular code…
A. Creating a “driver” code and using subroutines
B. Breaking your code up into meaningful filesi) Using INCLUDEii) Declaring everything in one placeiii) Using COMMON blocksiii) Compiling a group of files
Breaking your code up into meaningful files…-Using INCLUDE-Declaring everything in one place
constants.inc:
Breaking your code up into meaningful files…-Using INCLUDE-Declaring everything in one place
Using COMMON blocks
USE: The preamble to each subroutine is a series of INCLUDE statements that define all the variables. The final thing after all the included definitions is then the INCLUDE statement for the file that contains the COMMON block
• Declare the variables once (only), in one place
• Never have to worry about whether a variable is being passed to subroutines
• Saves RAM, because each subroutine looks at the same place in the memory (don’t use extra memory to declare a redundant copy of variables passed to the subroutine)
• When a variable is updated in a subroutine, it is updated everywhere in the code
Recall: creating a “driver” code and using subroutines…
So, each subroutine also begins with the INCLUDE statements!
Breaking your code up into meaningful files…-Using INCLUDE-Declaring everything in one place
Breaking your code up into meaningful files…-Compiling a group of files
Your model code can become quite long…(the last time I did this project, the 2D model driver with simple microphysics was 1431 lines, excluding all declarations)
Why not break the subroutines up, too?
All subroutines related to initial conditions: init.F
All subroutines related to boundary conditions: boundaries.F
All subroutines related to writing out data: writeout.F
All subroutines related to main integration: integration.F
All subroutines related to main driver: modeldriver.F
Breaking your code up into meaningful files…-Compiling a group of files
Then, to compile, create a shell script: compile.sh
#!/bin/csh
rm cmm.exe
cat modeldriver.F init.F boundaries.F writeout.F integration.F > onefile.F
f90 –o cmm.exe onefile.F
Compiling the model is now simple, and you know right where to look if you want to work on the boundary conditions!