Section L3.3: Further Ideas in Fortran Programming

This section covers:

Repetition

Much of the power of computers derives from their ability to carry out repetitive calculations very quickly. In previous examples the instructions we have written have each been carried out once and only once every time the program was run.

It is very easy to make a program repeat a sequence of instructions an indefinite number of times. However the example we are about to give is an example of very bad programming practice which should never be emulated! it is provided here simple to illustrate how easy concept is to implement.

Any instruction or sequence of instructions will be repeated indefinitely if it is preceded and terminated respectively by the two instructions shown below:

do
   ! instructions to be repeated here
end do
It is considered good practice to indent every instruction between the do and end do by three character spaces.

Here is a very simple program which illustrates this. It is a very bad program, because as written it will never stop running!

program zap
! This is not a good program !!!
! Before running it, make sure that you can identify
! `panic button' which stops programs on you computer.
! On Unix systems it is simultaneously `Control' and `c'
!JWP 29/10/98

implicit none

do
 print *, 'ZAP!!'
end do

end program zap

If you are going to compile and run it, first make sure you know how to do an `emergency stop' on your computer system. This is usually achieved by pressing simultaneously the letter `c' key and the key marked `Control' or `Cntrl'.

The principle should however be clear from this example....

To be useful, the repetition clearly needs to be under rather better control! A useful way of achieving this is to count the number of repetitions and stop after a predetermined number. the means of doing this is described next.

Counting and further variables

To repeat a set of operations, say, 10 times, we use a modified construction as follows:
 
do times = 1, 10
 print *, 'ZAP!!'
end do
The only difference between this and the previous example (so far) is in the first line. The interpretation of this can be consider to be as follows:

Like any other variable, times must be declared at the beginning of the program, before it is used. However, because times will have values which are always whole numbers or integers, the form of the declaration is different from those seen so far:

integer :: times
Here is the complete, and much improved, program:
program zap
! This is a better program !!!
! JWP 29/10/98

implicit none

integer :: times

do times = 1, 10
   print *, 'ZAP!!'
end do

end program zap
Copy it from this link.

The counter variable has other uses than just stopping this program loop. Its value is 1 the first time round, 2 the second and so on.

Try changing the instruction in the loop to:

print *, 'ZAP times ..', times
and confirm this.

There are a number of other uses for integer variables, but a major one is controlling loops like this. The number of times the loop is to be repeated, and, associated with it, the starting and finishing values of the counter variable, must be specified before the do instruction is encountered, but may in fact be general integer expressions.

Note that in this example the numbers `1' and `10' were written without decimal points, whereas these were required for numbers in the general expressions encountered previously. This is because these starting and finishing values must be whole numbers. The following would also be allowed, try them, printing out the counter value on each loop:

The counter variable may be also be used in any expression inside the loop. Note that integers may generally be used in expressions like real non-integer variables and numbers. Care must however be taken if real and integer variables are to be used in the same expression. This is illustrated in the examples below.

The following sections provide programs to illustrate the use of repetition loops and a number of other programming techniques. You are encouraged to copy and modify these as described.

Example: a table of squares

This program calculates and prints a table of squares.
program squares
! calculate squares
! JWP 29/10/98

implicit none

integer :: number, square
integer, parameter :: max = 10
!
print *, 'Squares of numbers from 1 to', max
!
do number=1, max
   square = number ** 2
   print *, 'Square of ', number, ' is ', square
end do
!
end program squares

Modify it to calculate and print the squares of numbers from 1 to 15 by altering only a single character in the program.

Example: a table of square roots

The function sqrt() will calculate the square root of an expression or variable inside the brackets, However, this must be a real. However there is a function called real() which converts an integer to a real value as shown in this program.
program squares
! calculate squares
! JWP 29/10/98

implicit none

integer :: number
integer, parameter :: max = 10
real :: real_number, root
!
print *, 'Table of square roots'
print *, 'Number  ', '        Square root'
!
do number=1, max
   real_number = real(number)
   root = sqrt(real_number)
   print *,  number, root
end do
!
end program squares

Square roots can also be calculated by raising numbers to the power 0.5, or in Fortran writing ** 0.5 Check that this gives the same answers, and modify the program to tabulate cube roots.

Non-integer Loop Counting

The variable used in the do instruction in general must be an integer. However we sometimes want a quantity which will will take non-integer values to be changed each time we go round the loop. Below are parts of a program which tabulates square roots of number at from 1.0 to 3.0 at 0.25 intervals.
integer :: i 
real :: x
.....
do i = 1, 12
 x = i * 0.25
 print *, x, sqrt(x)
