Spring, 2019
Jewett
Fortran information
ATMS 502 / CSE 566
Numerical fluid dynamics
Issues unique to Fortran - that we will encounter in class - are discussed here.

Compiling
  • We will generally use a Makefile when compiling.  But in general, to compile a file with the Intel compiler ifort:

         
    ifort  name-of-your-program.f90                         (.f90 is the typical suffix for free-format Fortran 90 code)

  • As we'll discuss in class, there are times when you need to employ additional checking of your code while it executes, to help track down possible errors (bugs).  In such cases, we will change the OPTIONS in our Makefile to enable additional code-checking when compiling; some of those compiler options are described here.  For example, we could do this:

          ifort -g -traceback -check all -ftrapuv -zero  name-of-your-program.f90
  • The Portland Group (PGI) compiler, pgf90, is available on Stampede via the module command.

  • There is also the GNU Fortran compiler, gfortran, which is free and usable on all platforms and which is also available on Stampede.  It may prove helpful if you want to download it onto your home/work/laptop personal computer and do testing of code there.  For more information on gfortran, check this page, where you will find binary downloads for Windows, Linux and Mac.  For Mac use, consider also taking a look at this sourceforge page (sourceforge is a safe site for software).

Boundary conditions and array layout

In Fortran, 1-D arrays are usually defined/declared with statements like real s(nx), which is the same as real s(1:nx).
This means that there are nx array elements, and we address them as s(1), s(2), ..., s(nx).

In some cases this is fine.  However, for arrays used in our integration, we will encounter issues connected to boundary conditions.

Imagine we have a domain that is nx grid points in size.  And, assume we use an array named s1 to hold the conditions at the present time, and array s2 to hold conditions in the future - at the next time step.  For some array index j, determining s2(j) - the future value of array s2 at index point j - requires knowing s1(j), the current value at point j.  However, most integration methods also require knowing the neighboring grid values: s1(j-1) and s1(j+1), for example - the grid points to the left and right of point j.

Why do we care?  Well -
  • away from the boundaries - far from j=1 or j=nx - we can examine points s1(j-1) and s1(j+1) without problems.
  • at the left boundary, s1(j=1), though, we would need the j-1 point:  s1(0).
  • at the right boundary, s1(j=nx), we would need the j+1 point: s1(nx+1).
  • but: s1(0) and s1(nx+1) don't exist in an array defined s1(nx).
Most codes deal with this one of two ways. 
  1. One approach breaks down the integration procedure with checks to see if we are near a boundary.  For example,

        do  j = 1,nx
            if (j.eq.1) then
                do something special for integration at the left-boundary to get s2(j)
           
    else if (j.eq.nx) then
                do something special for integration at the right-boundary to get s2(j)
           
    else
                this is an "interior" grid point: do the usual integration, e.g. s2(j) = some function of s1(j-1), s1(j), and s1(j+1)
           
    endif
        enddo

    This gets pretty messy, particularly in 2-D and 3-D; there are boundaries and corner points to consider.

  2. Another approach, which we will follow: deal with the boundaries before you do the integration.  So it looks like:

        call bc( this subroutine sets the boundary points s1(0) and s1(nx+1) )
        do j = 1,nx
            do the same procedure for all j = 1,...,nx
        enddo
In other words, we set the grid points we need "off the ends" of our arrays before we do the integration.

We call those 'extra' grid points "ghost zones" or "ghost grid points" - they are there to help us solve things quickly and neatly (there are also some considerations for ghost or "halo" points for parallel computing we can talk about in class).  So, our arrays are defined s1(0:nx+1) instead of s1(1:nx).  The declaration at the top of your program would look like "real s1(0:nx+1)".

So you will see plenty of places in Fortran codes where extra array points are used, more than the simple (nx).
In the demo code, we define arrays s1(0:nx+1) and s2(0:nx+1).  Only 1,...,nx are physical; the others are 'helper' points.

Click on the following icon to see an image summarizing the above: Icon for fortran arrays and BCs