Skip to content

SHPC: Fortran

The first of six posts covering content from SHPC4001 and SHPC4002


“FORTRAN is ancient, it’s a dinosaur”

— SHPC4001 Tutor

When I learned that SHPC4001 would be taught in Fortran, I half-joked about whether we’d be poking holes into punch cards. Venerable Fortran is, as our tutor happily pointed out, ancient. It is the second-oldest language currently in widespread use (second only to Lisp). Up until then I had understood Fortran to be one of those C pre-cursors used for supercomputing, but otherwise highly specialised. I came to appreciate rather quickly that Fortran is, in fact, more general-purpose than I had originally thought.

Fortran (FORmula TRANslation) is especially good at numerical and scientific computation. It is the world’s first high-level language, and the first to ship with an optimising compiler. Even to this day, Fortran’s speed, performance and ubiquity have cemented its place as the dominant language for scientific and high performance computing. In this post I will provide a brief overview of Fortran, at least enough to cover what was needed for the first few assignments.

Installing

The version of Fortran to be taught was Fortran 90/95. Most of the class used macOS, with the rest using Linux and a small subset holding out with Windows. To work with Fortran we need a compiler, either the Intel Fortran Compiler (popular with the Windows users) or GNU Fortran (now part of the GNU Compiler Collection or GCC). I personally used macOS and installed GCC with Homebrew:

$ brew install gcc

Fortran programs can then be compiled by running gfortran:

$ gfortran myprogram.f90 -o myprogram

and then executed as with any other executable (i.e. $./myprogram).

Variables

The first SHPC4001 lesson actually taught UNIX essentials, before briefly introducing Fortran (by way of example programs) then moving on to plotting with gnuplot. Immediately it became clear that those without prior programming experience were at a disadvantage, for the lecture notes did not describe the keywords, elucidate upon the program structure, nor were there any live programming demonstrations. Granted, there were ample external resources (including a draft textbook by the lecturer), but a few in-class demonstrations would have been nice instead of learning by experimentation. In my case, I drew on my past programming experience to find its similarities and differences with other languages I’ve used. I found Fortran’s syntax to be quite similar to Lua (especially with the for loops and if statements).

program test
   implicit none

   !comments start with ! (though Fortran77 uses c)

   integer :: i
   real :: x

   x = 1.0

   do i=1,10 !blocks are terminated with '\n'
      x = x*i
   end do
end !can also say end program or end program test

Above is a program for evaluating 10! (i.e 10 factorial). Fortran has some fairly strict rules. The most notable is that variables must be defined at the top (after implicit none but before the main program). So, after the line x = 1.0 we cannot go on and suddenly define another variable. Fortran’s compiler implicitly assumes that the variables i, j, k, l, m and n are integers, while every other variable is a real. As you can imagine, this can cause wacky behaviour with even the smallest of typos. implicit none forces the compiler to override this default behaviour, thus forcing all variables to be declared. This is the origin of the quote “GOD is real, unless declared integer“.

In Fortran, every variable must be defined, including variables used for iterating loops (such as i in the above example!). This is different to C/Java’s for loops where the loop variable is defined within the for statement. Furthermore, unlike in C, variables cannot be instantiated at the same time as when they are defined (i.e integer :: x = 0 is not allowed).

Fortran has several variable types, including complex for complex numbers, character for single alphanumerics and character(len=12) for strings and logical for booleans. Precision can also be defined, e.g real(4) for single precision (real defaults to real(4)) and real(8) for double-precision.

Fortran’s core structure is organised into blocks or scoping units. In general, these units begin with a keyword (such as if, do, etc) and are terminated with end if, end do, etc. Note that the entire program is itself a unit, but usually the program statement is terminated with end rather than end program (but the more explicit these end statements are, the easier it is to go back over your code).

A note on style

Before going any further I’d like to make a few comments regarding Fortran’s syntax and how it has evolved. Fortran used to require all keywords (such as integer, write, program, etc) to be capitalised. This is no longer required, but you will likely see programs that use capitals for keywords (I personally use all caps for the build-in / intrinsic functions). The syntax in Fortran 90/95 is relatively free compared to earlier iterations such as Fortran 77, that had strict rules regarding column position. Unlike Python, there is no need to indent anything, but it is standard practice to use indentation where possible. The one area where I occasionally forego indentation is between the program and implicit none statements (especially for shorter programs), but this is just a personal style choice.

Control and Looping

Fortran’s conditionals are fairly straightforward (make sure you remember the brackets):

if (a < b .or. a < c) then
   [do something]
else if (a > b) then
   [do something]
else
   [do something]
end !if optional

with the keyword then terminating the individual if statements (except for else). Fortran also has its own C/Java-like switch statement, albeit with slightly different syntax:

select case (variable)
   case (value)
      [do something]
   case (othervalue)
      [do something]
   case default
      [do something]
end !select optional

Fortran’s two key loops are the do and do while that work as typical for and while loops.

do [var]=[start,stop,step]
   [your code]
end do

do while [condition]
   [your code]
end do

Fortran also has the keywords cycle (i.e. continue) and break to either skip to the next iteration of a loop or exit the loop.

I/O

Input-output is pleasantly easy, with open, read, write and close. Each file must be assigned a flag (or label) when opened so that it can be later referenced by write and close.

write(10,*) 'hello'
close(unit=10) !or simply close(10)

Here the second * in write means to use default formatting. Fortran’s read is also handy in that we can easily control what happens when we reach the end of the file:

