Lead Image © homestudio, 123RF.com

Lead Image © homestudio, 123RF.com

Modern Fortran for today and tomorrow

Up To Date

Article from ADMIN 39/2017
By
Fortran development is still progressing, with a new version scheduled for release in 2018. We look at Fortran's evolution into a modern language for HPC.

Fortran has been one of the key languages for technical applications since almost forever. With the rise of C and C++, the popularity of Fortran waned a bit, but not before a massive library of Fortran code had been written. The rise of scripted languages such as Perl, Python, and R also dented Fortran a bit, but it is still in use today for many excellent reasons and because of a HUGE library of active Fortran code.

Fortran 90 took Fortran 77 from the dark ages by giving it new features that developers had wanted for many years and by deprecating old features – but this was only the start. Fortran 95 added new features, including High Performance Fortran (HPF), and improved its object-oriented capabilities. Fortran 2003 then extended the object-oriented features; improved C and Fortran integration, standardizing it, and making it portable; and added a new range of I/O capabilities. Features and capabilities still on the developers' wish lists led to Fortran 2008 and concurrent computing.

I still code in Fortran for many good reasons, not the least of which is performance and readability.

Origins

Fortran was originally developed as a high-level language so code developers didn't have to write in assembly. It was oriented toward "number crunching," because that was the predominant use of computers at the time (Facebook and YouTube weren't around yet). The language was fairly simple, and compilers were capable of producing highly optimized machine language. Fortran had data types that were appropriate for numerical computations, including a complex data type (not many languages have that), and easily dealt with multidimensional arrays, which are also important for number crunching. For all of these reasons, Fortran was the language of numerical computation for many years.

The advent of C caused a lot of new numerical code to be written in C or C++, and web and systems programming also moved to other languages, such as Java, Perl, and Python. However, Fortran is definitely not dead, and a lot of numerical code is still being written today using modern Fortran.

Fortran 77

Fortran began in about 1957 with the advent of the first compiler. It was used a great deal because it was much simpler than assembly language. In my experience, Fortran 77 (F77) brought forth the features that made the language easier to use to create many algorithms. As a result, a lot of older Fortran code was rewritten for F77.

In the past, Fortran was not case sensitive, so a lot of code was written in uppercase, creating a precedent followed for many years. You will find that Fortran coders of some lineage code in uppercase. However, in this article I mix upper- and lowercase in the examples.

In F77, a very useful way to share data across functions and subroutines was the use of common blocks, in which you define multiple "named" blocks that contain variables. (They could be of mixed type, including arrays.) A typical scenario was to put the common blocks in an include file and put each function or subroutine into its own file. Each file that needed access to the common block "included" the file of common blocks.

Another feature, or limitation, of Fortran is that all arrays have to be fixed in size at compile time (no dynamic memory), so if you declare an array x(100,100), you cannot change the dimensions or size after the code has been compiled. One trick was to define one very large vector and then "give" parts of it to various portions of the code. Although a little messy, you could simulate dynamic memory if the array was large enough.

Fortran also had fixed formatting. Some languages sort of have this today, such as Python, which requires indentation. In Fortran, the first column of each line could contain a C or c, indicating the beginning of a comment line, although you couldn't put comments just anywhere in the code. It could also be used for a numerical label. Column 6 was reserved for a continuation mark, so that lines that were longer than one line could be continued. The code began in column 7. Columns 1 to 5 could be used for statement labels (Listing 1; note that the first and last three lines are not code, but guides to show how code aligns in columns).

Listing 1

Sample Fortran 77 Code

         11111111112222222222
12345678901234567890123456789
-----------------------------
      SUM = 0.0
      D0 100 I=1,10
         SUM = SUM + REAL(I)
 100  CONTINUE
...
      Y = X1 + X2 + X3 +
     1    X4 + X5 + X6
-----------------------------
         11111111112222222222
12345678901234567890123456789

By default, F77 defines variables starting with (upper- or lowercase) i, j, k, l, m, and n as integers, so they are used as do loop counters. Variables that use any other letter of the alphabet are real unless the code writer defines them to be something else (they could even define them to be integers). You could turn off this implicit definition with IMPLICIT NONE just after the program, subroutine, or function name, which forces defining each and every variable (not necessarily a bad thing).

Fortran 90

The next Fortran standard, referred to as Fortran 90 (F90), was released in 1991 as an ISO standard, and in 1992 as an ANSI standard. Fortran 90 is a huge step up from F77, with a number of new features added and a number of older features deprecated. Fortran 90 was the first big step in creating modern Fortran.

The first new feature of importance was free-form source input. You were now allowed to put the code anywhere you wanted, and you could label statements, for example:

sum = 0.0 all: do i=1,10 sum = sum + real(i) enddo all

