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.
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
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
trim functions to
deal with that issue.
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)
You can turn a single-digit integer
i into string characters using
This is useful in do loops where it can be concatenated with other strings to form an output file name, e.g.
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
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
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:
- Improved readability. Your program should be a page or two long, showing only the flow of the algorithm. Details of algorithm implementation are clearly defined in a separate file. Constants used are centrally defined and modifiable. This is good for future users of the program and for evaluation purposes.
Built-in interfacing. By
functionsmodule in your main program, you already have all of the functions it defines in your namespace, and you can pass them to other procedures with no need for explicit
interfacestatements. This is performed by including the
module procedureline before the
containssection in the
functionsmodule. If you don't include this line, you can still call the function, but you can't pass it as an argument.
Simplified building of the project. It may seem more complicated at first to have to build three files
instead of one, but the use of a generic project makefile makes things quite easy. To build my projects, I just type
makeon the command line and it does whatever needs to be done. I've included my makefile at the end of this document for reference. Also, as I find myself viewing the output of the program as just another thing to be built, I can just type in
make psto run the program to generate the output files or to run gnuplot to generate the graphs. Too bad I didn't know this a few years ago.
Clean function definitions. You don't have to worry about explicitly passing the global parameters to
each and every function, nor does the
datumsmodule have to be
used in each function, as they inherit variables from the module. This cleans up and clarifies function definitions quite a bit.
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.