230 likes | 356 Views
Guidelines for the CMM coding project. (or, “How to make your life easier in the long run”). 5 October 2006. Guiding philosophies of this advice….
E N D
Guidelines for the CMM coding project (or, “How to make your life easier in the long run”) 5 October 2006
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
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 may be = 4 on some compilers B=A*REAL(I) (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! We all like to save keystrokes and keep our code compact, but which of these is unambiguous? EPS EPSILON ASSELINCOEF
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… GAMMA = K * DT / DX**2. 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.
8. Continuing the theme… don’t use a bunch of temporary/dummy variables for multiple things at various points in the code ! 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) And later… ! Check for parcel buoyancy DUM=PTPAR-PTENV IF(DUM.GE.0.0) CAPE = CAPE + G*DZ*DUM/PTENV 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!
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? 2 5 Compute saturation mixing ratio Apply boundary conditions 3 1 Update water vapor for evap/cond Predict “dry” change in temperature 4 Update temperature for evap/cond
Use modular code… • A. Creating a “driver” code and using subroutines • B. Breaking your code up into meaningful files • i) Using INCLUDE • ii) Declaring everything in one place • iii) Using COMMON blocks • iii) 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 main driver: modeldriver.F 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
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!