September 12, 2021

Fortran 90 and Crystal Sleuth

FORTRAN was the first computer language I ever learned. The year was 1970, I was beginning my freshman year at the university, and I saw a 1 unit class called "FORTRAN". I asked someone what the heck it was, and they told me it was some kind of computer class. It has been an ugly, tragic, and downhill road ever since.

Now it is 2021 (that makes it just over 50 years later) and I am looking at the Crystal Sleuth program, which was written in Fortran-90. My experience is Fortran-IV, with just a dab of Fortran-77 along the way. The purpose of this page is to educate myself in the features of Fortran-90 used in the Crystal Sleuth program.

I will apologize a bit for my references to the C language. I am quite familiar with C and it is natural for me to make references to it, but it will no doubt frustrate anyone reading this who is not C fluent.

Comments and input form

I don't intend to make any code modifications, but I might add a block of explanatory comments to the front of some of the files that comprise Crystal Sleuth.

In Fortran-90, the comment character is the exclamation, and lines are free form (the old 72 column rule is gone now, which is good). Online comments are now allowed Fortran is also case insensitive, i.e Xdata and xdata are the same.

The old business of a continuation character in column 6 is gone now too, which is also good. A continuation line is indicated by placing an ampersand at the end of the line, which continues on the next line. Lines are limited to 32 characters, and you can have only 39 continuation lines. There is a trick of chopping a string across lines where you place an ampersand at the end of the first line and the beginning of the next. Multiple statements can be crammed into a single line by separating them with a semicolon.

The old default type business of certain letters being real and others integer persists, but is commonly canceled (as it is in Crystal Sleuth) by the statement "implicit none" which requires you to declare types for all variables. Note that you must place "implicit none" in every function and subroutine (and of course the main program).

Functions and such

The CrystalSleuth program begins in CrystalSleuth.f90 with:
integer*4 function WinMain( hInstance, hPrevInstance, lpszCmdLine, nCmdShow )
The usual "program" statement is nowhere to be seen. This program was generated as part of some kind of Microsoft Visual Studio software, and clearly it is deviating from usual standards.

Fortran 90 still has subroutines (which you call) as well as functions, which you just use in expressions. A function sets the value it returns by setting a "pseudovariable" with the same name as the function itself.

Variables and declarations

We have both integer and integer*4 types, along with real, logical, character and probably more. Exactly what plain integer is, is unclear and probably platform dependent. Here are some examples:
    integer i
    integer*4 n1, n2
    integer:: numxrays = 0
    integer, parameter:: HOLD = 1
    integer, save:: rec_col = 0
    integer, allocatable, save:: ron(:)
So, we can initialize a variable, but we will need to drag in the double colon. A variety of interesting "attributes" can also be used with the double colon.

The attribute "parameter" simply indicates that the variable is an immutable constant.

The attribute "save" indicates that a variable should persist between function calls (rather than being allocated on the stack). This is pretty much like a "static" variable in C.

    character*50 In
    character drive*10, dir*256, name*256, ext*10
    character*15, allocatable, save:: smn(:)
Here we have some alternate ways to do things. The first declaration declares a single variable "In" that can contain 50 characters. The second declaration is essentially the same, but declares several variables of different lengths. The last declaration declares an array of 15 character variables. In other words, each item in the array is a 15 character variable. The array has yet to be allocated.

Dynamic storage

To use this, you must tag the variable with the "allocatable" attribute. Once this is done, it is all about using ALLOCATE(p) and DEALLOCATE(p). Consider this code:
    character*256, allocatable, save:: Name_Match(:)
    if(allocated(Name_Match))deallocate(Name_Match)
	allocate(Name_Match(num_files))
Here we have an array of 256 character items. Somehow the integer variable "num_files" gets set to the desired size of the array, then we allocate an array of the desired size.

Pointers

Before you can use a pointer, you have to tag the variables you intend to point to. You do this with the "target" attribute. And you declare a pointer with a type that corresponds to the dimensions of whatever it will point to.
    integer, target :: j(100)
    integer, pointer :: pj(:)
