Improve your Fortran 77 programs using some Fortran 90 features

This essay is under construction.

Ryo Furue (Email: my family name AT hawaii PERIOD edu)

There are already many tutorials exploring various features of the Fortran 90 (F90) language in a systematic way. Instead of adding yet another tutorial of such kind, I here focus on how to improve your Fortran 77 (F77) programs by using small subsets of the F90 language. To do so, I'll show small code in F77, point out problems in it, and propose a solution or improvement using some features of F90.

I think this approach is useful to convince scientists that F90 is useful and easy to adopt. I've found persistent resistance among fellow scientists against F90, reasons for the resistance including

  1. Benefits of F90 aren't clear ("Why bother?").
  2. F90 is a large, complex language.
  3. Transition to F90 would require a lot of efforts.
  4. F90 compilers may not be available everywhere.
My answers to these issues are simple:
  1. F90 will cut down your efforts in debugging your code (you get results more quickly).
  2. You have only to learn a small subset of the F90 language;
  3. Your choice is not F77 or F90; rather, you mix a bit of F90 in your F77 programs. You don't have to convert existing F77 programs; rather, when you modify them, you can mix in some F90 features.
  4. It's hard to find a F77-only compiler nowadays; and free F90 compilers are easier to find than F77-only ones.
To me, the situation is similar to the following fictitious conversation:
A: It's hard to climb up to this floor everyday.
B: But, why don't you use the elevator?
A: Um, I heard it's for expert building-climbers. I just have to climb it once a day, so it's not worth it to learn the new technology.
If you find this conversation funny, you understand how I feel.

Mismatched COMMON blocks

Problem

  subroutine sub1
    common /pars/a, b, d
    ! . . . . .
  end

  subroutine sub2
    common /pars/a, b, c, d
    ! . . . . .
  end
You sometimes modify a COMMON block in one subroutine and forget to update other subroutines using the COMMON block. This error often leads to a long debug session.

What's the problem? The fundamental source of the problem is that you have identical information at many places. (This is a recurring theme as we'll see.)

A minimal solution

Put the COMMON block in a MODULE and USE it elsewhere:

  module pars_mod
    common /pars/a, b, d
    save
  end module pars_mod

  subroutine sub1
    use pars_mod
    ! . . . . .
  end

  subroutine sub2
    use pars_mod
    ! . . . . .
  end
Here, the original problem does not exist, because its source (repetition of the same information) is gone.

A better solution

In fact, it's better to avoid COMMON blocks altogether. The solution above can be rewritten as

  module pars_mod
    real a, b, d
    save
  end module pars_mod

  subroutine sub1
    use pars_mod
    ! . . . . .
  end

  subroutine sub2
    use pars_mod
    ! . . . . .
  end

Details

You may have noticed the SAVE statements in the MODULEs above. You might think this is an extra complication in F90. It's not. In fact you usually have to save COMMON variables in F77, too. Did you know that? Indeed, situations where you need to save COMMON variables are the same as those where you need to save MODULE variables. When you have to save and when you don't is a complicated subject, which I don't want to discuss here. Don't worry. Since you want COMMON and MODULE variables to be saved 99% of the time, it's safe always to save them.

"Wait," I hear you say, "I've never used SAVE but my COMMON blocks have always worked for me." Right. For the same reason, your MODULE variables usually work without SAVE-ing them. I just recommend using SAVE, to be on the safe side.

Initial values of COMMON variables

Problem

How do you give initial values to COMMON variables? You might assign values to them in a subroutine:

  subroutine sub1
    common /pars/a, b
    a = 3.14
    b = -999.0
    ! . . . . .
  end

  subroutine sub2
    common /pars/a, b
    common /foos/kk, mm
    kk = -1
    mm = 10
    !. . . use a, b, kk, and mm . . .
  end
But, what if you forget to call sub1 before calling sub2 ? That's bad, because in sub2, those variables won't have proper values. Also, it's hard to keep track of who initializes which COMMON variables. In the example above, sub1 initializes a and b and sub2 initializes kk and mm. If you have many COMMON blocks, this would be pretty messy. Or you might use DATA statements:
  subroutine sub1
    common /pars/a, b
    data a/3.14/, b/-999.0/
    ! . . . . .
  end

  subroutine sub2
    common /pars/a, b
    common /foos/kk, mm
    data kk/-1/, mm/10/
    ! . . . . .
  end
But, still you aren't sure whether or not a and b are initialized in sub2. The problem of who initializes what isn't solved, either.

