4.2. Using Fortran

4.2.1. Developing

  1. Understand the Problem
    • Question being asked
    • Mathematical description
  2. Formulate the Problem
    • Inputs
    • Outputs
    • Variables and constants
    • Flow chart
  3. Design Algorithm to Solve the Problem
    • Numerical scheme
    • Pseudo-code
  4. Implement Algorithm in Code

Steps 3) and 4) may be incremental - where each sub-step can be tested

Useful to use comments at each step so you are clear about the question being asked and what you are going to do (like a quick step 1) and 2))

Always use meaningful names for programs, modules, functions, subroutines and variables.

4.2.2. Naming Convections

  1. Use lowercase for all Fortran constructs (do, subroutine, module, ...).
  2. Follow short mathematical notation for mathematical variables (sigma, gamma, rho, epsilon, ...).
  3. For other names use all lowercase: try to keep names to one or two syllables; if more are required, use underscores to clarify (spline_interpolate, stop_error).

This convection is quite good for Fortran 90:

Fortran Naming Convection

4.2.2.1. Program Stucture

  • The trick with names is to avoid ambigous interpretation, e.g. avoid:
  • mod could be module or modulus
  • int could mean integrate or interpolate
  • sub could be subroutine or subset
  • pro could be program or product
  • Fortran usually prefers whole words, but these may become too verbose at some point
Code Meaning
program area_under_curve
Program names usually denote the overall task - “what is being tested?” usually answers this one. Prefixes like test_ can sometimes be useful for programs
module integrate
module problem_description
Module names should describe the overall function of the procedures within that module
function trapezoid_rule(x, y ,z)
function simpsons_rule(x, y, z)
Function names are usually verb-noun combinations. Prefixes like print_ write_ get_ and set_ are sometimes useful for functions.
subroutine initial_conditions(u, v, x, y, t)
subroutine analytical_solution(u, v, x, y, t)
subroutine boundary_conditions(u, v, x, y, t)
Similar convention for subroutines as for functions.

4.2.2.2. Variables and Constants

As these are used in formulae, it’s important to keep them as short as possible. Note that Fortran is case insensitive

Code Meaning
x, y, z
Cartesian coordinates
i, j, k
Usually denote spatial coordinates
n, m
Usually denote time or interation number
process_no, step_no
_no can denote number (as is usual in english)
i_max, j_max, k_max, n_max, m_max, process_no_max, step_no_max
Maximum value or range denoted by _max. Max denoting maximum as is usual
PI, RHO, NU
Constants (parameters) can be in uppercase

4.2.3. Indentation

Use 4 spaces for indentation

4.2.4. Templates

4.2.4.1. Module Template

  • implicit none works for whole module
  • private implies that everything in this module is private by default
  • Only make public what you want
module integrate

    use constants, only: RHO, PI
    use utilities, only: stop_error
    implicit none
    private
    public integrate, normalize, &
           euler, runge_kutta

contains

    subroutine get_values(a, b, c)

    end subroutine

end module

4.2.4.2. Program Template

  • Note the use of explicit imports, i.e. avoid use integrate. Instead say what you are using use integrate, only: midpoint
program uranium
    use mesh, only: create_mesh
    use utilities, only: stop_error, RHO
    implicit none

    integer, parameter :: Z = 92
    real(kind=8), parameter :: R_MIN = 8.0d-9, R_MAX = 50.0d-10, A = 1.0d+7

    print *, "I am running"
end program

4.2.5. Comments

Comments have two functions - explain the interface and explain the implementation

4.2.5.1. Interface

Explain what is does - the interface. Put it before any code is written

4.2.5.1.1. Functions/Subroutines

! Estimate the integral of f(x) from a to b using the
! Trapezoid Rule with n points.

! Input:
!   f:  the function to integrate
!   a:  left endpoint
!   b:  right endpoint
!   n:  number of points to use
! Output:
!   the estimate of the integral

function trapezoid

4.2.5.1.2. Modules

! Performs integration using quadrature integration
! and creates a table of the error between this and
! the known solution.

module quadrature_omp

4.2.5.1.3. Programs

! Prints a table for the effect of the number
! of integration points on the accuracy of the integration

! Example use:
! $ gfortran -fopenmp quadrature_omp.f90 test2_omp.f90
! $ ./a.out

program test_openmp

4.2.5.2. Implementation

Explain how it does it - the implementation

Above a block of code to denote how it does it (never usually need these inline - that’s too much commenting)

! Print the number of function evaluations by each thread:
    do i=0,nthreads-1
        print 101,  i, fevals(i)
101     format("fevals by thread ",i2,": ",i13)
        enddo

4.2.6. Makefiles

The general form of a make command is as follows. Use a tab character not spaces:

target: dependencies
    <TAB> commands(s) to make target

Example where there is no output file. It’s important to know the dependencies for the module files, as they will compile in that order, i.e. MODULES = functions.mod newton.mod implies that the newton module depends on the functions module.

# $MYHPSC/homework3/Makefile
# Example usage:
#   $ make test1
#   $ make clean

# Dependencies for test1.exe - The names of object files (\ is a continuation character):
OBJECTS = functions.o \
          newton.o \
          test1.o

# Dependencies for test1.exe - The names of modules (needed if modules are used):
MODULES = functions.mod \
          newton.mod

# FLAGS
# -c flag means compile to one file (very common if program is in many files)
# -o FILENAME means rename output from a.out to FILENAME.exe
# -g generates extra debugging information usable by GDB
# -03 level 3 optimisation for compling code