The next big feature in F90 is my personal favorite – allocatable arrays. In Listing 2, line 3 shows how to define an allocatable array. In this case, array a is a 2D array defined by (:,:). The allocation does not occur until line 7. Along with the array allocation is a "status" that returns a nonzero value if the allocation was unsuccessful or 0 if it was successful.

Listing 2

Allocatable Arrays

01   PROGRAM TEST1
02   IMPLICIT NONE
03   REAL, ALLOCATABLE :: a(:,:)
04   INTEGER :: n
05   INTEGER :: allocate_status
06   n=1000
07   ALLOCATE( a(n,n), STAT = allocate_status)
08   IF (allocate_status /= 0) STOP "Could not allocate array"
09 ! Do some computing
10   DEALLOCATE( a )
11   END PROGRAM TEST1

Allocatable arrays use heap memory and not stack memory, so you can use a lot more memory. For almost any large arrays, I always use allocatable arrays to make sure I have enough memory.

After performing some computations, you then deallocate the array, which returns the storage to the system. Line 9 is a comment line. Fortran 90 allows you to put a comment anywhere by prefacing it with an exclamation mark.

Another feature added to F90 was array programming, which allowed you to use array notation rather than do loops. For example, to multiply two, 2D arrays together, use c = matmul(a, b), where a, b, and c are compatible arrays. You could also use portions of an array with d(1:4) = a(1:4) + b(8:11).

Unlike CPU-specific code, typically with loop unrolling to achieve slightly better performance, Fortran coders used array notation because it was so simple to write and read. The code was very portable, because compiler writers created very high performance array intrinsic functions that adapted to various CPUs. Note that this included standard intrinsic mathematical functions such as square root, cosine, and sine.

Fortran 90 also introduced derived data types (custom data types). The simple example in Listing 3 shows how easy it is to create several derived data types (lines 8-15). The derived (customer data) type has variables (e.g., i, r, and r8), a fixed-size array (array_s, which is allocated on the stack), an allocatable array (array_h, which is allocated on the heap), and another derived type, meta_data. Using derived types within derived types allows you to create some complex and useful custom data types.

Listing 3

Derived Data Type

01 program struct_test
02 type other_struct
03    real :: var1
04    real :: var2
05    integer :: int1
06 end type other_struct
07
08 type my_struct ! Declaration of a Derived Type
09    integer :: i
10    real :: r
11    real*8 :: r8
12    real, dimension(100,100) :: array_s           ! Uses stack
13    real, dimension(:), allocatable :: array_h    ! Uses heap
14    type(other_struct), dimension(5) :: meta_data ! Structure
15 end type my_struct
16
17 !  Use derived type for variable "a"
18    type(my_struct) :: a
19 ! ...
20    write(*,*) "i is ",a%i
21
22 !  Structures (variables) of the the derived type my_struct
23    type(my_struct) :: data
24    type(my_struct), dimension(10) :: data_array
25
26 end program struct_test

To access a specific part or member of a derived type, you simply use a percent sign (%), as shown in line 20. You can also make an allocatable derived type (lines 23-24).

A convenient feature called modules, and generically referred to as "modular programming" [1], was added to F90. This feature allows you to group procedures and data together. Code can take advantage of a module, and you can control access to it through the use of simple commands (e.g., public, private, contains, use). For all intents and purposes, it replaces the old common blocks of F77.

Modules are extremely useful. They can contain all kinds of elements, such as parameters (named constants), variables, arrays, structures, derived types, functions, and subroutines. The simple example in Listing 4 defines the constant pi and then uses the module (line 7) to make the contents available to the program.

Listing 4

Modules

01   module circle_constant
02   real, parameter :: pi = 3.14159
03   end module circle_constant
04
05   program circle_comp
06 ! make the content of module available
07   use circle_constant
08   real :: r
09 !
10   r = 2.0
11   write(*,*) 'Area = ', pi * r**2
12   end program circle_comp

The example in Listing 5 uses contains within a module to access content outside of the module. You can go one step further and denote public components (the default) that can be used outside the module and private components that can only be used within the module, giving you a lot more control over what can be accessed by other parts of the code.

Listing 5

Accessing External Content

01   module circle_constant
02   real, parameter :: pi = 3.14159
03
04   type meta_data
05     character(len=10) :: color
06     real :: circumference
07     real :: diameter
08     real :: radius
09     real :: area
10   end type meta_data
11
12   contains
13     subroutine meta_comp(r, item)
14        type(meta_data) :: item
15        item%diameter = 2.0 * r
16        item%area = pi * r **2
17        item%circumference = 2.0 * pi * r
18     end subroutine
19
20   end module circle_constant
21
22   program circle_comp
23 ! make the content of module available
24   use circle_constant
25   real :: r
26   integer :: iloop
27   type(meta_data), dimension(10) :: circles
28 !
29   r = 2.0
30   circles(1)%radius = 4
31   circles(1)%color = "red"
32   call meta_comp(r, circles(1))  ! Call the module function
33
34   circles(2:10)%color = "blue"   ! array operation
35   r = 4.0
36   circles(2:10)%radius = r       ! array operation
37   do iloop=2,10
38      call meta_comp(r,circles(iloop))
39   end do
40
41   end program circle_comp