program read
implicit none
real :: a

open(1,file='test.dat',action='read')

do !infinite loop
   read(1,*,end=10) a
   write(*,*) a
end

10   write(*,*) 'Finished'

close(1)

Here 10 is a flag, and end=10 acts as a goto , enabling the jump out of the otherwise infinite loop. In the above example we are reading each line of the input file test.dat and printing it to stdout. We can use either write(*,*) or print to print to stdout while read(*,*), predictively, reads from stdin. Consider this example:

program test

   implicit none

   integer :: x

   read(*,*)x
   print *,x

end

Here the * in print *,x specifies the default formatting. In practice, it is much simpler to write to stdout using print, which essentially does the same thing as write(*,*) but is considerably more readable.

Formatting

When writing to file, it is useful to have some degree of control over how numbers and the like are formatted. There are two ways to format numbers, either directly in the write statement or by explicitly using a format label:

real :: x
x = 3.14159

write(*,'(E10.4)')x

write(*,10)x
10 format(E10.4)

where E10.4 specifies exponential notation to 4 decimal places (the 10 specifies the length of the output string; in this case, 0.3142E+01 is 10 characters long). Notice the lack of parentheses and quotes when using format as opposed to directly including the format in write.

Functions

There are two types of functions – normal functions, or simply functions (which must return a value), and subroutines (which need not return a value). Functions are called as you would typically expect (e.g x = myfunc(a,b)) but subroutines are explicitly called (e.g call mysubroutine(x,y)). Functions and subroutines can be defined externally (i.e outside of the program unit) or internally (within the program unit). If you choose to define them externally, then you must define the return type of the function as a variable within the main program.

program main
   implicit none
   real :: myfunc
   !main program code
end program

function myfunc(x,y) result(z)
   implicit none
   real, intent(in) :: x, y !specifies input parameters
   real :: z
   !function code
   z = !set return value
end function

We can even say real function myfunc(x,y) result(z) to avoid having to type real :: z.

Internally defined functions are simpler and preferred. We need to make use of the contains keyword:

program main
   implicit none
   !no need to define myfunc
   !program code

   contains

   function myfunc(x,y)
   !function code

end program

Subroutines

Subroutines are similar, although there have no return value. In order to “return” something we instead need to modify one of the parameters. We can specify this by using intent(out) or intent(inout).

program test

implicit none
real :: x,y,z

call mysub(x,y,z)

contains

subroutine mysub(x,y,z)
   implicit none
   real, intent(in) :: x, y
   real, intent(out) :: z
end subroutine

end program

Fortran also has support for optional arguments using the keyword optional. You can then check to see whether the argument was passed by using present. Note that optional arguments can only be used with internally-defined procedures unless you include an explicit interface.

function test(x,y) result(z)
   implicit none
   real, intent(in) :: x
   real, intent(in), optional :: y
   real :: z

   if (present(y)) then
      [do something]

   [rest of the code]
end function

If you have externally defined functions (i.e functions defined outside of the main program block), you can write an interface to describe that function. Essentially this involves grouping the function and variable declarations inside of an interface block. This is necessary in order to use optional arguments and assumed-shape arrays. Personally I’ve never used these as internally-defined functions are much easier to work with.

Recursion

If you want to make a procedure recursive (i.e make it repeatedly call itself), you must declare this with the recursive keyword. For example:

recursive function factorial(n) result(m):
   integer, intent(in) :: n
   integer :: res

   if (n == 1) then
      res = 1
   else
      res = n*factorial(n-1)
   end if
end function factorial

Arrays

Fortran’s arrays index from 1 instead of 0. We define arrays by specifying their dimension (e.g real, dimension(5) :: x sets x to be an array with 5 elements, accessed by x(1) and so on). Multidimensional arrays are defined with dimension(5,5) and accessed by x(1,1)and so on. We can also specify the shape of the array directly by including the number in the variable declaration, or simply construct the array from a list of values.

real, dimension(5) :: a  !either use the dimension keyword
real :: b(5)  !or directly specify the number

! either syntax below is okay
a = [1, 2, 3, 4, 5]
b = (/1, 2, 3, 4, 5/)

We can specify arrays with undetermined lengths by using dimension(:) as well as the allocatable keyword. This allows us to dynamically manage the memory associated with that array using allocate:

program main
   implicit none
   real, dimension(:), allocatable :: x
   real :: a

   read(*,*) a !read size from standard in
   allocate(x(a),stat=ierr)
   if (ierr /= 0) stop !exit in case of an error
   !perform things with x
   if (allocated(x)) deallocate(x) !free up memory when done
end

Like Python’s list comprehensions, Fortran allows you to quickly construct an array with an implied do loop:

a = [(i**2,i=1,10,1)]

Like R, Fortran’s array operations are performed on a per-element basis.

real :: a(5), b(5), c(5)

c = a + b !per-element addition

Element slicing has the syntax start:stop:step:

a(3:5) ! gives [a(3),a(4),a(5)]
a(1:10:2) ! gives [a(1),a(4),a(7),a(10)]

b(1,:) ! gives [b(1,1),b(1,2),b(1,3),...]

Where to from here?

I hope this post has piqued your interest in Fortran. There are a wealth of resources out there from online tutorials to textbooks. I recommend the following:

In addition to these, introductory textbooks on Scientific Computing or related disciplines are bound to include either Fortran or C.