FFLAGS = -g

# Phony targets don't create files (e.g deletes files or prints to screen)
.PHONY: test1 clean

# 1) Highest level: dependency for make test1 is test1.exe
#    If older it runs ./test1.exe
test1: test1.exe
    ./test1.exe

# 2) Second highest level: dependencies for test1.exe are .mod and .o files
#    If older it runs gfortran complier
test1.exe: $(MODULES) $(OBJECTS)
    gfortran $(FFLAGS) $(OBJECTS) -o test1.exe

# 3) Third highest level: dependencies for .o and .mod files are .f90 files
#    If older it runs gfortran complier ($< refers to dependency)
%.o : %.f90
    gfortran $(FFLAGS) -c  $<

%.mod: %.f90
    gfortran $(FFLAGS) -c $<

# Removes all files (rm) -f means force nonexistent files (never prompt)
clean:
    rm -f *.o *.exe *.mod

You can have multiple Makefiles in the same directory. To distinguish use:

$ make test1 -f Makefile_2

To print to the screen from a Makefile use @echo

OBJECTS = functions.o newton.o test1.o
MODULES = functions.mod newton.mod
.PHONY: test

test:
        @echo "Modules are: " $(MODULES)
        @echo "Objects are: " $(OBJECTS)

4.2.7. Compiling

If the Makefile does not compile, you may need to compile separately to see what the errors are in the compilation

4.2.7.2. Multifile complitation (with modules)

  • Compile first (separate compile needed because of module dependencies):
$ gfortran -c program.f90
$ gfortran -c module_1.f90
$ gfortran -c module_2.f90
  • Then link (with optional executable rename):
$ gfortran program.o module_1.o module_2.o -o program.exe
  • Easier to use Makefile for multi-file programs

4.2.8. Flags

Code Meaning
-c
Compile to an object file, object files are later linked into a complete program
-o FILENAME
Specifies the name of the output file
-fopenmp
Compile using OpenMP
-g
Generates extra debugging information for GDB
-g3
Generates even more debugging information
-03
Optimised code - program is faster but takes longer to compile

4.2.9. Timing

4.2.9.1. Total CPU Time

This is for the total CPU time (called the “user” time). The real time and sys time aren’t really important.

$ time ./a.out
<output from code>

real    0m5.279s
user    0m1.915s
sys     0m0.006s

4.2.9.2. CPU Time for Part of Code

The cpu_time tells the CPU time used between two successive calls:

real(kind=8) :: t1, t2, elapsed_cpu_time

call cpu_time(t1)

!code to be timed

call cpu_time(t2)
elapsed_cpu_time = t2 - t1

CPU Time is proportional to \(n^3\) if using matrix multiplication, doubling n will multiply CPU time by 8 (although larger matrices may be affected by cache). Optimisation flags can be used for reducing CPU time.

4.2.10. Debugging

4.2.10.2. Compiling with gfortran Flags

There are a number of flags you can use when compiling your code that will make it easier to debug.

Here’s a generic set of options you might try:

$ gfortran -g -W -Wall -fbounds-check -pedantic-errors \
-ffpe-trap=zero, invalid, overflow, underflow program.f90

Most of these options indicate that the program should give warnings or die if certain bad things happen.

Compiling with the -g flag indicates that information should be generated and saved during compilation that can be used to help debug the code using a debugger such as gdb or totalview. You generally have to compile with this option to use a debugger.

4.2.10.3. The gdb Debugger

gdb is an open source debugger, but often doesn’t work well with Fortran.

$ cd $UWHPSC/codes/fortran
$ gfortran -g segfault1.f90
$ gdb a.out

(gdb) run

 Runs for a while and then prints
 "Program received signal EXC_BAD_ACCESS,
 Could not access memory." Tells what line it died in.

 11 a(i) = 5
 (gdb) p i
  $1 = 241
 (gdb) q

This at least revels where the error happened and allows printing the value of i where it died.

4.2.11. Software

It is best to use high quality software as much as possible for several reasons:

  1. It will take less time to figure out how to use the software then to write your own version (assuming it’s well documented)
  2. Good general software has been extensively tested on a wide variety of problems
  3. Often general software is much more sophisticated than what you might write yourself, for example it might provide error estimates automatically, or it might be optimized to run fast.

Often we use:

  • LAPACK (Linear Algebra Package)
  • BLAS (Basic Linear Algebra Subprograms)

There are others, such as:

  • Clawpack. Clawpack stands for “Conservation Laws Package” and was initially developed for linear and nonlinear hyperbolic systems of conservation laws, with a focus on implementing high-resolution Godunov type methods using limiters in a general framework applicable to many applications. These finite volume methods require a “Riemann solver” to resolve the jump discontinuity at the interface between two grid cells into waves propagating into the neighboring cells. The formulation used in Clawpack allows easy extension to the solution of hyperbolic problems that are not in conservation form.

There are many version of Clawpack, such as:

  • AMRClaw includes block-structured adaptive mesh refinement that allows one to use a non-uniform grid that changes in time and uses smaller grid cells in regions with fine structure or where high accuracy is required.
  • GeoClaw Includes the AMR capabilities of AMRClaw and also has a number of special routines and algorithms for handling geophysical problems, including special well-balanced, positivity-preserving shallow water solvers.
  • PyClaw includes the high-order WENO-RK algorithms of SharpClaw.