POINTER was a new type of variable in F90 that references data stored by another variable, called a TARGET. Pointers are typically used as an alternative to allocatable arrays or as a way to manipulate dynamic data structures, as in linked lists.

A pointer has to be defined as the same data type and rank as the target and has to be declared a pointer. The same is true for the target, which has to have the same data type as the pointer and be declared a target. In the case of array pointers, the rank, but not the shape (i.e., the bounds or extent of the array), has to be specified. The following are simple examples of a declaration

INTEGER, TARGET :: a(3), b(6), c(9)INTEGER, DIMENSION(:),POINTER :: pt2

and multidimensional arrays:

INTEGER, POINTER :: pt3(:,:)
INTEGER, TARGET :: b(:,:)

You cannot use the POINTER attribute with the following attributes: ALLOCATABLE, EXTERNAL, INTRINSIC, PARAMETER, INTENT, and TARGET. The TARGET attribute is not compatible with the attributes EXTERNAL, INTRINSIC, PARAMETER, and POINTER.

Using pointers is very simple, having only two operators (Listing 6). In some code, pointers are also used with blocks of dynamic memory (lines 13-17), for which you still have to use the allocate statement (function). In this example, PTR2 points to a single real value, and PTRA points to a block of dynamic memory for 1,000 real values. After you're done using the block of memory, you can deallocate it. In this regard, the pointers behave very much like allocatable arrays. One other common use for pointers is to divide arrays into smaller sections with the pointer pointing to that subsection (Listing 7).

Listing 6

Pointers

01   PROGRAM PTR_TEST1
02   INTEGER, POINTER :: PTR1
03   INTEGER, TARGET :: X=42, Y=114
04   INTEGER :: N
05   REAL, POINTER :: PTR2:wq:w
06   REAL, POINTER :: PTRA(:)
07 ! ...
08   PTR1 => X    ! PTR1 points to X
09   Y = PTR1     ! Y equals X
10   PTR1 => Y    ! PTR1 points to Y
11   PTR1 = 38    ! Y equals 38
12
13 ! Dynamic memory blocks
14     N = 1000
15     ALLOCATE( PTR2, PTRA(N) )
16 ! Do some computing
17     DEALLOCATE(PTR2, PTRA)
18
19   END PROGRAM PTR_TEST1

Listing 7

Pointers and Arrays

01   program array_test
02   implicit none
03   real, allocatable, target :: array(:,:)
04   real, pointer :: subarray(:,:)
05   real, pointer :: column(:)
06   integer :: n
07   integer :: allocate_status
08 !
09   n = 10
10   allocate( array(n, n), stat  = allocate_status )
11   if (allocate_status /= 0) stop "Could not allocate array"
12 !
13   subarray => array(3:7,3:7)
14   column => array(:,8)
15 !
16   nullify(subarray)
17   nullify(column)
18   deallocate(array)
19
20   end program array_test

A linked list [2], a sequence of "nodes" that contain information and point to the next node in the sequence, is a very useful data structure. Variations allow the node to point to the previous node. Although it's not difficult to write for specific cases, generic linked lists [3] are more difficult. One instructive example online creates a linked list manager [4].

Fortran 90 created a way of specifying precision to make the code more portable across systems. The generic term used is kind. Real variables can be 4-byte (real*4), 8-byte (real*8, usually referred to as double precision), or 16-byte (real*16). In this example, I assign various kinds of precision to variables.

real*8 :: x8  ! kind=8 or doule precision
real*4 :: x4  ! kind=4 or single precision
real(kind=4) :: y4   !
integer*4 :: i4 ! integer single precision (kind=4)
integer(kind=8) :: i8 ! Integer double precision

By using kind or the equivalent *<n> notation, you make your code much more portable across systems.

At a high level, in addition to the features mentioned, F90 also introduced in-line comments, operator overloading, unlabeled do loops (constructs such as do, if, case, where, etc. may have names.), and the case statement.

In F90, the language developers also decided to deprecate, or outright delete, some outdated F77 features [5], including the arithmetic IF, non-integer DO parameters for control variables, the PAUSE statement, and others. You can easily discover what features have been deprecated or deleted by taking some F77 code, switching the extension to .f90, and trying to compile it with a F90 compiler.

Fortran 90 was only the start. The next two iterations – Fortran 95 and 2003 – pulled Fortran into a new era of programming languages.

Buy this article as PDF

Express-Checkout as PDF
Price $2.95
(incl. VAT)

Buy ADMIN Magazine

SINGLE ISSUES
 
SUBSCRIPTIONS
 
TABLET & SMARTPHONE APPS
Get it on Google Play

US / Canada

Get it on Google Play

UK / Australia

Related content

comments powered by Disqus