One you have done this preparation, you can associate the pointer with a target using the "=>" operator in a statement like this:
    pj => j
Once you have done this, the pointer "pj" is effectively an alias for the variable "j", and can be used with exactly the same syntax as you would use for "j" itself.

To use this with dynamic allocation, you might have code like this:

    integer, allocatable,target,save:: numinpa(:)
    integer, pointer:: numinp(:)
    if(allocated(numinpa))deallocate(numinpa)
    allocate(numinpa(max))
    numinp => numinpa
    if ( numinp(index) > limit ) then
	call my_error ( numinp(index) )
    endif
In essence, a pointer is a run time equivalence or alias, and is only a rough analog to a C language pointer.

A pointer can be associated with a different variable at any time. The routine "nullify(pj)" will eradicate any association.

Pointers can be associated with portions of an array by a statement like:

    pj = j(10:19)
Using pointers along with derived types allow you to construct linked lists and such.

Arrays

Some people will try to make a religion out of whether 1-based or 0-based array indexing is "better". I'll simply say that in the world of FORTRAN where we are dealing with scientific problems and algorithms expressed in vectors and matrices, 1-based arrays are certainly logical and appropriate.

It is possible in Fortran 90 to declare the first and last index for an array:

    real pos(100)
    real kpos(0:99)
Both of the above would specify an array of 100 elements. Notice that we use parenthesis, not square brackets.

Fortran 90 allows use of arrays as entire objects, i.e. you can add two arrays together without writing a loop.

You can do dynamic allocation, like this:

    real, allocatable, dimension (:) :: xx

    n = 102
    allocate ( x(n) )

Modules

Modules are collections of variable declarations and such that are shared between program units. The "use" statement drags a module into a given program unit.

In the software I am working with, most (but not all) of the modules are defined in the file CrystalSleuthglobals.f90. Other files pull them in with statements like:

    use Region_Globals
There is no "include" of CrystalSleuthglobals.f90, so it is mysterious to me how one file knows how to find the modules it wants to use, but apparently it works.

There is also an "include" statement that seems to work exactly like the include statement in C. There is no magic, it just gets replaced with the contents of the included file.

Interface

This works a lot like prototypes in Ansi C. You must put these "prototypes" in an interface block as in the following example. Then you declare the return type and the types of the arguments of whatever functions you want to declare.
    interface
        integer*4 function Pref_Dlg_Proc(hdlg)
            integer*4 hdlg
        end function

        logical function Scroll_Screen(intent_msg)
            integer intent_msg
        end function

    end interface

Types

The "type" statement is much like a "struct" in the C language. You declare a derived data type like so:
TYPE Point
      REAL :: X, Y
END TYPE Point
You use it like so:
TYPE (Point) :: Center

Center%X = 1.0
Center%Y = 2.0

Logical stuff

You can still use the old fortran .EQ. and such, but you can alternately use == and some programs mix them up and use both in various places!

IF statements must include a THEN as in the following:

    if(dif1 < dif2)then
            dif2 = dif1
    elseif(curs == 1)then
            dif2 = 99
    else
            dif2 = 12
    endif
There is also something like a switch/case in C --
    select case(intent_msg)
        case(1) !lmouse button clicked
            rgn_mode = 0
        case(4) !lmousebutton dbl click
	    call DblClickInRgn(2)
        case default
	    call Trouble()
    end select
The case values may have ranges as follows:
    CASE (:19999)
    CASE(20000:49999)
    CASE(50000:)
Logical constants are .true. and .false. Hence, you could write code like:
    logical, parameter :: bogus = .true.

Loops

This is all about the wonderful fortran "do" statement. The simplest form is the one every English schoolboy is familiar with, namely:
    do i = 1, n
	data(i) = 0
    enddo
A third index can be added to the "do", which overrides the usual increment of 1.

There is also a do-while statement:

    do while ( keep_going )
	a = b
    enddo
The while can test a logic veriable, a logical expression, or if you want an infinite loop, it can just test .true.
Feedback? Questions? Drop me a line!

Tom's Mineralogy Info / [email protected]