wingosoft

I'm writing this page so that I can remember a number of details about f90 coding that always seem to slip my mind. I figured I'd put it up for folks that might be going through similar problems.

Useful string routines

Initialization

To initialize an array of strings, try

real, dimension(3), character*80 :: (/ 'a  ', 'ab ', 'abc' /)

The issue at hand is that, at least in the NAGware f90 compiler that I use, there is no easy way to initialize strings with an automatic length. For this reason, I declare the elements to be of type character*80. Also, my compiler complains if the individual strings within the array are not all of the same length, even if they are all smaller than the maximum length, so I add whitespace at the end to even them up. The slashes inside the parentheses delimit a static array constructor.

To initialize a string passed to a subroutine, things are slightly more elegant.

subroutine mysub(str)
       character*(len(str)) :: str
...

In this manner you can let the subroutine figure out how long the string is implicitly. You still have to worry about any whitespace after the text of the string; use the len_trim and trim functions to deal with that issue.

Individual characters

You can treat strings as arrays of characters and take slices of them just like you can with data. For instance,

character*10 :: str = 'abcdefg'
print *,str(2:6)

prints 'bcdef'.

You can turn a single-digit integer i into string characters using achar(i+iachar("0")). This is useful in do loops where it can be concatenated with other strings to form an output file name, e.g. 'myproject.'//achar(i+iachar('0'))//'.out'.

Passing functions as arguments

I find myself always wanting to pass functions as arguments to other routines, usually for math library routines like integration/differentiation and root-finding. The problem comes in if you want to make your code look nice, with the main program file being relatively short and showing just the program flow. Functions that are declared in a program's contains section can't be passed as arguments. Functions declared within the same source file must be declared before the main program and interface'd into the main program so that the compiler can check the arguments and make sure they are right. Functions declared outside the program source file have to be imported using the external statement. In short, it's quite a mess.

My current favorite solution to this problem is to break most projects up into three files. All of my functions are containsed in a module called functions, defined in a file called functions.f90. For example,

module functions
      use datums
      implicit none
      
      interface x
         module procedure x
      end interface

contains
      real function x(z)
             real, intent(in) :: z
	     x = z/H
      end function x
end module functions

In a separate file, datums.f90, I define a module called datums:

module datums
      implicit none
      real, parameter :: H = 120. ! inches
end module datums

There's quite a bit to this example. First off, the datums module is meant to hold common parameters and variables. It doesn't use anything, so it can be used by any module without conflicts. It gets used by both my program and my functions module. It's called 'datums' because 'data' is already a keyword (albeit deprecated) in Fortran 77. In this example, it defines a height (say, of a reactor core).

There are a number of advantages to separating the code in this manner, from my perspective:

Example makefile

This is a GNU makefile, and it doesn't work with other systems. On NC State's eos system, type add gnu to get access to the GNU software, and agg nagf90 to get the NAGware compiler. Place the contents of this code example in a file named 'Makefile' in your project directory. For a three-file setup, you'll want to set libraries = datums.o functions.o.

libraries = 
program = skeleton
otherfiles = 
outfiles = 

f90 = f90 -Qpath /ncsu/nagf90/lib -I/ncsu/nagf90/lib -L/ncsu/nagf90/lib \
      -L$(HOME)/lib -I$(HOME)/lib/mod

$(program): $(libraries) $(program).f90 $(otherfiles)
        $(f90) -u -o $(program) $(libraries) $(otherfiles) $(program).f90

$(libraries): %.o : %.f90
        $(f90) -u -c $<

$(outfiles): %.out : $(program)
        -./$(program)

ps : $(outfiles) $(program) $(program).gnuplot
        gnuplot $(program).gnuplot > $(program).ps

clean:
        rm -f *.mod *.out *.ps *.o $(program) *~

I use this makefile at NC State. If you are using it somewhere else, probably a better definition for the f90 variable would be

f90 = f90 -L$(HOME)/lib -I$(HOME)/lib/mod

This assumes that you have a ~/lib directory that you keep the modules in and a mod directory beneath that the the .mod files (like header files in C, except not human-editable, or edible) are stored in. I have a makefile in my ~/lib directory that will build libraries from ~/lib/src into ~/lib, putting the .mod files in ~/lib/mod:

libraries = math.o datafile_type.o steamtables.o least_squares.o linear_least_squares.o

f90 = f90 -Qpath /ncsu/nagf90/lib -I/ncsu/nagf90/lib -L/ncsu/nagf90/lib \
      -L$(HOME)/lib -mdir $(HOME)/lib/mod -I$(HOME)/lib/mod

libraries: $(libraries)

%.o : src/%.f90
        $(f90) -u -c $<

clean:
        rm -f mod/*.mod *.o *~

The libraries mentioned in the libraries variable are available at this site.