end do
...
There is a more general way of doing this described later.

Example: `recursive' evaluation

What would be the effect of the following assignment instruction?:
x = x + 3
If you are in any doubt about what will happen, complete the following program segment by adding the necessary additional instructions, and run it.
.....
x = 4
print *, 'x was', x
x = x + 3
print *, 'x now is', x
.....
There are many circumstance where it is convenient or necessary to use this kind of `recursive' instruction. One such is illustrated in the example below. Complete this program and use it to compute some factorials. Check to see that it gives the correct answers! 5! is 120, 10! is 3628800. What happens when you try to calculate 100! which is about 10158?

Example: calculating factorials

Recall that n! is given by:

n! = 1 . 2 . 3 .... (n-1) . n

Consider the following program segment:

...
integer :: fact, n, i
n = 5
fact = 1
do i=2,n
 fact = fact * n
end do
...

Exercise

Write a program which you can use to calculate the sum of the first n integers where n is specified as a variable.

Recall that this quantity is equal to ½(n+1)n.

More Non-integer Loops

Consider the problem of printing out 10 temperatures spaced equally between the boiling points of propane (231.0K) and butane (272.6K). Because these are both non-integer and not obviously related, the method used above is not convenient. the following program steps show how it can be done.
real :: Tprop, Tbut, T, dT
integer :: i, n
....
n=10   ! number of points
...
dT = (Tbut - Tprop) / (n-1)  ! divide range by number of INTERVALS
! .. this gives size of interval
T = Tprop ! Start here
do i=1, n
   print *, T  ! the current temperature
   T = T + dT  ! next temperature
 ...
end do
...
Exercise: Write a program to tabulate the vapour pressures of propane and butane at 12 equispaced temperature points between their respective 1atm boiling points.

Decision Making: Conditions

Consider the following sequence of instructions:

....
if ( a  >=0.0 ) then
   print *, 'a is zero or positive'
else
   print *, 'a is negative'
end if
...
Their implication should be fairly obvious. If the variable a has a value greater than or equal to zero, then the instruction:
 print *, 'a is zero or positive'