You can guarantee the initialization of COMMON variables by putting all COMMON blocks and their initializations to the main program:

  program main
    common /pars/a, b
    common /foos/kk, mm
    data a/3.14/, b/-999.0/
    data kk/-1/, mm/10/
    . . . . .
  end  

  subroutine sub1
    common /pars/a, b
    . . . use a and b . . .
  end

  subroutine sub2
    common /pars/a, b
    common /foos/kk, mm
    !. . . use a, b, kk, and mm . . .
  end
As a result, you end up with having all COMMON blocks in the main program, whether you use them in the main program or not. Your program becomes the messier for that.

The best solution within F77 to this problem is to use a BLOCK DATA.

  block data
    common /pars/a, b
    common /foos/kk, mm
    data a/3.14/, b/-999.0/
    data kk/-1/, mm/10/
  end

  subroutine sub1
    common /pars/a, b
    !. . . use a and b . . .
  end

  subroutine sub2
    common /pars/a, b
    common /foos/kk, mm
    !. . . use a, b, kk, and mm . . .
  end
You initialize all COMMON blocks in a single BLOCK DATA. At least, we have solved the original problems: The COMMON variables are guaranteed to be initialized before any subroutines are called, and it is now clear that initial values are all given in the BLOCK DATA.

This solution, however, still has a problem of the repetition of the same information. For example, if you introduce another common block, or another variable in an existing COMMON block, you have to edit the BLOCK DATA, too, which you sometimes forget.

Solution

When the two COMMON blocks in the problem above aren't much related, it's natural to put them in different MODULEs:

  module pars_mod
    real:: a = 3.14, b = -999.0
    save
  end module pars_mod

  module foos_mod
    integer:: kk = -1, mm = 10
    save
  end module foos_mod

  subroutine sub1
    use pars_mod
    !. . . use a and b . . .
  end

  subroutine sub2
    use pars_mod
    use foos_mod
    !. . . use a, b, kk, and mm . . .
  end
This solution is much cleaner than the F77 counterpart for a few reasons. First, the definition of variables and their initial values are close together:
    real:: a = 3.14, b = -999.0
Second, the repetition of information is reduced. Finally, you can keep related variables in a single place and unrelated variables separated; in the example above, a and b are put in one MODULE and kk and mm are in another.

The last point is another important, recurring theme: Related information should be put in one place and unrelated information, separated. That will lead to a better organized (and therefore easier to understand) program.

More initialization of COMMON variables

Problem

In the example above, the initialization of COMMON variables have been just assignments of constants. What if you need more than just assignments to initialize those variables? For example, you sometimes need to conduct some computation to determine initial values for COMMON variables. BLOCK DATA cannot be used because it cannot contain any executable statements. In that case, your best solution within F77 would be

  subroutine init_pars
    common /pars/a, b
    ! . . . . . .
    read(. . .) dd, ee
    call compute_a_b(a, b,  dd, ee)
  end

  subroutine compute_a_b(a, b,  dd, ee)
    ! . . . . . .
  end

  program main
    call init_pars
    ! . . . . . .
  end

  subroutine sub1
    common /pars/a, b
    ! . . . use a and b
  end
This isn't bad. But, it's better to put the information concerning the variables a and b together (better organization).

Solution

MODULEs can contain not only variables but also subroutines:

  module pars_mod
    real a, b
    save
  contains
    subroutine init_pars
      . . . . . .
      read(. . .) dd, ee
      call compute_a_b(a, b, dd, ee)
    end subroutine init_pars

    subroutine compute_a_b(a, b, dd, ee)
      ! . . . . . .
    end subroutine compute_a_b

  end module pars_mod

  program main
    use pars_mod
    call init_pars
    . . . . . .
  end

  subroutine sub1
    use pars_mod
    . . . use a and b . . .
  end
Now, MODULE pars_mod contains everything related to variables a and b.

Constants

Problem

You often need to use constants at various places in your program. How should you define and use them? The worst strategy is

  subroutine sub1
     . . . . . .
     a = b * 1000
  end

  subroutine sub2
     . . . . . .
     d = e / 1000
     . . . . . .
     k = i + 1000
  end
What's this magic number 1000 ? What if you later need to change its value? Should you change all occurrences of the value 1000 ? or do some of the occurrences have different meanings? The source of this problem is again the repetition of information.

A better approach is to give the value a name and use the latter throughout your program:

  subroutine sub1
    parameter (nx = 1000)
    . . . . . .
    a = b * nx
  end

  subroutine sub2
    parameter (nx = 1000)
    parameter (mode = 1000)
    . . . . . .
    d = e / nx
    . . . . . .
    k = i + mode
  end
