Skip to content

Advanced Fortran

⚠️ We assume in the following course basic knowledge in computer programming: CPU, Memory, source code vs. executable code, compilers vs. interpreter, etc.

Exercises

Go to this page

Bibliography

  • Fortran: Le langage normalisé by M. Dubesset & J. Vignes, Technip Ed.

  • fortran 95/2003 explained by M. Metcalf, J. Reid, and M. Cohen, Oxford Univ. Press

Introduction

Fortran is a programming language widely used in the scientific community. It is also one of the oldest programming language.

If C language was described as the "mother" of modern programming languages (C++, Java, Python, PHP, Perl, etc.), Fortran could be regarded as the "father": it is older (1960's) but it is still widely used, especially in the HPC (High Performance Computing) community since its programs can be translated (compiled) into most efficient executables in terms of CPU time. At the turn of the century, Fortran language has changed to incorporate many features of "modern" programming languages (i.e., modules, interfaces, object-oriented schemes, etc.) as well as unique features targeted to HPC programming (i.e., array management, high-level I/O management, etc.).

Fortran's superiority had always been in the area of numerical, scientific, engineering, and technical applications.


Fortran is an acronym for FORtran TRANslation. It was originally created by John Backus while working at IBM in 1954. It was approved by the American Standards Association (later the American National Standards Institute, ANSI) as the first ever standard for programming language: Fortran 66.

Following the growing success of Fortran 66, a new standard, Fortran 77, was published to incorporate many compiler implementation features targeted to large-scale programs (a.k.a., compiler/vendor extensions).

With the emergence of new modern programming languages (C++, Java, etc.) new Fortran standardization was started in 1990's. To preserve the vast investment in Fortran 77 codes, the whole of Fortran 77 was retained as a subset. However, unlike the previous standard, which resulted almost entirely from an effort to standardize existing practices, the Fortran 90 standard was much more a development of the language, introducing features which were new to Fortran, but were based on experience in other languages.

In 1995, an improved Fortran standard, Fortran 95, was published. This minor revision is mostly compatible with Fortran 90 and includes HPF (High Performance Fortran) features targeted for parallel computers.

The last major Fortran revision was published in 2004 (Fortran 2003). Among other things, it includes object-oriented programming support, floating point exception handling, or interoperability with the C programming language.

Fortran 77: an old language widely used

Language elements

Fortran makes use of words built from characters that are part of an alphabet. These words are combined to form sentences that follow the rules of the Fortran syntax.

Basic elements

The Fortran alphabet

The Fortran alphabet is composed of:

  • the 26 letters from A to Z

⚠️ Fortran does not make the difference between UPPER case and lower case

  • the 10 digits from 0 to 9

  • some special characters like +, *, -, /, ., =, etc.

  • the space (or blank) character

Keywords

Fortran makes also use of reserved words (a.k.a., keywords) that have special meanings. For example: END, RETURN, DOUBLE, INTEGER, etc.

Identifiers

Identifiers are words designed by the user. They are built 1 to 6 alphanumerical characters. The first character of an identifier must be a letter.

Identifiers serve to indicate names of programs, functions, variables, etc.

Labels

A label is an integer composed of 1 to 5 digits that serves to identify instructions (see below).

Sentences

A sentence is composed of a succession of syntax elements and should have a special meaning in the Fortran programming language.

For example:

A = 3
SUBROUTINE toto

Program units

A program unit is composed a series of sentences that form a block. This is either a main program or a sub-program.

A main program starts with the declaration:

      PROGRAM name

A sub-program is either:

  • a SUBROUTINE

  • a FUNCTION

  • a BLOCK DATA

Codification

When the Fortran programming language was created, the only way to write source codes was by using punch-cards. While this support has been now abandoned, Fortran 77 still uses its structure:

  • a file containing a Fortran program is composed of 80 characters lines

  • For each line:

    • column 1 has a particular role: if starting with C, it refers to a comment
    • column 1 to 5 can contain a label
    • column 6 is used as the continuation mark. If the character in column 6 is not a blank character, then column 7 to 72 should be considered as the continuation of the previous line.
    • column 7 to 72 contains the Fortran instruction
    • column 73 to 80 are not compiled and can therefore be used for user labelling (usually, one puts the name of the program and the line numbering)

Constants

Numerical constants

integer constants

0
-1
91
234242342

real constants

3.1415
0.
-1.
+0.25
-2.47E+1
5.14D-2

⚠️ the D character designs exponentiation for DOUBLE PRECISION floating point numbers.

Logical constants

logical constants

.TRUE.
.FALSE.
Character constants

Character constants are also named Hollerith constants.

Character constants

'AR.843-123'
'WITH A SPACE'
'TODAY''S SPECIAL MENU'
Symbolic constants

Symbolic constants

PARAMETER (PI=3.141592)
PARAMETER (M=1, N=2)

The compiler will replace the symbolic names by their corresponding constant values.

Variables

Variable name

Variables names

A
B
TOTO
RUDOPH
HAL1969
MY_2K_BUG

⚠️ The Fortran 77 standard implies the use of 1 to 6 characters maximum to name a variable. However, compilers usually accept longer names (= this is a compiler extension, not in the standard).

Variable types

Standard variable types:

  • INTEGER

  • LOGICAL

  • REAL

  • DOUBLE PRECISION

  • COMPLEX

  • DOUBLE COMPLEX

  • CHARACTER

The IMPLICIT statement

The IMPLICIT statement enable the implicit type declaration of variables.

By default, a Fortran program has the following IMPLICIT statement:

default IMPLICIT statement

IMPLICIT INTEGER (I-N), REAL (A-H, O-Z)

This means that all variables with names starting with letters between I and N will be declared as INTEGER, while all other variables will be implicitly considered as REAL.

⚠️ the statement IMPLICIT NONE is not part of the Fortran 77 standard. But there again, it is a common Fortran extension that prevents implicit declaration of variables: any variable used in a program unit must have its type declared.

Arrays

By default, arrays in Fortran are indexed starting from the index 1.

The DIMENSION keyword starts a statement indicating the size of an array. Parentheses are used to indicate the size of an array as well as to access an element of an array.

Array examples

PARAMETER (MAXEL=10)
DIMENSION A(MAXEL)

INTEGER B
DIMENSION B(100)

DIMENSION C(0:10)
DIMENSION D(-10:10)
DIMENSION E(10,10)
REAL F
DIMENSION F(-MAXEL:MAXEL,MAXEL+2:5*MAXEL/3)

B1 = B(10)
EE = E(5,3)

⚠️ in Fortran, elements are ordered in memory column by column. For example, for a two-dimensional array B:

DIMENSION B(4,3)

The storage in memory is contiguous for B(1,1), B(2,1), B(3,1), B(4,1), B(1,2), B(2,2), B(3,2), B(4,2), B(1,3), B(2,3), B(3,3), B(4,3).

Character variables
CHARACTER*18 NAME, FIRST
CHARACTER TOWN*40, ZIP*5
CHARACTER*20 A, B

FIRST='RUDOLPH'
NAME='VALENTINO'
ZIP='54000'

A='VANDOEUVRE'
B='-LES-NANCY'
TOWN=A//B

The // operator is the concatenation operator.

Substrings can be accessed via the : operator:

TOWN(10:13)
TOWN(:10)
TOWN(12:)

Operators

Numerical operators
  • +, -, /, *, **
Logical operators

Logical operators operating on logical variables or constants:

  • .NOT.

  • .AND.

  • .OR.

  • .EQV.

  • .NEQV. Logical operators operating on integer or real variables and constants

  • .EQ. (valid also for complexes)

  • .NEQ. (valid also for complexes)

  • .LT.

  • .LE.

  • .GT.

  • .GE.

Lexical operators
  • LGT(str1, str2)

  • LGE(str1, str2)

  • LLT(str1, str2)

  • LLE(str1, str2)

Control structures

GOTO

      A = 10
      GO TO 100
      ...
  100 B = -3
      ...
      GOTO 200
      ...
  200 CONTINUE
      C = -14.2

Branching GO TO:

      K = 2
      ...
      GO TO (1, 2, 3, 4, 5) K
      PRINT *, 'UNKNOWN VALUE FOR K'
      GOTO 10

    1 PRINT *, 'K = 1'
      GOTO 10

    2 PRINT *, 'K = 2'
      GOTO 10

    3 PRINT *, 'K = 3'
      GOTO 10

    4 PRINT *, 'K = 4'
      GOTO 10

    5 PRINT *, 'K = 5'

   10 CONTINUE

Branching

      DELTA = B*B-4*A*C
      IF (DELTA) 10, 20, 30

   10 PRINT *, 'NO SOLUTION'
      GOTO 40

   20 PRINT *, 'ONE SOLUTION'
      GOTO 40

   30 PRINT *, 'TWO SOLUTION'

   40 PRINT *, 'BYE'
      IF (A.GT.B) C = 10

      IF (L.EQ..TRUE.) THEN
         PRINT *, 'SOMETHING'
      END IF

      IF (RAD.LE.PI) THEN
         DEG = RAD/PI*180.0
      ELSE
         DEG = -(RAD-PI)/PI*180.0
      END IF

      IF (DELTA.LT.0) THEN
        ...
      ELSE IF (DELTA.EQ.0) THEN
        ...
      ELSE
        ...
      END IF

Loops

      I1 = 1
      I2 = 10
      I3 = 3
      DO 10 I = I1, I2, I3
        PRINT *, I
   10 CONTINUE
      PRINT *, 'LOOP COMPLETED'

Common Fortran extensions:

      DO I = I1, I2
        ...
      END DO

      DO WHILE (SOMETHING.EQ..TRUE.)
        ...
      END DO

Functions and subroutines

In Fortran, one distinguish FUNCTION from SUBROUTINE. The former is a sub-program which specifically returns a value while the latter does not.

      DOUBLE PRECISION FUNCTION DELTA1(A, B, C)
      IMPLICIT NONE
      DOUBLE PRECISION A, B, C
      DELTA1 = B*B - 4*A*C
      RETURN
      END
      SUBROUTINE DELTA2(A, B, C, DELTA)
      IMPLICIT NONE
      DOUBLE PRECISION A, B, C, DELTA
      DELTA = B*B - 4*A*C
      RETURN
      END
      PROGRAM SECOND
      IMPLICIT NONE
      DOUBLE PRECISION A, B, C, D1, D2

      A = 1.0
      B = 4.0
      C = 4.0
      D1 = DELTA1(A, B, C)
      CALL DELTA2(A, B, C, D2)

      END
INTRINSIC

INTRINSIC functions in Fortran refers to functions provided by the language and therefore handled by the compiler.

EXTERNAL
SAVE
Array argument of unknown dimension
      program second_order
      implicit none
      double precision coeff
      dimension coeff(3)
      double precision delta

      data coeff/1.0d0, 6.0d0, 1.0d0/

      call delta_calc(coeff, delta)
      print *, delta

      end
      subroutine delta_calc(a, delta)
      implicit none
      double precision a, delta
      dimension a(*)
      delta = a(2)*a(2)-4*a(1)*a(3) 
      return
      end

Data management

COMMON
EQUIVALENCE
DATA
BLOCK DATA

Input/Output

File Units

Files are composed of logical entities called records. Records can be lines, variables, arrays, etc. They are groups of related entities.

Records can be formatted or unformatted. Formatted records are composed of characters with significant meaning. Unformatted records are stored on the computer in binary format.

A file is composed of records of the same kind: formatted or unformatted. They can have sequential or direct access.

Fortran does not directly manipulate files. It assigns a logical unit and defines operations for this logical unit.

A logical unit is an integer number (usually < 100).

Reserved logical units are:

  • 5 (or '*'): the standard input

  • 6 (or '*'): the standard output

  • 0: the standard error output

Files can be accessed in three different ways:

  • create

  • write/append

  • read

To access a file, it must be opened and assigned a logical unit:

      OPEN(unit=n, options)

Options of the OPEN subroutine:

  • FILE=filename

  • STATUS='OLD' or STATUS='NEW' or STATUS='UNKNOWN'

  • FORM='FORMATTED' or FORM='UNFORMATTED'

  • ERR=label

  • IOSTAT=int

When working with a logical unit is finished, one must close the file using the CLOSE subroutine:

      CLOSE(unit)

The REWIND subroutine allows for starting reading back at the beginning of a file when using sequential access.

The INQUIRE subroutine can give the state of a file (e.g., existing or non-existing).

      INQUIRE(UNIT=unit, options)

INQUIRE options are:

  • FILE=filename: to specify the filename

  • EXIST=logic: put in the logic variable a logical number stating if the file exists or not

  • NUMBER=num: put in num the logical unit number of the file, if it has been previously opened

READ/WRITE/PRINT

      unit1 = 5
      unit2 = 6
      READ(unit1, 10) a
      WRITE(unit2, 20) a
      PRINT 20, a
   10 FORMAT (f)
   20 FORMAT (f20.10)

or

      READ(5, '(f)') a
      WRITE(*, '(F20.10)') a
      PRINT '(f20.10)', a

FORMAT

Common flags for FORMAT statement:

  • f: floating point

  • a: character or string

  • i: integer

  • l: logical

Flags can be repeated in a format:

      dimension ii(4,3)
      data ii /1,2,3,4,5,6,7,8,9,10,11,12/
   10 FORMAT (3i5)
      WRITE(*,10) ((ii(i,j),j=1,3),i=1,4)

Internal Files

String can be read and written like logical unit

      CHARACTER*20 str
      WRITE(str, 100) 3.1415
  100 FORMAT (f20.5)

Fortran 90/95/03: the new Fortran era

Language elements

Free Form

No column requirement in Fortran90 source code 😄

But implicit statement still active by default 😟

Comment lines start now with ! (instead of C or c)

program my_f90
! a comment line
write(*,*) 'Hurray for Fortran90'
end

Variable declaration

The main difference is the use of ::to declare variables.

 integer :: a
 double precision :: b
 character(len=20) :: string
 real, dimension(10) :: T
 integer, parameter :: constant = 10

In Fortran90, you can define your own type:

program toto
type cartesian
  real :: x
  real :: y
  real :: z
end type cartesian
type(cartesian) :: point1, point2
point1%x = 1.0
point1%y = 2.0
point1%z = 3.0
print *, point1
point2 = cartesian(1.0, -1.0, 3.0)
print *, point2
end

Logicals

Relational operators:

Fortran77 (still accepted) new Fortran90 syntax
.LE. <=
.LT. <
.GE. >=
.GT. >
.EQ. ==
.NE. /=

Strings

Better handling of strings with Fortran90 since new INTRINSIC functions exists:

 character(len=20) :: town = 'NANCY'
 character(len=20) :: addon= '-LES-BAINS'
 character(len=len(town)+len(addon)) :: townname 

 townname = town//addon
 print '("|",a,"|")', town
 print '("|",a,"|")', addon
 print '("|",a,"|")', townname

 townname = town(:len_trim(town)) // addon(:len_trim(addon))
 print '("|",a,"|")', townname
end

Control constructs

Control constructs are similar as in Fortran 77. Preferred version are:

IF (test) THEN
  ...
ELSE [IF (test) THEN]
  ...
ENDIF

DO I = I1, I2 [, I3]
  ...
END DO

The EXIT statement can be used in loops, especially in an endless loop:

integer :: i
i = 10
DO
  PRINT '("IN LOOP, I = ", i3)', i
  i = i -1
  IF (i == 0) EXIT
END DO
PRINT *, 'Finished'

Program units and procedures

In Fortran90, you can control what goes in and what goes out of a function or a subroutine:

program second_order_f90
 double precision :: a = 1.0
 double precision :: b = 6.0
 double precision :: c = 1.0
 double precision :: delta

 print 10, a, b, c

 call delta_calc(a, b, c, delta)

 print '(f10.5)',  delta

 10 format (3f10.5)
end

subroutine delta_calc(a, b, c, delta)
 double precision, intent(in) :: a
 double precision, intent(in) :: b
 double precision, intent(in) :: c
 double precision, intent(out) :: delta

 delta = b*b-4.0d0*a*c
end

Fortran90 introduces the concept of module which provides a mechanism for packaging all data, subroutines, etc. related to one specific group.

For example, if one defines a new type, then a module can contain the type definition as well as all functions and subroutines related to the use of that new type.

module second_order
  type polynome
    double precision :: a
    double precision :: b
    double precision :: c
  end type polynome
 contains
  function delta(P)
    implicit none
    double precision :: delta
    type(polynome) :: P
    delta = P%b * P%b - 4.0d0 * P%a * P%c
    return
  end function delta
end module

program test
  use second_order
  type(polynome) :: my_equation

  my_equation%a = 1.0
  my_equation%b = 6.0
  my_equation%c = 1.0

  print *, delta(my_equation)
end program

Arrays

DATA statement can be replaced by direct initialization for arrays:

integer, parameter :: constant = 10
integer, dimension(constant) :: Tab = (/1, 2, 3, 4, 5, 6, 7, 8, 9, 10/)
integer, dimension(4) :: AnotherTab
data AnotherTab/1, 2, 1, 0/
print *, constant
print *, Tab
print *, AnotherTab
end

You can slice arrays !

integer, parameter :: constant = 10
integer, dimension(constant) :: Tab = (/1, 2, 3, 4, 5, 6, 7, 8, 9, 10/)
print '(10i4)', constant
print '(10i4)', Tab
print '(10i4)', Tab(3:5)
print '(10i4)', Tab(:5)
print '(10i4)', Tab(8:)
print '(10i4)', Tab( (/1, 3, 4, 5, 3/) )

print '(10i4)', Tab
Tab( (/1, 3, 5, 7, 9/) ) = (/0, -2, 0, -2, 0/)
print '(10i4)', Tab
end

Vectors or arrays can be directly addressed without the use of DO loops.

integer, parameter :: constant = 10
integer, dimension(constant) :: Tab1 = (/1, 2, 3, 4, 5, 6, 7, 8, 9, 10/)
integer, dimension(constant) :: Tab2 
integer, dimension(constant) :: Tab3

data Tab2 /10*2/

Tab3 = Tab1+Tab2
print '(1x,10i3)', Tab1
print '("+",10i3)', Tab2
print '("=",10i3)', Tab3
print *

Tab3 = Tab1*Tab2
print '(1x,10i3)', Tab1
print '("*",10i3)', Tab2
print '("=",10i3)', Tab3
end
integer, dimension (4,3) :: B
integer, dimension (2,2) :: C
integer, dimension (2,2) :: D

data B /1,2,3,4,5,6,7,8,9,10,11,12/
data C /-1, -2, -3, -4/

print *, 'B Matrix:'
print '(3i3)', ((B(i,j),j=1,3),i=1,4)
print *, 'C Matrix:'
print '(2i3)', ((C(i,j),j=1,2),i=1,2)

D(1:2, 1:2) = B(3:4, 2:3) + C(1:2, 1:2) - 5
print *, 'D Matrix:'
print '(2i3)', ((D(i,j),j=1,2),i=1,2)
end

Array size does not have to be specified at the compilation. They can be dynamically allocated:

program dynamic
  double precision, dimension(:), allocatable :: T
  integer :: my_size

  read(*,'(i)') my_size
  allocate(T(my_size))
  T = 1.5
  print '(10f8.3)', T
  deallocate(T)
end

The WHERE statement helps you to perform operation only for certain elements:

program dynamic
  double precision, dimension(:), allocatable :: T
  integer :: my_size

  print *, '("T size ?")'
  read(*,'(i)') my_size
  allocate(T(my_size))
  do i = 1, my_size
    write(*,'("element ", i3, "?")') i
    read(*, '(f)') T(i)
  end do

  print *, "T as you defined it:"
  print '(10f8.3)', T

  print *, "Absolute value of T:"
  where (T < 0) T = -T
  print '(10f8.3)', T

  deallocate(T)
end
program tablog
  integer, parameter :: my_size = 10
  integer, dimension(my_size) :: TAB_I
  double precision, dimension(my_size) :: TAB_F

  integer i

  do i = 1, my_size
    TAB_I(i) = i*7-5
  end do

  where (mod(TAB_I,2) == 0) TAB_I = -TAB_I
  print '(10i4)', TAB_I

  where (TAB_I > 0)
    TAB_F = log(DBLE(TAB_I))
  elsewhere
    TAB_F = log(-1.0d0*TAB_I)
  end where

  print '(10f8.3)', TAB_F
end

In some cases, DO loops can be replaced by FORALL:

program test_forall

double precision, dimension(12) :: A
integer, dimension(4, 3) :: B
integer :: i, j, k

do i = 1, 12
  A(i) = 3.0d0*i-2.5d0
end do
print '(12f8.3)', A

print *
forall(i=1:12) A(i) = 3.0d0*i-2.5d0
print '(12f8.3)', A

do i = 1,4
  do j = 1, 3
    B(i,j) = i+(j-1)*4
  end do
end do

print '(3i3)', ((B(i,j), j = 1,3), i = 1, 4)

print *
forall(i=1:4, j=1:3) B(i,j) = i+(j-1)*4
print '(3i3)', ((B(i,j), j = 1,3), i = 1, 4)

print *
forall(i=1:4, j=1:3)
 B(i,j) = FLOOR(A(i+(j-1)*4))
end forall
print '(3i3)', ((B(i,j), j = 1,3), i = 1, 4)

end

Object-oriented programming

The use of module to contain new type definitions and subroutines/functions related to these new types is the beginning of Object-oriented programming (= it resembles class definition).

Fortran90 provide a way to override common operators using the INTERFACE statement.

module fract
  type fraction
    integer :: numerator
    integer :: denominator
  end type fraction

  interface operator(+)
    module procedure add
  end interface

 contains

  function add(f1, f2)
    implicit none
    type(fraction) :: add
    type(fraction), intent(in) :: f1, f2
    integer a, b, c, d
    a = f1%numerator
    b = f1%denominator
    c = f2%numerator
    d = f2%denominator
    add%numerator = a*d+b*c
    add%denominator = b*d
    return
  end function add

end module fract

program test_interface
use fract
type(fraction) :: F1
type(fraction) :: F2
type(fraction) :: F3
F1%numerator = 2
F1%denominator = 3
F2%numerator = 1
F2%denominator = 4
F3 = F1+F2
print *, F1
print *, F2
print *, F3
end

Fortran90 provides also PUBLIC and PRIVATE statement for attributes and methods (=functions or subroutines); inheritance possibilities using the EXTENDS statement; encapsulation; polymorphism (using the INTERFACE block statement); etc.