1 Introduction 2
1.1
Why use Fortran? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
2
1.2
Historical background . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3
2 The Fortran syntax 4
2.1
The structure of Fortran . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.2
Datatypes in Fortran . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.2.1
INTEGER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
2.2.2
REAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.2.3
LOGICAL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.2.4
CHARACTER . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.2.5
Derived datatypes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.3
Declaration of variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
5
2.3.1
Declaration of INTEGERS . . . . . . . . . . . . . . . . . . . . . . . . . .
6
2.3.2
Declaration of REAL numbers . . . . . . . . . . . . . . . . . . . . . . . .
6
2.3.3
Declaration of LOGICAL variables . . . . . . . . . . . . . . . . . . . . . .
7
2.3.4
Declaration of characters . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
2.3.5
Declaration of derived datatypes . . . . . . . . . . . . . . . . . . . . . . .
7
2.4
Instructions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
8
2.4.1
Instructions for giving a variable a value . . . . . . . . . . . . . . . . . . .
8
2.4.2
Instructions for program control . . . . . . . . . . . . . . . . . . . . . . .
8
3 A very simple Fortran 95 program 9
3.1
Programming our first problem . . . . . . . . . . . . . . . . . . . . . . . . . . . .
9
3.2
What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
3.3
A small exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
10
4 A more userfriendly Fortran 95 program 11
4.1
Extending our program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
4.2
What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
11
4.3
A small exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
12
5 The use of arrays 13
5.1
A one dimensional array example . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
5.2
What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
5.3
A small exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
6 The use of 2D arrays 15
6.1
A two dimensional array example . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
6.2
What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
6.3
A small exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
7 Getting data from a text file 18
7.1
How to read data from a file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
18
7.2
What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
7.3
A small exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
20
1
8 Programming formulas 21
8.1
What is a function in Fortran? . . . . . . . . . . . . . . . . . . . . . . . . . . . .
21
8.2
What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
8.3
A small exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
23
9 Programming formulas using functions 24
9.1
Programming the standard deviation function . . . . . . . . . . . . . . . . . . . .
24
9.2
What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
9.3
A small exercise . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
26
10 Programming subroutines 27
10.1 What is a subroutine in Fortran? . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
10.2 What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
11 Programming formulas using subroutines 30
11.1 Programming a subroutine calling another subroutine . . . . . . . . . . . . . . . .
30
11.2 What have we learned here?? . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
12 Introduction to modules 32
12.1 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
13 Introduction to Object Oriented Programming 36
13.1 Exercises . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
39
A Operators 40
A.1 Overview of the operators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
40
B Intrinsic functions in Fortran 95 41
B.1 Intrinsic Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
The purpose of this compendium is to give a good insight in the Fortran 2003 programming language by going through a number of examples showing how computational problems can be solved using Fortran 2003.
In the last 15 to 20 years Fortran has been looked upon as an old-fashioned unstructured programming language by researchers and students in the field of Informatics. Fortran has lacked most of the features found in modern programming languages like C++, Java etc. Especially the lack of object orientation has been the main drawback of Fortran. This is no longer true because Fortran 2003 has all the modern features including use of objects, operator overloading and genuine OOP functionality like in C++ and Java.
But why not forget Fortran and concentrate on using available OOP languages? The answer is very simple, speed . In the field of natural sciences, computer simulations of natural phenomena are becoming increasingly more important. Laboratory experiments are getting too complex and too costly to be performed. The only alternative is to use a computer simulation to try to solve the problem under study. Thus the need to have your code execute faster becomes more and more important when the simulations grows larger and larger. In number-crunching
2
Fortran still has an edge in speed over C and C++. Tests has shown that an optimised Fortran program in some cases runs up to 30 percent faster than the equivalent C or C++ program, but the difference is shrinking. For programs with a runtime of weeks even a small increase in speed will reduce the overall time it takes to solve a problem.
Seen in a historical perspective Fortran is an old programming language. 1954 John Backus and his team at IBM begin developing the scientific programming language Fortran. It was first introduced in 1957 for a limited set of computer architectures. In a short time the language spread to other architectures and has since been the most widely used programming language for solving numerical problems.
The name Fortran is derived from For mula Tran slation and it is still the language of choice for fast numerical computations. A couple of years later in 1959 a new version, Fortran II was introduced. This version was more advanced and among the new features was the ability to use complex numbers and splitting a program into various subroutines. In the following years Fortran was further developed to become a programming language that was fairly easy to understand and well adapted to solve numerical problems.
In 1962 a new version called Fortran IV emerged. This version had among it’s features the ability to read and write direct access files and also had a new data-type called LOGICAL.
This was a Boolean data-type with two states true or false. At the end of the seventies Fortran
77 was introduced. This version contained better loop and test structures. In 1992 Fortran
90 was formally introduced as an ANSI/ISO standard. This version of Fortran has made the language into a modern programming language. Fortran 2003 is a small extension of Fortran 90.
These latest versions of Fortran has many of the features we expect from a modern programming languages. The Fortran 2003 version which incorporates object-oriented programming support with type extension and inheritance, polymorphism, dynamic type allocation and type-bound procedures. We shall come back to what these terms mean.
3
As in other programming languages Fortran 2003 has it’s own syntax. We shall now take a look at the Fortran 2003 syntax and also that of the Fortran 77 syntax.
To start programming in Fortran a knowledge of the syntax of the language is necessary. Therefore let us start immediately to see how the Fortran syntax look like.
Fortran has, as other programming languages, a division of the code into variable declarations and instructions for manipulating the contents of the variables.
An important difference between Fortran 77 and Fortran 2003 is the way the code is written.
In Fortran 77 the code is written in fixed format where each line of code is divided into 80 columns and each column has its own meaning. This division of the code lines into columns has an historically background. In the 1960s and 1970s the standard media for data input was the punched cards. They were divided into 80 columns and it was therefore naturally to set the length of each line of code to 80 characters. In table 1 an overview of the subdivision of the line of code is given.
Column number
1
2 - 5
6
7 - 72
73 - 80
Meaning
A character here means the line is a comment
Jump address and format number
A character here is a continuation from previous line
Program code
Comment
Table 1: F77 fixed format
Fortran 77 is a subset of Fortran 2003 and all programs written in Fortran 77 can be compiled using a Fortran 2003 compiler. In addition to the fixed code format from Fortran 77, Fortran
2003 also supports free format coding. This means that the division into columns are no longer necessary and the program code can be written in a more structured way which makes it more readable and easier to maintain. Today the free format is the default settings for the F95 compiler.
Traditionally Fortran has had four basic datatypes. These were INTEGER and REAL numbers,
LOGICAL which is a boolean type and CHARACTER which represent the alphabet and other special non numeric types. Later the REAL data type was split into the REAL and COMPLEX data type. In addition to this a derived datatype can be used in Fortran 2003. A derived datatype can contain one or more of the basic datatypes and other derived datatypes.
2.2.1
INTEGER
An INTEGER datatype is identified with the reserved word INTEGER. It has a valid range which varies with the way it is declared and the architecture of the computer it is compiled on.
When nothing else is given an INTEGER has a length of 32 bits on a typical workstation and can have a value from [
−
2 31 ] to [2 30 ] . The 64 bit INTEGER has a minimum value from [
−
2 63 ]
4
to a maximum value of [2 62 ] . To declare a 64 bit INTEGER we use the construct INTEGER
(KIND=8) which means each variable uses 8 bytes each consisting of 8 bits.
2.2.2
REAL
In the same manner a REAL number can be specified with various ranges and accuracies. A
REAL number is identified with the reserved word REAL and can be declared with single or double precision. In table 2 the number of bits and minimum and maximum values are given.
Precision Sign Exponent Significand Max. value Min. value
Single
Double
1
1
8
11
23
52
2 128
2 1024
2
−
2
−
126
1022
Table 2: REAL numbers
A double precision real number are declared using the reserved words REAL (KIND=8) just like the 64 bit INTEGER.
An extension of REAL numbers are COMPLEX numbers with their real and imaginary parts. A COMPLEX number are identified with the reserved word COMPLEX. The real part can be extracted by the function REAL() and the imaginary part with the function AIMAG() or just IMAG() depending on the compiler implementation. There is no need for writing explicit calculation functions for COMPLEX numbers like one has to do in C / C++ which lacks the
COMPLEX data type.
2.2.3
LOGICAL
The LOGICAL datatype is identified by the reserved word LOGICAL and has only two values true or false. These values are identified with .TRUE. or .FALSE. and it is important to notice that the point at the beginning and end is a necessary part of the syntax. To omit one or more points will give a compilation error.
2.2.4
CHARACTER
The CHARACTER datatype is identified by the reserved word CHARACTER and contains letters and characters in order to represent data in a readable form. Legal characters are a to z ,
A to Z and some special characters like + , , * , / , = etc.
2.2.5
Derived datatypes
These are datatypes which are defined for special purposes. A derived datatype are put together of components from one or more of the four basic datatypes and also of other derived datatypes.
A derived datatype is always identified by the reserved word TYPE name as prefix and END
TYPE name as postfix.
In Fortran there are two ways to declare a variable. The first is called implicit declaration and is inherited from the earliest versions of Fortran. Implicit declaration means that a variable is declared when needed by giving it a value anywhere in the source code. The datatype is determined by the first letter in the variable name. An INTEGER is recognised by starting with the letters I to N and a REAL variable by the rest of the alphabet. It is important to notice
5
that no special characters are allowed in a variable name only the letters A - Z , the numbers 0
- 9 and the underscore character _ . A variable cannot start with a number.
The other way of declaring a variable is by explicit declaration. This is in accordance with other programming languages where all variables has to be declared within a block of code before any instructions occurs.
As a general rule an implicit declaration is not a good way to program. It gives a code that is not very readable and also it is easily introduce errors in a program due to typing errors.
Therefore always use explicit declaration of variables. Some variables must always be declared.
These are arrays in one or more dimensions and character strings.
2.3.1
Declaration of INTEGERS
First an example of how to declare an INTEGER in Fortran 2003. Note that the KIND parameter is architecture dependent.
INTEGER
INTEGER ( KIND =2)
:: i ! Declaration of an INTEGER
! length (32 bit)
:: j ! Declaration of an INTEGER (16 bit)
INTEGER ( KIND =4)
INTEGER ( KIND =8)
:: k ! Declaration of an INTEGER (32 bit)
:: m ! Declaration of an INTEGER (64 bit)
INTEGER , DIMENSION (100) :: n ! Declaration of an INTEGER array
! (100 elements)
As seen in the preceding examples there are certain differences in the Fortran 77 and the Fortran
2003 way of declaring variables. It is less to write when the variables are declared in the Fortran
77 style but this is offset by greater readability in the Fortran 2003 style. One thing to note is that in Fortran 2003 a comment can start anywhere on the code line and is always preceded by an exclamation point.
2.3.2
Declaration of REAL numbers
The REAL datatype is now in most compilers confirming to the IEEE standard for floating point numbers. Declarations of single and double precision is declared like in the next example.
REAL
REAL
REAL
(
,
KIND =8)
DIMENSION (200)
:: x ! Declaration of REAL
! default length (32 bit)
:: y ! Declaration of REAL
! double precision (64 bit)
:: z ! Declaration of REAL array
! (200 elements)
Fortran has, unlike C/C++, an intrinsic datatype of complex numbers. Declaration of complex variables in Fortran are shown here.
COMPLEX :: a ! Complex number
COMPLEX , DIMENSION (100) :: b ! Array of complex numbers
! (100 elements)
6
2.3.3
Declaration of LOGICAL variables
Unlike INTEGERS and REAL numbers a LOGICAL variable has only two values, .TRUE.
or .FALSE. and therefore only uses a minimum of space. The number of bits a LOGICAL variable are using depends on the architecture and the compiler. It is possible to declare a single LOGICAL variable or an array of them. The following examples shows a Fortran 77 and a Fortran 2003 declaration. In other programming languages the LOGICAL variable is often called a BOOLEAN variable after Boole the mathematician.
LOGICAL :: l1 ! Single LOGICAL variable
LOGICAL , DIMENSION (100) :: l2 ! Array of LOGICAL variables
! (100 elements)
2.3.4
Declaration of characters
Characters can either be declared as a single CHARACTER variable, a string of characters or an array of single characters or character strings.
CHARACTER
CHARACTER
CHARACTER ,
( LEN =80)
DIMENSION (10)
:: c1 ! Single character
:: c2 ! String of characters
:: c3 ! Array of single
! characters
CHARACTER ( LEN =80), DIMENSION (10) :: c4 ! Array of character
! strings (10 elements)
2.3.5
Declaration of derived datatypes
TYPE derived
! Internal variables
INTEGER
REAL
LOGICAL
:: counter
:: num
:: used
CHARACTER ( LEN =10) :: string
END TYPE derived
! A declaration of a variable of
! the new derived datatype
TYPE (derived) :: my_type
One question arises: why use derived datatypes? One answer to that is that sometimes it is desirable to group variables together and to refer to these variables under a common name. It is usually a good practice to select a name of the abstract datatype to indicate the contents and area of use.
7
There are two main types of instructions. One is for program control and the other is for giving a variable a value.
2.4.1
Instructions for giving a variable a value
To give a numeric variable (i.e. INTEGER, REAL etc.) we use the equal sign
INTEGER
REAL i = 0 r = 0.0
:: i
:: r where we initialises the variables to zero. Note that we have to use 0 .
0 or just 0 .
to initialise the
REAL number.
2.4.2
Instructions for program control
Instructions for program control can be split into three groups, one for loops, one for tests (even though a loop usually have an implicit test) and the last for assigning values to variables and perform mathematical operations on the variables. In Fortran all loops starts with the reserved word DO . A short example on a simple loop is given in the following piece of code.
DO i = 1, 100
!// Here instructions are performed 100 times
!// before the loop is finished
END DO
The next example shows a non terminating loop where a test inside the loop is used to exit the loop when the result of the test is true.
DO a = a * SQRT(b) + c
IF (a > z) THEN
!// Jump out of the loop
EXIT
END IF
END DO
This small piece of code gives the variable a a value from the calculation of the square root of the variable b and multiplied with the last value of a and the addition of variable c . When the value of a is greater then the value of variable z the program transfer control to the next instruction after the loop. We assumes here that all the variables has been initialised somewhere in the program before the loop. The various Fortran instructions will be described in the following chapters through the examples on how problems can be solved by simple Fortran programs.
8
The best way of learning a new programming language is to start using it. What we shall do now is to take a small problem and program a solution to that problem.
We want to make a small program solving the conversion of a temperature measured in Farenheit to Centigrade.
The formula is C = ( F
−
32)
·
5
9
.
All Fortran programs starts with the PROGRAM programname sentence and ends with the END
PROGRAM programname . In contrast to other programming languages is Fortran case insensitive.
It means that a variable name written in uppercase letters is the same variable as one written in lowercase letters. So let us write some code namely the first and last line in a program.
PROGRAM farenheit_to_centigrades
END PROGRAM farenheit_to_centigrades
Next is to declare the variables and perform the conversion.
PROGRAM farenheit_to_centigrades
IMPLICIT NONE
REAL
REAL
:: F ! Farenheit
:: C ! Centigrade
F = 75
C = (F - 32)*5/9
PRINT *, C
END PROGRAM farenheit_to_centigrades
So what have we been doing here??
After the first line we wrote IMPLICIT NONE . This means that we have to declare all the variables we use in the program. A heritage from the older versions of Fortran is the use of implicit declaration of variables. As a default all variables starting with the letter i
− n is automatically an integer and the rest of the alphabet is a real variable. The use of implicit declarations is a source of errors and should not be used. Next we declare the variable to hold the temperature in Farenheit ( F ) and also the same for the temperature in Centigrade ( C ). Note the construct !
Farenheit which tells the compiler that the rest of the line is a comment. Before we can start the computation we have to give the F variable a value, in this case 75 degrees Farenheit. Now that we have the temperature we simply calculate the corresponding temperature in degrees
Centigrade. To se the result of the calculation we simply writes the result to the computer screen using the PRINT * and the name of the variable containing the value we want so display.
Note that the PRINT * and the variable name C is separated with a comma.
All we have to do now is to compile the program and run it to see if it works. The process of compiling a program will be dealt with later, but the simplest form for compiling can be done like this: gfortran -o farenheit_to_centigrades farenheit_to_centigrades.f90
The name of the compiler is here gfortran where the -o tells the compiler to create the executable program with the name occurring after the -o and last the name of the file containing the source code. Note that all Fortran 95 source files has the ending .f90
. To run the program we simply type:
9
./farenheit_to_centigrades and then the result will be printed to the screen which in this case is 23 .
88889 . Note the use of ./ before the program name. This tells the command interpreter to use the program in the current directory.
We have learned
• Never to use implicit declarations
• How to declare real variables
• How to assign a value to a variable
• How to perform a simple calculation
• How to print a value to the screen
Rewrite the temperature conversion program to make the conversion the other way from Centigrade to Farenheit (Note: the formula for the conversion is F = C
·
5
9
+ 32 ).
10
In the previous section we made a program converting a temperture from Farenheit to Centigrades. It was not a very userfriendly program since we had to change the value of the Farenheit variable and recompile the program for each new temperature we would convert. We shall now try to make the program asking the user for a temperature before we perform the calculation.
Let us take the source code from our first program and simply add some new lines of code to ask the user for a temperature and then read the temperature from the keyboard.
PROGRAM farenheit_to_centigrades2
IMPLICIT NONE
! The temperature variables
REAL :: F ! Farenheit
REAL :: C ! Centigrade
! A text string
CHARACTER ( LEN =32) :: prompt
! The text that will be displayed prompt = ’Enter a temperature in Farenheit:’
! Display the prompt
PRINT *, prompt
! Get the input from the keyboard
! and put it into the F variable
READ (*,*) F
! Perform the calculation
C = (F - 32)*5/9
! Display the result
PRINT *, C
END PROGRAM farenheit_to_centigrades2
The text string prompt is used to contain a message to the user telling the user to enter a temperature in degrees Farenheit. To get the temperature entered by the user we simply use the READ(*,*) function to capture the input from the keyboard. The syntax (*,*) tells the program to retrieve the temperature form the keyboard. The first * identifies the input source as the keyboard and the second * tells the system to use default formatting of the input. The rest of the program is just like the first version where we hardcoded the temperature.
Again w compile the program like this: gfortran -o farenheit_to_centigrades2 farenheit_to_centigrades2.f90
To run the program we type:
./farenheit_to_centigrades2 and then the result will be printed to the screen with the Centigrade temperature corresponding to the Farenheit temperature.
We have learned
• To declare a character string
11
• To give the character string a value
• Displaying the contents of the character string
• Reading a number form the keyboard
Extend the program with code to ask if the temperature is in Centigrades or Farenheit and perform the conversion either way. In addition print a line of test telling if it is a conversion from Farenheit to Centigrade or from Centigrade to Farenheit.
Hint: The PRINT *, statement can take several variables as arguments to be displayed on the screen. The arguments are separated with a comma like this PRINT *, variable1, variable2, variable3 .
12
In this compendium we use the name array for both a vector and a matrix. A one dimensional array is another name for a vector and an array with two or more dimensions is the same as a matrix with two or more dimensions. A one dimensional array can be seen as a set of containers where each container is an element in the array like in this figure
68.2 69.6 73.3 75.0 77.6 79.5 81.2
Here we have an array with seven elements each containing a value.
Let us take the example array above and use the values as temperatures in degrees Farenheit.
We shall now program the array, give each element the value from the example and convert the values into degrees Centigrade stored in a separate array.
PROGRAM array1D
IMPLICIT NONE
! The temperature arrays
REAL , DIMENSION (7) :: farenheit
REAL , DIMENSION (7) :: centigrade
! Some help variables
! An index variable
INTEGER :: i
! Insert the temperature values into the
! farenheit array farenheit(1) = 68.2
farenheit(2) = 69.6
farenheit(3) = 73.3
farenheit(4) = 75.0
farenheit(5) = 77.6
farenheit(6) = 79.2
farenheit(7) = 81.2
! Performing the conversion using a loop
DO i = 1, 7 centigrade(i) = (farenheit(i) - 32)*5/9
END DO
! Print the value pairs for each element in the
! two arrays
DO i = 1, 7
PRINT *, ’Farenheit: ’ , farenheit(i), &
’ Centigrade: ’ , centigrade(i)
END DO
END PROGRAM array1D
The declaration of the temperature arrays is straight forward using the DIMENSION(7) parameter in addition to the datatype which is REAL in this case. To give the Farenheit temperature to each array element we have to use the element number to put the value in the right place. To convert the values we use a loop with an index variable taking the number from one to seven.
13
The loop is declared using the DO END DO construct. Note that for each time we go through the loop the index variable is incremented by one. When the index variable get a value larger than the given end value (seven in this case) the loop is terminated and the program continues from the line after the END DO statement.
Then again we compile the program gfortran -o array1D array1D.f90
and run it like this
./array1D
We have learned
• To declare a vector of real numbers
• To initialise the vector
• Perform calculations on each element of the array
• Printing the result of our calculations to the screen
Try to find out how we can print out every second element from the array starting with element number two.
14
Using a one dimensional array is quite simple. Using a two dimensional array is somewhat more complex, but we will take a look at how we do this here.
Again we shall use a temperature array like in the previous example, but now we have temperature measurements from two different places. That means we will have to use a two dimensional array to store the temperatures from the two places. The array will look like this:
68.2
65.4
69.6
63.7
73.3
66.1
75.0
68.0
77.6
70.1
79.2
71.4
81.2
73.2
So let us go on programming it. First we will have to declare the arrays for both Farenheit and
Centigrade. Note that in this declaration we define the number of rows before the number of columns. In this case the number of rows is 7 and the number of columns is 2.
PROGRAM array2D
IMPLICIT NONE
! The temperature arrays
REAL , DIMENSION (7,2) :: farenheit
REAL , DIMENSION (7,2) :: centigrade
! Some help variables
! Two index variables
INTEGER
INTEGER
:: i
:: j
! Insert the temperature values into the
! farenheit array farenheit(1,1) = 68.2
farenheit(2,1) = 69.6
farenheit(3,1) = 73.3
farenheit(4,1) = 75.0
farenheit(5,1) = 77.6
farenheit(6,1) = 79.2
farenheit(7,1) = 81.2
farenheit(1,2) = 65.4
farenheit(2,2) = 63.7
farenheit(3,2) = 66.1
15
farenheit(4,2) = 68.0
farenheit(5,2) = 70.1
farenheit(6,2) = 71.4
farenheit(7,2) = 73.2
! Performing the conversion using a loop
DO j = 1, 2
DO i = 1, 7 centigrade(i,j) = (farenheit(i,j) - 32)*5/9
END DO
END DO
! Print the value pairs for each element in the
! two arrays
DO j = 1,2
DO i = 1, 7
PRINT *, ’Farenheit: ’ , farenheit(i,j), &
’ Centigrade: ’ , centigrade(i,j)
END DO
END DO
END PROGRAM array2D
Some explanation is needed here. First of all the structure of this array consists of two columns with seven rows in each column. Fortran always access the element column-wise. That is each row element of a column is accessed before we proceed to the next column. It is important to notice the use of the index variables. We have the first index in the innermost loop thus traversing the array as indicated in the ??
. Note also in the two lines containing the PRINT statement we use an ampersand & at the end of the first line in the statement. This is to tell the compiler that this line continues on the next line. In Fortran we do not have any begin end structure because Fortran is a line oriented language due to the use of punched cards as an input media.
✞ ☎
❄ ❄
Why is it so important to traverse the array in this manner? The answer has to do with how the computer is constructed. In the figure below we can see that the CPU (Central Processing Unit) get the data and instructions via a Cache memory which is almost as fast as the CPU . The Cache
16
memory get the data and instructions from the Main memory which is rather slow compared to the Cache memory and the CPU . To utilize the CPU as effective as possible the Cache memory has to be filled with as much relevant data as possible. If we traverse an array in the wrong direction we have to go to the slow Main memory to fetch the data we need for each iteration in the innermost loop. For very large arrays this is costly in CPU-cycles and will slow down the execution speed of the program.
CPU
✛
✲
Cache memory
✲
✛
Main memory
So now we see how important it is to traverse an array in the right manner to keep the CPU operating at full speed.
Then again we compile the program gfortran -o array2D array2D.f90
and run it like this
./array2D
We have learned
• To declare a two dimensional array of real numbers
• To initialise the array
• Perform calculations on each element of the array in a nested loop
• Printing the result of our calculations to the screen
Try to find out how we can print out only the elements from the first column in the array and program the solution.
17
In the previous sections we have hardcoded the temperature in the program source. This is of course not what we normally do. The input data is usually stored in a file which can be an
ASCII-file which is a readable text file or in a binary file. We will use the ASCII-file type in our problem solving here.
The dataset containing the temperatures is stored in an ASCII-file with two real numbers separated by a space like this:
68.2 65.4
69.6 63.7
73.3 66.1
75.0 68.0
77.6 70.1
79.2 71.4
81.2 73.2
What we have to do here is to read the contents of each line into the corresponding array elements. To be able to read the contents of a file into an array we have to open the file just like we would open the drawer in a file cabinet to get access to the content of the drawer. We use the OPEN function with a set of arguments to do this. After the opening of the file we should always test to see if the opening was successful. If not we just terminate the program. To read the contents of the file w use the READ function with a set of arguments. Since we are reading the file one line at a time we have to use a loop to put the values into the correct position in the array. We again should test to see if the read operation was successful and close the file and terminate the program if an error occurred. Finally we close the file using the CLOSE function with one argument. The program code below illustrates how we can do this.
PROGRAM readtemp
IMPLICIT NONE
! The temperature arrays
REAL , DIMENSION (7,2) :: farenheit
REAL , DIMENSION (7,2) :: centigrade
! The filename
CHARACTER ( LEN =16) :: filename
! A unit number to reference the file
INTEGER , PARAMETER :: lun = 10
! Some help variables
! Two index variables
INTEGER :: i
INTEGER :: j
! A result variable
INTEGER :: res
! Set the filename into the variable filename = ’temperature.txt’
! Open the file for reading
OPEN ( UNIT =lun, FILE =filename, FORM = ’FORMATTED’ , &
IOSTAT =res)
18
! Test if the file was opened correctly
IF (res /= 0) THEN
! Print an error message
PRINT *, ’Error in opening file, status: ’ , res
! Stop the program
STOP
END IF
! Read the data into the farenheit array
DO i = 1, 7
READ ( UNIT =lun, FMT = ’(F4.1,X,F4.1)’ , IOSTAT =res) & farenheit(i,1), farenheit(i,2)
IF (res /= 0) THEN
PRINT *, ’Error in reading file, status: ’ , res
CLOSE ( UNIT =lun)
STOP
END IF
END
! Perform the conversion
DO j = 1, 2
DO i = 1, 7 centigrade(i,j) = (farenheit(i,j) - 32)*5/9
END DO
END DO
! Print the value pairs for each element in the
! two arrays
DO j = 1,2
DO i = 1, 7
PRINT *, ’Farenheit: ’ , farenheit(i,j), &
’ Centigrade: ’ , centigrade(i,j)
END DO
END DO
END PROGRAM readtemp
Some explanations might be useful. We already know how to declare variables, but in this program we also declare a constant value referred to by a name using this syntax INTEGER,
PARAMETER :: lun = 10 . Here the constant value is the number 10. Why, you ask, do we do it this way and not just write the number 10 instead of using the lun variable. The answer is to prevent errors in case we need to change the value. Using a constant we only have to change the value in one place and not everywhere in the source code as we had to do if we used the number in several places in the code. To open a file for reading we use the OPEN function with four keyword-argument pairs. The first argument is the unit number we use as a reference when we access the file later in the program preceded by the keyword UNIT= . The next is the filename again preceded by a keyword-argument pair which is here FILE=filename . The third argument is the type of file wer are opening. Here it is an ASCII file (a human readable text file) which the keyword-argument pair is FORM=’FORMATTED’ . The last keyword-argument pair is IOSTAT=res where the argument is the name of an integer variable which will contain the result of the opening of the file. Any number except zero marks a failure to open the file. The if test IF(res /= 0) returns true it means that the res variable contains an error number telling what went wrong in opening the file. The problem could for example be that the filename is misspelled or we are in a wrong directory or some other error situation. Then we proceed to
19
read the contents of the file into the farenheit array. We use the function READ with a number of keyword-argument pairs. The first is the same as for the OPEN function. The next describes how the format is for the values in the input file. Here the format of the file is FMT=’(F4.1,X,F4.1)’ which means that we have two real numbers on each line in the file. The numbers have a total of 4 positions each including the decimal point and with one decimal which is denoted by F4.1
.
Each number is separated by a character which is just one space character and is referred to by the letter X which tells the system to ignore this character in the reading process. Note that we put the first number in the first column of the farenheit array and the second number in the second column. The index variable points to the corresponding row in the array while we here are hardcoding the second index. Note also the use of an ampersand & at the end of the line containing the PRINT statement and the one at the beginning of the next line. This is the syntax allowing a text constant to span over more than one line.
We have learned
• We have declared a constant using the PARAMETER keyword
• The process of opening a file for reading, specifying the filetype and test if the opening process was a success
• In order to read the contents of the file we have passed the format specifications to the
READ function and storing the values into the farenheit array. Then testing for errors in the reading process
• To have a text constant span more than one line using two ampersands one at the end of the first line and the second at the beginning of the next line.
Change the program to just print out the first column of the centigrade array
20
Now that we have learned to read a set of data into an array we will start to program some functions which will operate on the dataset.
A function in Fortran 95 is just like a mathematical function. It receives one or more input arguments and returns a result. One such function we shall program is the mean function which will return the mean value of the argument which will have to be a 1D array. The mathematical formula for the mean value is n
¯ =
1 n
·
X x i
(1) i =1
The skeleton of our mean value function for a dataset can be like this:
FUNCTION mean(arg) RESULT (res)
IMPLICIT NONE
! Input array
REAL , DIMENSION (7) :: arg
! The result of the calculations
REAL :: res
! Index variable
INTEGER :: i
! Temporary variable
REAL :: tmp
! Initialize the temporary variable tmp = 0.
DO i = 1, 7 tmp = tmp + arg(i)
END DO res = tmp/7.
RETURN
END FUNCTION mean
All functions declared in Fortran begins with the keyword FUNCTION with the name of the function and the input argument enclosed in parenthesis. The type of value returned by the function is defined by the datatype in the RESULT(res) statement. Note that we have hardcoded the length of the input argument. This is of course not desirable, but as an example on how we program a function it will be ok for now. Note the initialising of the tmp variable where we use the construct tmp = 0.
where the decimal point after the number is necessary to tell the compiler that we have a real number and not an integer. The same notion goes for the division at the end of the function. The RETURN stement returns the program to the calling process. In this case it will be our main program. Let us change our program to incorporate the calculation of the mean function.
PROGRAM readtemp
IMPLICIT NONE
! The temperature arrays
REAL , DIMENSION (7,2) :: farenheit
REAL , DIMENSION (7,2) :: centigrade
! The filename
21
CHARACTER ( LEN =16) :: filename
! A unit number to reference the file
INTEGER , PARAMETER :: lun = 10
! External declaration of the mean function
REAL , EXTERNAL :: mean
! The variable to hold the mean value
REAL ::mvalue
! Some help variables
! Two index variables
INTEGER
INTEGER
:: i
:: j
! A result variable
INTEGER :: res
! Set the filename into the variable filename = ’temperature.txt’
! Open the file for reading
OPEN ( UNIT =lun, FILE =filename, FORM = ’FORMATTED’ , &
IOSTAT =res)
! Test if the file was opened correctly
IF (res /= 0) THEN
! Print an error message
PRINT *, ’Error in opening file, status: ’ , res
! Stop the program
STOP
END IF
! Read the data into the farenheit array
DO i = 1, 7
READ ( UNIT =lun, FMT = ’(F4.1,X,F4.1)’ , IOSTAT =res) & farenheit(i,1), farenheit(i,2)
IF (res /= 0) THEN
PRINT *, ’Error in reading file, status: ’ , res
CLOSE ( UNIT =lun)
STOP
END IF
END
! Perform the conversion
DO j = 1, 2
DO i = 1, 7 centigrade(i,j) = (farenheit(i,j) - 32)*5/9
END DO
END DO
! Print the value pairs for each element in the
! two arrays
DO j = 1,2
DO i = 1, 7
PRINT *, ’Farenheit: ’ , farenheit(i,j), &
’ Centigrade: ’ , centigrade(i,j)
END DO
END DO
22
! Calculate the mean value of the first column
! of the centigrade array mvalue = mean(centigrade(:,1))
! Print the mena value
PRINT *, ’The mean value of the first column in centigrade’ ,& mvalue
END PROGRAM readtemp
All functions that is not a Fortran intrinsic function has to be declared as an external function.
This is done using the EXTERNAL parameter in addition to the type of data returned by the function. In addition to declare a variable to contain the result of the calling of our mean function we just add one line where we set the mvalue equal to the function mean and just use the centigrade(:,1) to send the values from the first column to the mean function.
We have learned
• To create a function with one input argument and a result
• That all non Fortran intrinsic functions has to be declared external in the procedure calling the function
• To call a function in our main program
Extend the program to print out the mean from both series and calculate the difference of the two mean values and print the results to the screen
23
Having a working function for the mean we shall now start to write other functions which will use functions we already have programmed
Th function we shall write is the standard deviation function. The mathematical formula for the standard deviation is
σ = v u u t
1
N
N
X i =1
( x i −
µ ) 2 .
(2)
Note that the standard deviation function also uses the mean value of the dataset in the calculation. So we will then call our mean function from within the standard deviation function which we will call std . The skeleton of our standard deviation function can be like this:
FUNCTION std(arg) RESULT (res)
IMPLICIT NONE
! Input array
REAL , DIMENSION (7) :: arg
! The result of the calculations
REAL :: res
! The mean function
REAL , EXTERNAL
! Index variable
:: mean
INTEGER :: i
! Temporary variables
REAL :: tmp
REAL :: tmpmean
! Calculate the mean value tmpmean = mean(arg)
! Initialise the tmp variable tmp = 0.
! Calculate the standard deviation
DO i = 1, 7 tmp = tmp + (arg(i) - tmpmean)**2
END DO
! Divide the result by the number of elements tmp = tmp / 7.
! Take the square root of the tmp res = SQRT(tmp)
! Return to calling process
RETURN
END FUNCTION std
We introduce a couple of new things in the std function. First we declare the mean function as an external function of type REAL . A new variable is used to hold the mean value of the dataset ( tmpmean ). Calling the mean returns the mean value of the input argument. Then we loop through all the elements in the input argument and sum up the square of the element value minus the mean value. Note the use of the ** to denote the square of the value. We divide the sum by the number of elements and finally we take the square root of the value and place it in
24
the res variable using the Fortran intrinsic function SQRT . Note that we do not have to declare the SQRT function as an external function since it is one of a set of intrinsic functions in Fortran.
Let us change our program to incorporate the calculation of the std function.
PROGRAM readtemp
IMPLICIT NONE
! The temperature arrays
REAL , DIMENSION (7,2) :: farenheit
REAL , DIMENSION (7,2) :: centigrade
! The filename
CHARACTER ( LEN =16) :: filename
! A unit number to reference the file
INTEGER , PARAMETER :: lun = 10
! External declaration of the mean function
REAL , EXTERNAL :: std
! The variable to hold the mean value
REAL ::mvalue
REAL ::stdvalue
! Some help variables
! Two index variables
INTEGER
INTEGER
:: i
:: j
! A result variable
INTEGER :: res
! Set the filename into the variable filename = ’temperature.txt’
! Open the file for reading
OPEN ( UNIT =lun, FILE =filename, FORM = ’FORMATTED’ , &
IOSTAT =res)
! Test if the file was opened correctly
IF (res /= 0) THEN
! Print an error message
PRINT *, ’Error in opening file, status: ’ , res
! Stop the program
STOP
END IF
! Read the data into the farenheit array
DO i = 1, 7
READ ( UNIT =lun, FMT = ’(F4.1,X,F4.1)’ , IOSTAT =res) & farenheit(i,1), farenheit(i,2)
IF (res /= 0) THEN
PRINT *, ’Error in reading file, status: ’ , res
CLOSE ( UNIT =lun)
STOP
END IF
END
! Perform the conversion
DO j = 1, 2
DO i = 1, 7 centigrade(i,j) = (farenheit(i,j) - 32)*5/9
25
END DO
END DO
! Print the value pairs for each element in the
! two arrays
DO j = 1,2
DO i = 1, 7
PRINT *, ’Farenheit: ’ , farenheit(i,j), &
’ Centigrade: ’ , centigrade(i,j)
END DO
END DO
! Calculate the mean value of the first column
! of the centigrade array mvalue = mean(centigrade(:,1))
! Print the mean value
PRINT *, ’The mean value of the first column in centigrade’ ,& mvalue
! Calculate the standard deviation value stdvalue = std(centigrade(:,1))
! Print the std value
PRINT *, ’The standard deviation value of the first column &
& in centigrade’ , stdvalue
END PROGRAM readtemp
We have learned
• To make a function use another function which we have written
• To use the square syntax ** to calculate the square of a number
• To take the square root of a value using the SQRT Fortran intrinsic function
Extend the program to print out the standard deviation from both series and calculate the difference of the two values and print the results to the screen.
26
Now that we have learned to program functions we will take a look at subroutines which is in many ways like a function .
A subroutine in Fortran 95 is just like a function . It receives one or more input arguments, but returns no result. Instead any returning value is passed through one or more arguments.
We shall now take a look at a subroutine that calculates the mean value of the argument which will have to be a 1D array just like the mean function.
SUBROUTINE mean(arg,res)
IMPLICIT NONE
! Input array
REAL , DIMENSION (7), INTENT ( IN ) :: arg
! The result of the calculations
:: res REAL , INTENT ( OUT )
! Index variable
INTEGER :: i
! Temporary variable
REAL :: tmp
! Initialize the temporary variable tmp = 0.
DO i = 1, 7 tmp = tmp + arg(i)
END DO res = tmp/7.
RETURN
END SUBROUTINE mean
All subroutines declared in Fortran begins with the keyword SUBROUTINE with the name of the subroutine and the input and output arguments enclosed in parenthesis. The result is returned int th res argument. Note the use of the keywords INTENT(IN) and INTENT(OUT) which will flag an error if we try to write something in the variable with the parameter INTENT(IN) and likewise try to read something from the variable with the parameter INTENT(OUT) . The internal functionality is the same as for the mean function. We have only two changes in the main program to be able to use the subroutine. First we remove the line declaring the external mean function. Second we change the line calling the function with the construct CALL mean(centigrade(:,1),res) .
PROGRAM readtemp
IMPLICIT NONE
! The temperature arrays
REAL , DIMENSION (7,2) :: farenheit
REAL , DIMENSION (7,2) :: centigrade
! The filename
CHARACTER ( LEN =16) :: filename
! A unit number to reference the file
INTEGER , PARAMETER :: lun = 10
! The variable to hold the mean value
REAL ::mvalue
27
! Some help variables
! Two index variables
INTEGER
INTEGER
:: i
:: j
! A result variable
REAL :: res
! Set the filename into the variable filename = ’temperature.txt’
! Open the file for reading
OPEN ( UNIT =lun, FILE =filename, FORM = ’FORMATTED’ , &
IOSTAT =res)
! Test if the file was opened correctly
IF (res /= 0) THEN
! Print an error message
PRINT *, ’Error in opening file, status: ’ , res
! Stop the porgram
STOP
END IF
! Read the data into the farenheit array
DO i = 1, 7
READ ( UNIT =lun, FMT = ’(F4.1,X,F4.1)’ , IOSTAT =res) & farenheit(i,1), farenheit(i,2)
IF (res /= 0) THEN
PRINT *, ’Error in reading file, status: ’ , res
CLOSE ( UNIT =lun)
STOP
END IF
END
! Perform the conversion
DO j = 1, 2
DO i = 1, 7 centigrade(i,j) = (farenheit(i,j) - 32)*5/9
END DO
END DO
! Print the value pairs for each element in the
! two arrays
DO j = 1,2
DO i = 1, 7
PRINT *, ’Farenheit: ’ , farenheit(i,j), &
’ Centigrade: ’ , centigrade(i,j)
END DO
END DO
! Calculate the mean value of the first column
! of the centigrade array
CALL mean(centigrade(:,1),res)
! Print the mean value
PRINT *, ’The mean value of the first column in centigrade’ ,& mvalue
END PROGRAM readtemp
28
We have learned
• To create a subroutine with one input argument and an argument to hold the result of the calculations
• That we can add optional parameters to prevent overwriting values in an argument and to prevent unintentionally reading a value from an argument meant for writing
• To call a subroutine in our main program
29
Th subroutine we shall write has the same functionality as the standard deviation function. Note that the standard deviation subroutine will use the mean value subroutine from the previous section.
SUBROUTINE std(arg,res)
IMPLICIT NONE
! Input array
REAL , DIMENSION (7) :: arg
! The result of the calculations
REAL :: res
! Index variable
INTEGER :: i
! Temporary variables
REAL
REAL
:: tmp
:: tmpmean
! Calculate the mean value
CALL mean(arg,tmpmean)
! Initialise the tmp variable tmp = 0.
! Calculate the standard deviation
DO i = 1, 7 tmp = tmp + (arg(i) - tmpmean)**2
END DO
! Divide the result by the number of elements tmp = tmp / 7.
! Take the square root of the tmp res = SQRT(tmp)
! Return to calling process
RETURN
END SUBROUTINE std
Like in the previous section we just change a couple of lines both in the main program and in the std subroutine
PROGRAM readtemp
IMPLICIT NONE
....
! Calculate the standard deviation value
CALL std(centigrade(:,1),stdvalue)
! Print the std value
PRINT *, ’The standard deviation value of the first column &
& in centigrade’ , stdvalue
END PROGRAM readtemp
30
We have learned
• To make a subroutine use another subroutine which we have written
31
In the previous sections we have used programs, functions and subroutines to solve our computing problems. With less complex problems this is an ok approach, but when the problem increases in complexity we have to change the way we break down the problem and start to look into the data structures and procedures. To illustrate this we will take the program reading water flow values and the date as the number of days after 01.01.0000 and make a global data structure with procedures working on the global data structures.
To facilitate this we will introduce the Module which is a structure with variable declarations and procedure declarations. We start breaking down the problem by looking at the data structure. We know that the file contains one line with the number of lines with the value pairs (date and water flow) and a line with the description of each column in the file. We need then one variable to store the number ov value pairs and two single dimension arrays to store the date and water flow. We have to have these array allocatable since we do not know in advance the number of value pairs. In addition we need a variable to hold the filename, the ASCII -digits, the unit number, a status variable and a loop variable. The code snippet below shows a working module containing the necessary data structure to solve our problem.
MODULE flowdata
IMPLICIT NONE
INTEGER
INTEGER
INTEGER
, PARAMETER
CHARACTER ( LEN =80)
CHARACTER ( LEN =80)
:: lun = 10
:: res
:: flength
:: filename
:: cbuffer
INTEGER :: c_position, string_length
INTEGER , ALLOCATABLE , DIMENSION (:) :: dates
REAL , ALLOCATABLE , DIMENSION (:) :: water_flow
END MODULE flowdata
In addition to the global data structure we need the procedures to get the data into the variables.
The code below shows how we declare the subroutine to read the number of value pairs into the arrays dates and water_flow variable. the arrays.
MODULE flowdata
IMPLICIT NONE
....
CONTAINS
SUBROUTINE read_data()
IMPLICIT NONE
INTEGER :: i
OPEN ( UNIT =lun, FILE =filename, FORM = ’FORMATTED’ , IOSTAT =res)
READ ( UNIT =lun, FMT = ’(A)’ , IOSTAT =res) cbuffer string_length = LEN_TRIM(cbuffer) c_position = INDEX(cbuffer, ’:’ )
READ (cbuffer(c_position+1:string_length), FMT = ’(I15)’ ) flength
ALLOCATE (dates(flength), STAT =res)
ALLOCATE (water_flow(flength), STAT =res)
READ ( UNIT =lun, FMT = ’(A)’ , IOSTAT =res) cbuffer
DO i = 1, flength
READ ( UNIT =lun, FMT = ’(I6,X,F6.3)’ , IOSTAT =res) dates(i), water_flow(i)
32
END DO
CLOSE ( UNIT =lun)
END SUBROUTINE read_data
....
END MODULE flowdata
A little explanation is in place here. First we separate the declarations of the global variables from the procedure declarations with the keyword CONTAINS . Since the variables with the exception of the index variable in the do-loop are global we can use them directly in the subroutine. The subroutine is declared as usual, but is now residing inside the module. In the same manner we shall proceed with the rest of the necessary procedures in order to extract the data we need from the arrays. We will of course need a function to convert a date to an integer according to the date array in the file. The date should have the format dd-mm-yyyy and the result an integer containing the number of days from 01.01.0000. To make this correct we also need the function leapyear which we solved in the section about functions. In the code below we have the first part of the function date2number .
MODULE flowdata
IMPLICIT NONE
....
CONTAINS
....
FUNCTION date2number(datestr) RESULT (datenum)
IMPLICIT NONE
CHARACTER
INTEGER
INTEGER
INTEGER
INTEGER
( LEN =10)
INTEGER
INTEGER , DIMENSION (12) marray = 31 marray(2) = 28 marray(4) = 30 marray(6) = 30 marray(9) = 30 marray(11) = 30
....
datenum = 0
END MODULE flowdata
:: datestr
:: datenum
:: i
:: year
:: month
:: day
:: marray
This function has a character string as an input argument and an integer as the result. The input argument is in the format of dd-mm-yyyy . the array marray will contain the number of days in each month. Note that we use the marray = 31 to initialise the whole array with the value of 31. Then we just modify the elements for the moths with fewer than 31 days afterwards.
We also initialise the resulting variable to zero so we can add the number of days to it for each iteration.
MODULE flowdata
IMPLICIT NONE
....
READ (datestr(1:2), FMT = ’(I2)’ ) day
33
READ (datestr(4:5), FMT = ’(I2)’ ) month
READ (datestr(7:10), FMT = ’(I4)’ ) year
DO i = 0, year-1
IF (.NOT. leapyear(i)) THEN datenum = datenum + 365
ELSE datenum = datenum + 366
END IF
END DO
DO i = 1,month-1 datenum = datenum + marray(i)
END DO datenum = datenum + day
IF (leapyear(year)) THEN datenum = datenum + 1
END IF
END FUNCTION date2number
....
END MODULE flowdata
Note that we use the READ function to extract part of the input argument and store the binary value in the variables day , month and year . Now we can begin to take a look at the part that extracts the indexes of a subset of the arrays dates and water_flow . We call this subroutine find_indexes . Using this subroutine to find the indexes of the subset we can in our main program access the subset of the water_flow data using the start date and end date of the subset. The code below shows how this can be done.
MODULE flowdata
IMPLICIT NONE
....
SUBROUTINE find_indexes(start_date, end_date, start_index, end_index)
CHARACTER ( LEN =10), INTENT ( IN ) :: start_date
CHARACTER ( LEN =10), INTENT ( IN ) :: end_date
INTEGER , INTENT ( OUT ) :: start_index
INTEGER , INTENT ( OUT ) :: end_index
INTEGER
INTEGER
INTEGER
:: sday
:: eday
:: i
PRINT *, start_date
PRINT *, end_date sday = date2number(start_date) eday = date2number(end_date)
PRINT *, sday, eday start_index = 0 end_index = 0
DO i = 1, flength
IF (sday == dates(i)) THEN start_index = i
END IF
IF (eday == dates(i)) THEN
34
end_index = i
EXIT
END IF
END DO
END SUBROUTINE find_indexes
....
END MODULE flowdata
1. Write a main program using the module flowdata to read the values of the input file, extract data ranging from the date 01.03.1989 to the date 31.05.1989 and display the contents of the water flow on the screen.
2. Take the main program and add code to write the extracted data to a new file. Then use a program to plot the extracted data (you can use any plotting program available for you).
3. Add code to the main program to have a user interface asking for the start and end date.
4. Run the new main program and extract the data from 01.01.1989 to 31.08.1990 which is what is called a hydrological year and plot the extracted data. Look at the plot and try to explain the variations in the water flow.
35
It is very likely that you have heard the notion Object Oriented Programming or OOP for short.
An excerpt from Wikipedia explains the various parts of the notion of OOP as:
Object-oriented programming (OOP) is a programming paradigm using "objects", i.e. data structures consisting of data fields and methods together with their interactions, to design applications and computer programs. Programming techniques may include features such as data abstraction, encapsulation, polymorphism, and inheritance.
Data abstraction is explained as:
In computer science, abstraction is the process by which data and programs are defined with a representation similar in form to its meaning (semantics), while hiding away the implementation details. Abstraction tries to reduce and factor out details so that the programmer can focus on a few concepts at a time .
Encapsulation is explained as:
In a programming language encapsulation is used to refer to one of two related but distinct notions, and sometimes to the combination thereof:
• A language mechanism for restricting access to some of the object’s components.
• A language construct that facilitates the bundling of data with the methods (or other functions) operating on that data.
The notion of polymorphism is:
Subtype polymorphism, almost universally called just polymorphism in the context of objectoriented programming, is the ability to create a variable, a function, or an object that has more than one form .
Last we have the inheritance which is explained as:
In object-oriented programming (OOP), inheritance is a way to reuse code of existing objects, establish a subtype from an existing object, or both, depending upon programming language support. In classical inheritance where objects are defined by classes, classes can inherit attributes and behaviour (i.e., previously coded algorithms associated with a class) from pre-existing classes called base classes or superclasses or parent classes or ancestor classes. The new classes are known as derived classes or subclasses or child classes.
Having explained a few things about OOP we are now ready to try out the theory in solving a known problem by using the OOP technique.
In the previous section we used derived data types to encapsulate several variables that was connected to a data structure. We will now use this and change the code putting everything into an object which can have several instances with different data values. The object is a part of a module where the type is declared as usual, but with one exception. The type also has a contains statement where the names of the procedures to be used is declared. The code snippet shows how this is done.
MODULE class_terrain
IMPLICIT NONE
PRIVATE
TYPE , PUBLIC
CHARACTER ( LEN =80)
:: terrain
:: filename
36
INTEGER , POINTER
INTEGER
INTEGER
INTEGER
INTEGER
CONTAINS
PROCEDURE
PROCEDURE
END TYPE terrain
CONTAINS
....
END MODULE class_terrain
:: map(:,:) => null()
:: n_cols = 0
:: n_rows = 0
:: cell_size = 0
:: no_value = 0
:: load => load_data
:: dump => dump_data
The first difference from the usual declaration of the type is that we do not enclose the type name in a set of parenthesis, but use the syntax of an ordinary variable declaration. Note also that we have introduces the PRIVATE keyword which means that anything not specific declared as
PUBLIC is not accessible from outside the module. This is done to prevent procedures not part of the module to make unintentionally changes to values inside the module. The declaration of a procedure with the construct fload => load_data gives an alias for the procedure load_data declared after the contains keyword in the module. In addition we initialise the pointer to a NULL value using the construct map(:,:) => null() . Also the other variables are getting initialised so we can use the intrinsic constructor to create an instance of the object without having to supply values for each variable in the object.
PROGRAM ctest
USE class_terrain
IMPLICIT NONE
TYPE (terrain)
....
td1 = terrain( ’demfile.dat’ )
CALL td1%fload()
....
END PROGRAM ctest
:: td1
Declaring a new object of the terrain type is done the normal way. Then we call the implicit constructor td1 = terrain(’demfile.dat’) where we give the input filename as the only argument.
If we had not initialised the internal variables in the declaration of the type we would have to use td1 = terrain(’demfile.dat’, null(),0,0,0,0) . Omitting these last arguments to the constructor and not having the internal variables initialised in advance, the program would not compile.
Having set up the object and learned how to use the constructor to create an instance of the object we will now take a look at how the rest of the module can be programmed. Using the module from the previous section we now that we need at least two procedures, one to read the file, allocate space etc. and one to take the header lines and extract the values we need to allocate space for the terrain map.
CONTAINS
....
SUBROUTINE load_data(this)
CLASS(terrain)
INTEGER , DIMENSION (4)
INTEGER , PARAMETER
CHARACTER ( LEN =512), DIMENSION (4)
:: this
:: vars
:: u_number = 10
:: buffer
37
INTEGER
INTEGER
....
INTEGER , EXTERNAL
END SUBROUTINE load_data
:: b_start, b_end
:: i, j, k, l, res
:: a2i
The only argument to the load_data subroutine is a class variable of type terrain named this .
The variable is an instance of the class of type terrain and contains the filename, the pointer array and the four integer values. The variables local to this subroutine is used to extract the contents of the file into the variables of the class instance. So let us take a look at the rest of the code for this subroutine.
SUBROUTINE load_data(this)
....
OPEN ( UNIT =u_number, FILE =this%filename, FORM = ’FORMATTED’ , IOSTAT =res) buffer = ’ ’
DO i = 1, 4
READ ( UNIT =u_number, FMT = ’(A)’ , IOSTAT =res) buffer(i)
IF (res /= 0) THEN
PRINT *, ’Error in reading line 1, status ’ , res
CLOSE ( UNIT =u_number)
RETURN
END IF
END DO
....
END SUBROUTINE load_data
The only difference from this version of the subroutine and the one in the previous section is that we precede the name of the variables with this% just like we would for a non object derived type. Of course the local variables for the subroutine is not preceded by this% . In addition we use an array of character strings to hold the header lines in stead of a single character string and another array to hold the integer values extracted from the header lines.
SUBROUTINE load_data(this)
....
CALL extract_header_lines(buffer,vars) this%n_cols = vars(1) this%n_rows = vars(2) this%cell_size = vars(3) this%no_value = vars(4)
ALLOCATE (this%map(this%n_rows,this%n_cols), STAT =res)
IF (res /= 0) THEN
PRINT *, ’Allocation failure, status ’ , res
CLOSE ( UNIT =u_number)
END IF
....
END SUBROUTINE load_data
Here we have modified the extract_header_lines subroutine to take two arrays as input arguments. After extracting the values from the header lines we copy them into the various internal variables for this class instance. The rest of the subroutine is the same as the one in the previous section.
38
The next part we have to program is the dump_data subroutine. It can be constructed like the code snippet below.
SUBROUTINE dump_data(this)
CLASS(terrain) :: this
INTEGER
INTEGER
, PARAMETER :: u_number = 11
:: i, j, res
OPEN ( UNIT =u_number, FILE =this%filename, FORM = ’FORMATTED’ , IOSTAT =res)
WRITE ( UNIT =u_number, FMT = ’(I5)’ , IOSTAT =res) this%n_cols
WRITE ( UNIT =u_number, FMT = ’(I5)’ , IOSTAT =res) this%n_rows
WRITE ( UNIT =u_number, FMT = ’(I5)’ , IOSTAT =res) this%cell_size
WRITE ( UNIT =u_number, FMT = ’(I5)’ , IOSTAT =res) this%no_value
DO i = 1, this%n_rows
DO j = 1, this%n_cols
WRITE ( UNIT =u_number, FMT = ’(I4,A2)’ , ADVANCE = ’NO’ , IOSTAT =res) & this%map(i,j), ’, ’
END DO
WRITE ( UNIT =u_number, FMT = ’(A)’ , IOSTAT =res) ’ ’
END DO
CLOSE ( UNIT =u_number)
END SUBROUTINE dump_data
Like the subroutine load_data we have only the instance of the object as an input argument.
After opening the file we write the four header variables to the file, but in this version we do not use any preceding header text. Then we loop and write each value for the columns separated with a comma to the file. Note the use of ADVANCE=’NO’ which prevents the automatic line feed after each write. To write the column values for the next line we simply write a space character to the file without the ADVANCE=’NO’ to get a new line.
1. Change the dump_data subroutine so that the header lines contains the ncols, nrows, cell_size and no_value text strings in addition to the values.
2. Write a subroutine to clear the contents of an object instance using the same type of argument as the load_data and the dump_data subroutines. Note that the map array has to be deallocated in order so have a completely clean object instance.
3. Extend the main program with code to create a new instance with a different name and copy the contents of the first instance into the new one. Hint: you have to allocate the space for the map array in the new instance before you can copy the contents from the first instance.
39
Operators in Fortran are for example IF(a > b) THEN which is a test using numerical values in the variables. For other types of variables like characters and character strings we use the construct IF(C1 .GT. C2) THEN .
Table3 gives an overview of the operators
Numerical Other Explanation
-
==
/=
<
>
<=
>=
**
*
/
+
.EQ.
.NE.
.LT.
Exponentiation
Multiplication
Division
Addition
Subtraction
Equal
Not equal
Less
.GT.
Greater
.LE.
Less or equal
.GE.
Greater or equal
.NOT.
Negation, complement
.AND.
Logical and
.OR.
Logical or
.EQV.
Logical equivalence
.NEQV.
Logical not equivalence, exclusive or
Table 3: Logical operators
40
Intrinsic functions in Fortran are functions that can be used without referring to them via include files like in other languages where functions has to be declared before beeing used in the program
Table4, table5 and table6 gives an overview of the intrinsic functions in Fortran 95
Function
ABS
ACHAR
ACOS
ADJUSTL
ADJUSTR
AIMAG
AINT
ALL
ALLOCATED
ANINT
ANY
ASIN
ASSOCIATED
ATAN
ATAN2
BIT_SIZE
BTEST
CEILING
CHAR
CMPLX
CONJG
COS
COSH
COUNT
CPU_TIME
CSHIFT
DATE_AND_TIME
DBLE
DIGITS
DIM
DOT_PRODUCT
DPROD
EOSHIFT
EPSILON
EXP
EXPONENT
FLOOR
FRACTION
HUGE
IACHAR
IAND
IBCLR
IBITS
IBSET
ICHAR
IEOR
INDEX
Argument Result
Integer real complex
Integer
Real
Integer real complex
Character
Real
Character string Character string
Character Character
Complex
Real
Real
Real
Logical mask, dim Logical
Array Logical
Real Real
Logical mask, dim Logical
Real
Pointer
Real
Real
Logical
Real
X=Real, Y=Real Real
Integer Integer
I=Integer, Logical
Pos=Integer
Real
Integer
Real
Character
X=Real,Y=Real Complex
Complex
Real Complex
Complex
Real complex
Real Real
Logical mask, dim Integer
Real Real
Array, shift, dim Array
Char D,T,Z,V
Integer real complex
Character
Double precision
Integer real
Integer real
Integer
Integer real
X=Real, Y=Real Real
X=Real, Y=real Double precision
Array, shift, boundary, dim
Array
Real
Real complex
Real
Real
Real
Real
Real complex
Integer
Real
Real
Integer real
Character
Integer, Integer
Integer, pos
Integer real
Integer
Integer
Integer
Integer, pos,len
Integer, pos
Character
Integer, integer
Integer
Integer
Integer
Integer
String, substring Integer
Explanation
The absolute value
Integer to ASCII character
Arcuscosine
Left adjustment
Right adjustment
Imaginary part
Truncate to a whole number
True if all elements == mask
True if allocated in memory
Round to nearest integer
True if all elemnts == mask
Arcsine
True if pointing to target
Arctangent
Arctangent
Number of bits in argument
Test a bit of an integer
Leat integer <= argument
Integer to ASCCI character
Convert to complex number
Conjugate the imaginary part
Cosine
Hyperbolic cosine
Count of true entries in mask
Returns the processor time
Circular shift of elements
Realtime clock
Convert to double precision
Number of bits in argument
Difference operator
Dot product
Double precision dot prod.
Array element shift
Smallest positive number
Exponential
Model exponenet of argument
Integer <= argument
Fractional pert of argument
Largest number
Integer value of argument
Bitwise logical and
Setting bit in pos = 0
Extract len bits from pos
Set pos bit to one
ASCII number of argument
Bitwise logical XOR
Position of substring
Table 4: Intrinsic functions
Function
INT
Argument
Integer real complex
Result
Integer
Explanation
Convert to integer
IOR
ISHFT
ISHFTC
KIND
LBOUND
LEN
LEN_TRIM
LGE
LGT
LLE
LLT
LOG
LOG10
LOGICAL
MATMUL
MAX
MAXEXPONENT
MAXLOC
MAXVAL
MERGE
MIN
MINEXPONENT
MINLOC
MINVAL
MOD
MODULO
MVBITS
NEAREST
NINT
Integer, integer
Integer, shift
Integer
Integer
Integer, shift Integer
Any intrinsic type Integer
Array, dim
Character
Character
A,B
Integer
Integer
Integer
Logical
A,B
A,B
A,B
Real complex
Real
Logical
Matrix, matrix a1, a2, a3,...
Logical
Logical
Logical
Real complex
Real
Logical
Vector matrix
Integer real
Real
Array
Integer
Integer vector
Maximum exponent
Indices in array of max value
Array, dim, mask Array element(s)
Tsource, Fsource, mask
Maximum value
Tsource or Fsource Chosen by mask a1, a2, a3,...
Real
Integer real
Integer
Array Integer vector
Array, dim, mask Array element(s) a=integer real,p Integer real a=integer real,p Integer real
From pos to pos Integer
Real, direction
Real, kind
Real
Real
Bitwise logical OR
Shift bits by shift
Shift circular bits in argument
Value of the kind
Smallest subscript of dim
Number of chars in argument
Length without trailing space
String A <= string B
String A > string B
String A <= string B
String A < string B
Natural logarithm
Logarithm base 10
Convert between logical
Matrix multiplication
Maximum value of args
Minimum value
Minimum exponent
Indices in array of min value
Minimum value a modulo p a modulo p
Move bits
Nearest value in direction
Round to nearest integer value
NOT
PACK
PRECISION
PRESENT
PRODUCT
Integer
Array, mask
Real complex
Argument
Integer
Vector
Integer
Logical
Array, dim, mask Integer real complex
RADIX Integer real
RANDOM_NUMBER Harvest = real
RANDOM_SEED Size, put or get
Integer
Real 0 <
Nothing
= x < = 1
Bitwise logical complement
Vector of array elements
Decimal precision of arg
True if optional arg is set
Product along dim
Radix of integer or real
Subroutine returning a random number in harvest
Subroutine to set a random
RANGE Integer real number seed
Decimal exponent
REAL
REPEAT
Integer real complex
Integer real complex
String, ncopies
Real
String
Convert to real type
Concatenate n copies of string
Table 5: Intrinsic functions
Function
RESHAPE
Argument
Array, pad, order
Real shape,
Result
Array
Explanation
Reshape source array to array
RRSPACING
SCALE
SCAN
SET_EXPONENT
SHAPE
SIGN
SIN
SINH
SIZE
SPACING
SPREAD
SQRT
SUM
SYSTEM_CLOCK
TAN
TANH
TINY
TRANSFER
TRANSPOSE
TRIM
UBOUND
UNPACK
VERIFY
Real, integer
SELECTED_INT_KIND Integer
SELECTED_REAL_KIND Integer
Real,integer
Real
Real
String, set, back Integer
Integer
Integer
Resl
Reciprocal of relative spacing of model
Returns X
· b I
Position of first of set in string
Kind number to represent digits
Kind number to represent digits
Set an integer as exponent of a real X
· bI
− e
Vector of dimension sizes
Absolute value of A
·
B
Array
Integer real, integer real
Real complex
Real
Array, dim
Real
Source, copies
Real complex dim, Array
Array, dim, mask Integer real complex
Count, count count,
Real complex
Trhough the arguments
Real
Real
Real
Real
Real
Real
Source, mold, size Mold type
Matrix
String
Array, dim
Vector, field mask,
Integer vector
Integer real
Real complex
Real
Integer
Real
Matrix
String
Integer
Vector type, mask shape
String, set, back Integer
Sine of angle in radians
Hyperbolic sine
Number of array elements in dim
Spacing of model number near argument
Adding a dimension to source
Square root
Sum of elements
Subroutine returning integer data from a real time clock
Tangent of angle in radians
Hyperbolic tangent
Smallest positive model representation
Same bits, but new type
The transpose of matrix
REmove trailing blanks
Largest subscript of dim in array
Unpack an array of rank one into an array of mask shape
Position in string not in set
Table 6: Intrinsic functions