is carried out and the appropriate message printed. On the other hand if this is not the case (`else') then the instruction:
 print *, 'a is negative'
is obeyed.

The program would then proceed with any instructions following end if.

The above is an example of the most general form of the conditional or `if' construction used for decision making in computer programs. The lines between then and else and between else and end if may contain any sequence of legal `executable' instructions. (This excludes `formal' instructions like real :: or end program.) As with do loops, note the indentation of the instructions by three character spaces. This is to give clarity to the program.

As an example of using this for of the `if' construction, here is a program to solve a quadratic equation using the quadratic formula. The program tests to see if the equation has real roots before proceeding with the solution.

program quadratic
! solve a quadratic with real roots
! JWP 22/10/98

implicit none

real :: a, b, c, d, x, x1, x2
!
a = 1
b = 2
c = -3
!
print *, 'Solving quadratic:'
print *, a, ' x**2 +', b, ' x + ', c
! check nature of roots..
!
d = b**2 - 4.0*a*c
!
if (d<0) then
   print *, 'Has complex roots.'
else
   x1 = (-b + sqrt(d)) / 2.0 / a
   x2 = (-b - sqrt(d)) / 2.0 / a
   print *, 'Roots are:', x1, x2
end if
!
end program quadratic

Three other forms of the `if' construction are useful. Sometimes it is only required to carry out a series of instructions if a condition is satisfied. This is achieved by the following. Note that the symbols /= are the Fortran version of the `not equals' sign.

if (a /=0.0 ) then
   print *, 'a is not zero so can be a divisor'
   x = 1 / a
end if
Notice that in this case nothing happens if the condition is not satisfied, i.e. if a is equal to zero. In this case the value of x is not set by any instruction shown in this program segment.

If only a single instruction is to depend on the outcome of the condition then it may be shortened to:

if (a /= 0.0 )  x = 1 / a
Finally, by using else if several conditional statements may be chained together:
if(a > 0) then
   print *, 'a is bigger than 0'
else if (a < 0) then
   print *, 'a is less than 0'
else
   print *, 'a must equal zero'
endif

Computer form of relational operators

Note the `computer form' of the the mathematical relational operators.

`<' and `>' have their obvious forms, <= stands for `less than or equal to' and hence the obvious interpretation of >=.

'Not equal to as above is /=.

Confusingly `equal to' must be written with two equals signs, i.e. as ==.

Example

Below is a program which generates and prints random numbers.
program random
! generate some random numbers
! JWP 22/10/98

implicit none

real :: x
real, external :: rand
integer :: i

do i= 1, 10
   x = rand(0)
   print *, x
end do

end program random
(Note the special form of declaration: real, external :: rand which says that rand, a function to generate random real numbers in the range 0.0 to 1.0, is to be found outside, i.e. `external' to the program. This is one of a number of useful function which exist in special libraries.)

Copy the program from here and modify it (a) to print only random number greater than 0.5, and (b) to count and subsequently print out the number of such numbers.

The exit Instruction

The instruction:
exit
which can only appear inside a do .. end do loop causes a immediate termination of the loop. The program carries on at the instruction following end do.

The do may be either with or without a counter variable. In the latter case the count is overridden and the loop stops at whatever is its current value.

The exit instruction is normally always the subject of a condition, e.g.:

if (TK <= 0.0) exit

Other Methods of Terminating Loops

There is another method of stopping a do...end do loop using a conditional statement:

do while (x == 1)
   .
   .
   .
end do
This is equivalent to saying:
do
   if(x /= 1) exit
   .
   .
   .
end do
The do while format is preferred by purists as the loop can only terminate after a complete iteration.

Further exercise

Adapt the modified vapour pressure calculation program so that it can be stopped by typing in an infeasible temperature, i.e. one less that O Kelvin.

Interacting with your program

This section has been left until last because we do not particularly want to encourage you to write `interactive' programs until you are reasonably experienced in writing self contained programs.

All the programs shown to you so far have been `self contained' in that all the information required for the program to work is contained within the program.

All your handin examples should normally be written in this way. A major advantage of such a program is that it's operation can be checked by reference only to a printout of the program. A disadvantage is that every time we want to change a parameter then the the program file must be edited and recompiled.

We will now discuss how to make programs more `user friendly' and `interactive'.

When ever the computer encounters an instruction like the following:

read *, x
it does two things.

First it stops and waits for the user to type something on the keyboard. Secondly it interprets whatever has been typed in the context of what kind of variable x has been declared to be, and if it can, assigns whatever has been typed in to x. This should be clear from the following complete program which you might like to copy and try:

program readin
real :: x
print *, 'Type in a valid real number:'
read *, x
print *, 'The number you typed was ', x
end program
You can copy the program from here. If you are trying it out, see what happens if you type either an integer or something that isn't a valid number.

Note that the variable which appears in the read instruction must be a variable which is not declared as a parameter.

It is possible in principle to have more than one variable in the list following read *, but this is not a good idea when numbers are being read from the keyboard. (They can come from elsewhere, as will be discussed later.) Also you should always print out a message to remind the user what to type in.

Subroutine procedure calls

We have already encountered an example of a `procedure call' in the print instruction. we can (loosely) define a procedure call as an instruction which `causes something to happen' other than, or in addition to, simply assigning a value to a variable.

Procedure calls have two main characteristics. Firstly the procedure invoked by the call has a name, e.g. print. Secondly there are usually one or more arguments which specify with what the procedure is to perform whatever it is meant to do.

The name of the procedure is fixed, but the programmer chooses the arguments. Thus:

 print *, 'Hello' 
has as an argument the character string 'Hello' and so that is what gets printed.
 print *, whatever 
has the variable whatever as an argument, so its value gets displayed.

print is a rather special procedure which is built into the language. There are some others like it which you will encounter later. Another type of procedure is the subroutine procedure or simply a subroutine. These are not built into the language but are available in special libraries or can be defined by the programmer.

At this point we will introduce one example of a subroutine procedure which is available in a standard library, and another user defined subroutine which will be supplied to you. Writing your own subroutines will be covered later.

A subroutine procedure call which activates the procedure consists of the word call followed by the name of the procedure and then a list of its arguments (if any) in brackets.

Subroutine procedure: Example 1

A subroutine (we shall use the words `subroutine' and `procedure' interchangeably from now on) named system enables any Unix command (i.e. what might be typed on the keyboard, like ls, edit, cp) to be activated from within a Fortran program. This is not something that you will often want to do (and it should be done with care, rm *.* will destroy all you files!) but it provides a simple example of a subroutine.

system takes a single argument, the name of the Unix command as a character string in quotes, e.g. 'ls'

The following complete program will cause a listing of your files to be printed on the screen when the compiled program is run:

program list_files
! An minimum example of a subroutine procedure call
! JWP 22/10/98

implicit none

! This instruction makes it work:
call system ('ls')
!
end program files
A version of this program can be copied from here. You can try changing `ls' to `ls -l' and see what happens.

The above versions of the program are the minimum size and complexity required to access this subroutine. If you learn more about the Fortran 90 language you will discover that a stricter and more pedantic form is available. For completeness we have supplied a `long' version of the program here.

Subroutine procedure: Example 2

subroutine atomic
is a set of instructions which will determine, for an element whose name is supplied as a character string (e.g. 'Na'), the atomic number and atomic weight. The instructions are supplied in the form of a subroutine procedure to illustrate further how these are used. This example also serves to show how user written subroutines are put together (though you are not expected to understand this yet, it will be covered later). Additionally, it shows a rather different kind of application for programs from those shown so far.
subroutine atomic (element, number, value)
   ! look up atomic no. and weight of some common elements
   ! JWP 22/10/98

   implicit none 

   character *(*), intent (in) :: element
   real, intent (out) :: value
   integer, intent(out) :: number
   integer, parameter :: na=12
   integer, dimension(1:na), parameter :: an = &
   (/17, 35, 11, 20, 6, 1, 7, 8, 16, 9, 53, 19/)
   real, dimension(1:na), parameter :: aw = &
   (/35.5, 79.9, 22.0 , 40.1, 12.0 , 1.0, 14.0, 16.0, 32.1, 19.0, 126.9, 39.1/)
   character*48, parameter :: ename = &
   'Cl,Br,Na,Ca,C ,H ,N , O ,S ,F ,I ,K '
   integer :: i
   character *(2) :: copy
   !
   copy = adjustl(element) // '  ' ; copy = copy(1:2) ! fiddle with length etc.
   i = index (ename,copy)  ! look up element in list
   if (i==0) then
      print *, 'I do not have data for element ' // element
      value = 0.0 ; number = 0
      return
   end if
   !
   i = (i-1)/3 + 1  ! Table entry number
   value = aw (i)
   number = an (i)
end subroutine atomic

To make use of this subroutine you must do two things. Firstly, as in the previous example, you must write a program to `call' the subroutine. A typical call might look like this:

call atomic ('K', Kno, Kwt)
Here the string 'K' determines that it is the properties of potassium which are required. Kno is a variable which you the programmer must have declared as an integer in your program. The subroutine call will set this to the atomic number of potassium. Kwt must have been declared as a real variable and will contain the corresponding atomic weight. Note that these can be any appropriately declared variables and can thus have any names that you want to give them.

To use use this subroutine you also need to let your program know about it. The simplest way to to this is to copy the whole subroutine from the link and put it in the same file as the program you are writing which will use the subroutine. the layout of the file will then be as shown below.

program atom_stuff
! My own program, declarations etc. go here..
real :: Kwt, ...
....
call atomic ('K', Kno, Kwt)
print *, 'Properties of Potassium..', ...
...
end program atom_stuff


! What follows is just copied..
subroutine atomic (element, number, value)
! look up atomic no. and weight of some common elements
... etc
...
end subroutine atomic
It is not necessary that you understand how atomic actually works, but it is useful to note the existence of variables which will hold character strings. a declaration such as:
character *(2) :: element
declares a variable called element for storing not numbers but letters, up to a maximum length of two characters.

Intent Attribute

The intent attribute informs a programmer using a subroutine whether a specific argument is to be:

If an argument has intent(in), then it may not be altered in the subroutine. All subroutine arguments should be given an intent attribute to aid clarity in the subroutine use.

Subroutine Structure

The structure of a subroutine is quite similar to that of a program:

For clarity, the subroutine code is usually indented by three character spaces.

Functions

Functions are very similar in structure to subroutines. They take a number of parameters and return a variable to the calling program. Here is a simple function:
function calc(x)
   !this function evaluates a simple formula
   !Ross Andrews 26/10/99

   implicit none

   real, intent(in) :: x
   real :: calc

   calc = x**2 + 3*x - 4

end function calc
The main point to note here is that the function name, calc is declared as a variable, and assigned to, in the function. This will be the value returned to the calling program. In the calling program, the function calc must be declared as follows:
real, external :: calc
i.e. external to the main program, and can be used as:
y = calc(43.2)

Fortran has a number of built-in functions, some of which you have used already:

Any user defined functions should come after the end program prog_name statement, and in the same file in the same manner as subroutines.

Subroutine and Function Variables

The variables in the subroutines and functions (subprograms) described here are independent from the main program variables, and vice versa. What this means in practice is that subprograms cannot refer to main program variables, unless they are passed as an argument, and likewise in the main program.

Subroutines or Functions

There may be some confusion as to whether a subroutine or a function best suits a particular task. Whilst this is down to the preferences of the individual programmer, there are a number of guidelines:

These are only guidelines, not strict rules.

Return to Modelling Lab

Course Organiser
Last Modified 4/9/00