Now, it's clear that the two occurrences of 1000 in sub2 have different meanings, so that even if you need to change the value of nx, you won't make the mistake of changing the value of mode.

The program above still has a problem: When you change the value of nx, you may forget to do so in some of the subroutines. Repetition of information hurts, again.

Solution

You put the definition of constants into a MODULE:

  module pars_mod
    parameter (nx = 1000)
  end pars_mod

  subroutine sub1
    use pars_mod
    . . . . . .
    a = b * nx
  end

  subroutine sub2
    use pars_mod
    parameter (mode = 1000)
    . . . . . .
    d = e / nx
    . . . . . .
    k = i + mode
  end
Now, you have only to change the value of nx in MODULE pars_mod. Notice that I didn't put the other constant (mode) in a MODULE. That's okey if the constant is used only in sub2.

Sharing values among subroutines

Problem

Subroutines sometimes need to share a lot of values. Should we pass them as arguments to the subroutines?

  subroutine sub1(a, b, p, q, r, s, t, u)
    ! . . . use p, q, r, s, t, and u . . .
  end

  subroutine sub2(kkk, p, q, r, s, t, u)
    ! . . . use p, q, r, s, t, and u . . .
  end

  subroutine othersub(x, y, z, p, q, r, s, t, u)
    ! . . . Doesn't use p, q, r, s, t, u . . .
    call sub2(kkk, p, q, r, s, t, u)
  end

  program main
    p = 3.14
    q = -999.0
    r = p + 3*q
    read(*,*) s
    t = s * p
    u = t * t + s
    call sub1(a, b,        p, q, r, s, t, u)
    call othersub(x, y, z, p, q, r, s, t, u)
  end
This style will quickly become cumbersome. You have to pass all the shared variables down to all subroutines requiring them. In particular, look at othersub, which needs to receive those variables and pass them onto sub2, even though othersub itself doesn't use them.

You are therefore tempted to use COMMON blocks to avoid this problem:
  subroutine sub1(a, b)
    common /pars/p, q, r, s, t, u
    ! . . . use p, q, r, s, t, and u . . .
  end

  subroutine sub2(kkk)
    common /pars/p, q, r, s, t, u
    ! . . . use p, q, r, s, t, and u . . .
  end

  subroutine othersub(x, y, z)
    ! . . . . .
    call sub2(kkk)
  end

  program main
    common /pars/p, q, r, s, t, u
    p = 3.14
    q = -999.0
    r = p + 3*q
    read(*,*) s
    t = s * p
    u = t * t + s
    call sub1(a, b)
    call othersub(x, y, z)
  end
This is an improvement. It is now clear that othersub has nothing to do with p, q, r, s, t, or u. Still, this isn't good enough: The fact that sub1 and sub2 share the variables isn't clear; the fact that othersub doesn't care about them isn't clear; nor is it clear who is responsible for the values of the shared variables.

Solution

You put the shared variables and subroutines that use them into a MODULE:

  module mysubs
    real, private:: p, q, r, s, t, u
    save
  contains
    subroutine init_mysubs
      p = 3.14
      q = -999.0
      r = p + 3*q
      read(*,*) s
      t = s * p
      u = t * t + s
    end subroutine init_mysubs

    subroutine sub1(a, b)
      ! . . . use p, q, r, s, t, and u . . .
    end subroutine sub1

    subroutine sub2(kkk)
      ! . . . use p, q, r, s, t, and u . . .
    end subroutine sub2

  end module mysubs


  subroutine othersub(x, y, z)
    use mysubs
    ! . . . . .
    call sub2(kkk)
  end

  program main
    use mysubs
    call init_mysubs
    call sub1(a, b)
    call othersub(x, y, z)
  end
Now, all the problems stated above are solved. 1) It's clear that p, q, r, s, t, and u are shared only by sub1 and sub2; 2) It's clear who is responsible for determining their values; 3) It's clear that othersub has nothing to do with them.

Details

Notice the PRIVATE keyword in the MODULE, which indicates that those variables are accessible only within the MODULE, implying that the MODULE is exclusively responsible for them. The PRIVATE specification isn't necessary, but it's better to have it if the variables aren't used outside the MODULE. Without it, the variables can be changed from outside the MODULE, a possibility that it's better to avoid.

More to come if I find the time . . .

. . . . . . . . .

Acknowledgments

Thanks to Zuojun.