This is meant to be a practical introduction to Fortran programming. The best way to learn programming is to jump right in. We will look at concrete examples that do useful things. Some topics will be used before they have been properly defined, but you should be able to grasp the sense of what is going on, and you may expect the explanation to come shortly thereafter.
Before you can do anything with Fortran, you need to have access to a computer, have an account, know how to use an editor, and so on. I'm not going to touch these topics, but our first exercise will test whether you have mastered them on your own so that we can move past these details and get into Fortran. Our goal in this first exercise is to do "whatever it takes" to create, compile, and run a "Hello, world!" program.
You will need to create a file something like this:
program hello print *, 'Hello, world!' stop end program
On my (UNIX) system, I can run this program with the commands:
f90 hello.f90which should cause the appearance of the message:
a.out
Hello, world!Do what you have to do until you can get this program to work on your system.
This program is written in Fortran90. You might have run across programs written in earlier versions of Fortran, which required that most statements begin after column 6. While we won't be using such versions here, you should be aware that this was the old style!
The program begins with a PROGRAM statement which includes a "name" for the program. The program ends with an END statement. Neither of these routines "does" anything, but they are important in defining the structure of the program.
The "work" of the program is carried out by the PRINT statement. You may be familiar with the more complex but versatile WRITE statement, which you could use here as
write ( *, * ) 'Hello, world!'.
The STOP statement tells the program to halt. The END statement says where the words of the program stop. The STOP statement says where the action or execution of the program stops.
Programming is the implementation of algorithms. Algorithms tend to be expressed in an informal language, and we need to understand how to express algorithmic ideas in a "picky" language.
Informally, our first algorithm is expressed this way:
To find the square root of a number, divide the number by your guess and average.As an example of this process, if we guessed that the square root of 12 was 3, then the algorithm tells us a better guess would be (3+12/3)/2, or 3.5.
Here is an explicit plan for implementing the algorithm:
program root a = 12.0 x = 2.0 print *, ' ' print *, 'Number whose square root is desired, A = ', a print *, 'Initial estimate X = ', x ! ! Instead of repeating this loop 5 times, we could ! stop early if X is close enough. ! do i = 1, 5 x = ( x + a / x ) / 2.0 print *, 'New X = ', x end do print *, ' ' print *, 'Correct square root is ', sqrt ( A ) stop end program
It's not hard to find the "interesting" line that does the hard work:
X = ( X + ( A / X ) ) / 2.0.. Note how the new value of X overwrites the old one immediately. Often a beginning programmer tries to save every computed value under a different name. This is hard to program, and in complicated problems can cause you to run out of computer memory.
Notice how we arranged to carry out the process 5 times in a row, using a DO loop, which begins with the DO statement and ends with the END DO statement. The form of the DO statement
do i = 1, 5requests that the statements inside the loop be carried out five times, during which the value of I will be increased in steps of 1 from 1 to 5.
In Fortran90, comment statements begin with an exclamation mark. (Older versions used the letter C in column 1). Comments are used to discuss what is going on in the program, and often explain what algorithm is being used, or why operations were done in a particular way.
We have used lots of PRINT statements, to declare the name of the program, to describe the input, and to report the output. PRINT statements are also useful when debugging a program.
One flaw of the root program is that it doesn't have any judgment. Although we clearly can't allow a starting guess of 0, the program doesn't check for this condition. Right after the value of X is set, we really ought to do something like this:
if ( x == 0.0 ) then print *, 'Fatal error!' print *, ' The starting guess was X = ', x print *, ' but this is not allowed.' stop end ifThis is an example of an IF statement. The object inside the parentheses is a condition to check. The operators allowed to be used are the arithmetic comparisons:
( x == y ) ( x > y ) ( x >= y ) ( x /= y ) ( x < y ) ( x <= y )and the logical operators .NOT., .AND. and .OR.:
( x < y .and. y < z ) ( .not. ( sin ( x ) == 0.0 ) ) ( i == 1 .or. i == n )
The IF statement has an expanded form that includes ELSE IF and ELSE statements:
if ( x > 0 ) then abs = x else if ( x < 0 ) then abs = - x else abs = 0 end ifwhere one or more ELSE IF statements can be used. The ELSE statement, if it occurs, occurs last.
Let's show how these ideas can be used in a program. Instead of rewriting the square root example directly, we will look at a related program that carries out the bisection method to find a root of a given function.
program bisect ! ! A and B are the endpoints of an interval to be searched. ! We require that F(A) and F(B) have opposite signs. ! a = 0.0 b = 2.0 if ( ( f(a) > 0.0 .and. f(b) > 0.0 ) .or. & ( f(a) < 0.0 .and. f(b) < 0.0 ) ) then print *, ' ' print *, 'Fatal error!' print *, ' The function does not change sign at the endpoints.' stop end if do c = ( a + b ) / 2.0 if ( f(c) == 0.0 ) then exit else if ( abs ( f(c) ) < 0.00001 ) then exit end if if ( sign ( 1.0, f(c) ) == sign ( 1.0, f(a) ) ) then a = c else b = c end if end do print *, ' ' print *, 'Estimated root occurs at X = ', c print *, 'Value of function is F(X) = ', f(c) stop end program function f ( x ) f = cos ( x ) - x return end function
Look at the various IF statements and see how they are used. Note that the ABS function is the absolute value, the SIGN(A,B) function returns a value with the magnitude of A and the sign of B, and that the function f(x) is a user-defined function that is included after the end of the program.
We have used a new version of the DO statement, which is often called "DO forever". Instead of counting how many times the statements are to be repeated, we don't specify any limit explicitly. Instead, we repeat the loop until some condition is reached, and then we use the EXIT statement to leave the loop. (If we aren't careful in writing a loop like this, our program can get stuck there forever.)
Even though we have a rough idea that this program really does calculate the square root of X, we should take the time to carefully verify that this really is a Fortran program, made up of Fortran statements. And we should talk a little more carefully about what each of the lines of the program are doing there.
First of all, there is a Program statement, and a matching End statement, that tell us where the program begins and ends. There are lines beginning with ! which we know are comments. So much for the easy stuff!
There is a declaration statement that declares that I is an integer variable, and can only take on whole number values. We'll see that this is because I is used to count the number of steps we take.
There are declarations that X and Xroot are real numbers, that is, numbers that can have decimal fraction parts. Even if X was a whole number, like 7, we know that its square root could have a decimal fraction part. These declarations tell the program to be prepared to handle any number, not just integers.
The Write statements are a simple way of printing messages to the screen, and the Read is a simple way of getting information from you, the user.
The statements Continue statements are labeled statements. They don't really "do" anything, but provide a way to terminate a loop, or can be a place where we can jump with a Go To statement.
The arithmetic operators, which are used to do calculations, are:
There are some statements that check whether certain conditions are true or false, and do something based on that fact. Checking a condition has the form:
If ( X. Eq. Y ) Then (statements to carry out if X is equal to Y) End If
We can also stack conditions together, so that if something is true, we do one thing,otherwise, if something is true, we do another thing, as in this example:
If ( X. Lt. Y ) Then (statements to carry out if X is less than Y) Else If ( X .Gt. Y ) Then (statements to carry out if X is greater than Y) End IfNotice that in this last example, we didn't do anything if X was equal to Y!
The Do statement begins a Do loop, that is, a group of statements that is to be repeated. The "I=1,10" in the Do statement tells us how many times we have to repeat the loop. We are to start the loop by setting the variable I to 1, then increase the value of I each time we reach the end of the loop, and keep repeating until we have completed the loop with I equal to 10. I is our loop counter, which will simply have the values 1, 2, 3, ..., 10. During each repetition we may actually use the value of I as part of the calculation if we need it. (Notice that we did this in the "hello2" example, where we printed the value of I.)
Finally, notice the indentations used. Fortran requires that most statements start in column 7 or beyond. We are free to indent those statements even further, however, to show which statements are controlled by the IF statement, and which are repeated by the DO statement, for example. This helps to make the program readable.
We've already discussed the "comment" statement, which allows you to insert remarks through your program. A Fortran program doesn't need any comments to run. Comments are for the Fortran programmer!
Programming is not an easy business. A program without comments is just a bunch of formulas. You need to write down in comments the reasons that you chose those particular formulas, what you were doing, and how you know you got the right answer. Adding comments for this purpose is called program documentation.
You might have a large beginning block of comments that describe the overall purpose of the program, and then comment individual groups of statements that carry out a step of the plan.
You should also consider using descriptive variable names when it makes sense. Your program is a script, and the variables are the "characters". If you can't remember their names, you'll never understand the plot!
When your program runs, it should also print information out. This is also a form of documentation. It might print out the program's name, the last date it was modified, and its purpose. If interactive, it might print out messages telling the user what input it is expecting. And any errors or problems that occur should result in a helpful message being printed.
The documentation for the program might also include a simple, standard problem that the program handles, and a record of the output produced by the program. This is an easy way to make sure the program isn't completely demented.
Most programs do calculations involving numbers. Usually we are working with numbers whose values are not specified when we write the program. Such quantities are called variables. We have to give variables symbolic names in a program. We use the symbolic names to set the value of variable, to alter it, or retrieve the value for use in formulas.
For instance, the following statements use the variables Sum and Average:Program Sumup Real Average Real Sum Sum = 0 Sum = Sum + 2 Sum = Sum + 16 Sum = Sum + 3 Average = Sum / 3 Write (*,*) 'The sum is ', Sum Write (*,*) 'The average value is ', Average Stop End
The statements are carried out one at a time. The value of the variable Sum is altered several times. The value of Average isn't known until the last step, when the current value of Sum ,which is now 21, is divided by 3, setting Average to 7.
The names that are used for variables can be up to 6 characters long, according to the strict rules of Fortran. Most versions of Fortran will let you use longer names than that. WATFOR allows names to be up to 32 characters long. Names must start with a letter, but may include numbers. In WATFOR, a name may also include underscores or dollar signs.
When writing a program, you will find that some variables will naturally only have whole number values, associated with counting things. Other variables will have values associated with measurements, and will usually have decimal fractions. Fortran calls the first type Integer variables and the second kind Real.
Fortran programs begin with a section which lists the names of the variables to be used. The declaration statement must also declare the "type" of the variable, that is, whether it is Real or Integer. This declaration determines how much space the program will set aside to store the variable, what kind of operations will be allowed to be performed on that variable, how it will be printed out, and so on.
If you do not explicitly declare the type of a variable, a type will be assigned to it, based on the first letter of the variable's name. Variables whose names begin with I, J, K, L, M or N will be assigned Integer type, while variables with names from A to H, or O to Z will be assigned Real type. It is usually a bad idea to rely on this fact. You should always explicitly declare every variable in your program.
A type declaration statement has the form
Variable-Type Variable-NameVariables of the same type may be declared together, separated by commas. The following are some type declarations:
Real X Integer Itmax, Numb, Jump Complex Alpha Double Precision Fred Logical Even, Weekend, Onsale Character*20 Myname
Declarations give information about how variables may be used. Hence, the type declarations must come before the "executable" statements, the ones that assign values to variables, read values in or write them out, or otherwise operate on the variables.
There are some other types available, some of which we will see later on. These include:Once we have declared variable names, we can begin writing statements that assign values to variables and compute formulas based on those values.
The simplest assignment statement has the form:Variable name = Constantwhere Constant is an explicit value, such as "1" or "-300.3". Of course, the constant we use should be of the proper type for the variable. For instance, if the variable were an integer, we wouldn't want to try to assign it the value "2.3". The more interesting form of the asignment statement is:
Variable name = Expressionwhere Expression is some formula involving constants, variables, operators and functions. Some simple examples of assignment statements include:
X = 1.3 Number = 17 Failing = .True. (Assigning a value to a LOGICAL variable) name = 'Francis' (Assigning a value to a CHARACTER variable)These statements are simple to understand because they set a variable to a specific, unchanging value, called a constant. The kind of constant we are allowed to use depends, of course, on the variable we are setting. A real variable can be assigned any decimal value, but we wouldn't try to assign an integer variable the value 1.3, since it's not set up for that kind of value. Most assignment statements are more complicated. They still set the value of the variable on the left hand side of the = sign, but they do so by evaluating a formula on the right side, involving variables or constants. In order to know what the result is, you have to know the values of the variables that were used in the formula:
X = X + 1 Y = (Z + 3) * (Z - 4) Score = (Poise + Difficulty + Speed) / 3.0 Z = Sqrt(X) (This takes the square root of X)It's easy to understand what X = 1 is trying to do, but the statement X = X + 1 is really a little strange. This is not a statement in algebra. The = sign doesn't state that the left and right hand sides are equal. It says evaluate the right hand side, and store the result in the variable on the left. So now if X starts out with a value of 1, what does the statement X = X + 1 do? X + 1 has a value of 2, so we store that value in X. The right hand side of an assignment statement can sometimes get complicated. You're allowed to use parentheses and spaces to try to keep the meaning clear. For a complicated expression, you may also use temporary variables to store partial results. Compare the following versions of the same computation:
X=LENGTH**2+WIDTH*RATE*TIME/FACTOR X = LENGTH**2 + WIDTH * RATE * TIME / FACTOR X = Length**2 + (Width*Rate*Time)/Factor Area1 = Length**2 Distance = Rate*Time Area2 = Width*Distance Area2 = Area2/Factor X = Area1 + Area2
In most cases, you can mix Integer and Real quantities in formulas and assignment statements, and get the results you expect. For instance, the following are legal formulas:
Program Mix Integer I Integer J Real X Real Y I = 2 J = 3.5 X = 2 Y = 3.5 Write (*,*) 'I=', I, ' and X=', X I = 2.3 X = 2.3 Write (*,*) 'I=', I, ' and X=', X I = Y X = J Write (*,*) 'I=', I, ' and X=', X I = J X = Y Write (*,*) 'I=', I, ' and X=', X I = 2.3 * J * Y X = 2.3 * J * Y Write (*,*) 'I=', I, ' and X=', X Stop EndWe simply have to remember that, because I is an integer, it can only hold whole number values. So when we say I = 2.3, we take the result, 2.3, and try to store it into I. But only the whole number part gets in, so this ends up being the same as saying I = 2. But Fortran handles division of integers in an unusual way, which is NOT what you might expect. If you are dividing two integer quantities, Fortran will chop off the fraction part of the result, before looking to see where you want to store the result. In many cases, this is NOT what you want, particularly if you are assigning the result to a real value. For instance, if you want to compute five thirds, that is, 1.6666..., here's what NOT to do, assuming that X is a Real variable:
X = 5/3 (This will result in X=1.0!)Here, Fortran treats 2 and 3 as Integer quantities, since they don't have a decimal point, and it computes 5/3 as 1 rather than 1.6666... In order to avoid this division problem, the simplest procedure is to use a decimal point on constants. So the cure is:
X = 5.0/3.0 (This will result in X=1.6666....)The same problem happens when the quantities to be divided are variables:
I = 5 J = 3 X = I/J (This will result in X = 1.0)Here, you have to ask Fortran not to use Integer division by requesting that it temporarily treat I and J as Real values:
X = Real(I) / REAL(J) (This will result in X=1.6666....)
In order to calculate with our variables, we need operators and functions. We have already mentioned the arithmetic operators, which are available for all numeric data types:
+ Addition 2+17 X+Y A+B+C+D - Subtraction or negation Income-Expenses A-B-C -X * Multiplication 2*X Length*Area / Division I/J (X+Y)/Z ** Exponentiation 2**5 P**Q (X+1)**2Fortran also has many "built-in" or Intrinsic functions defined for numeric data. For example, Abs(X) represents the absolute value of X. In most cases, the same function name is available for use with all numeric data types. Some of the functions which are available include:
Abs(X) Absolute value of X. Cos(X) The cosine of X. Exp(X) The exponential function of X. Int(X) Makes an Integer copy of the Real number X. Int(1.9) is 1, for example. Max(X1,X2) The maximum of X1 and X2. Min(X1,X2) The minimum of X1 and X2. Mod(X1,X2) The remainder, when X1 is divided by X2. Mod(7,2) is 1, because 7 is odd. Mod(14.3, 3.0) is 2.3 because 14.3 = 3*4 + 2.3. Nint(X) Returns the nearest integer value. Nint(1.9) is 2, for example. Real(I) Make a Real copy of the Integer I. Sign(X1,X2) A value having the magnitude of X1, and the sign of X2. Sign(20.0, -7.0) is -20.0, for example. Sin(X) The sine of X. Sqrt(X) The square root of X. Tan(X) The tangent of X, sine(X)/cosine(X).
If you use an intrinsic function in a program, you may declare the function by using an Intrinsic statement, as in:
Intrinsic Abs
Many programs do not bother to declare their functions in this way, and it's not strictly necessary. However, making this sort of declaration can be helpful. For one thing, this will help you avoid errors that can be caused by name conflicts. For instance, you would not want to name a variable Abs, since this would confuse the compiler. By declaring all your variables, and your functions, you can easily spot such problems.
There are also six operators which can be used to test numeric data:X .Eq. Y True if X equals Y. X .Ne. Y True if X is not equal to Y. X .Lt. Y True if X is less than Y. X .Le. Y True if X is less than or equal to Y. X .Gt. Y True if X is greater than Y. X .Ge. Y True if X is greater than or equal to Y.When we do these comparisons, we end up with a Logical result, that is, something that is true or false. We can make more complicated expressions out of such results by using the Logical operators Not, And, and Or:
.Not. Condition True if "condition" is false, and vice versa. Condition1 .And. Condition2 True if "condition1" and "condition2" are both true. Condition1 .Or. condition2 True if "condition1" is true, or "condition2" is true, or both.For instance, suppose we want to do something if X is in the interval [0,1]. Then we need to say that X is greater than or equal to 0, AND less than or equal to 1. We just use the And operator to make sure both things are true:
If ( X .Ge. 0.0 .And. Z .Le. 1.0 ) Then (do stuff) End If
Do I = 1, 10 Write (*,*) 'Current estimate is ', Xroot Xroot = (Xroot + (X/Xroot) ) / 2.0 End DoIn general, the Do statement has the form:
Do index = 1, number statement 1 statement 2 ... last statement End DoThe statements between the Do statement and the corresponding End Do statement will be repeated Number times. Here are some examples of Do loops:
Program Count Integer Age Integer I Integer J Integer Number Integer Sum c Age = 12 Do I = 1, Age Write (*,*) 'Happy birthday to you!' End Do Sum = 0 Number = 0 Do J = 1, Age Number = Number + 1 Sum = Sum + Number End Do Write (*,*) 'You"ve blown out ', Sum ,' candles in your life!' Stop EndWhen you use a Do statement, you have to have an "index" or "counter". In the above example, this was a variable called I or J. As the Do loop is carried out, the index variable is actually set to the successive values specified in the Do statement. In other words, the "Happy Birthday" loop is actually equivalent to these statements:
I = 1 Write (*,*) 'Happy birthday to you!' I = 2 Write (*,*) 'Happy birthday to you!' ... I = Age Write (*,*) 'Happy birthday to you!'Because you can use the value of the loop index in formulas, can you see how you could simplify the second loop in the previous example, by eliminating the variable Number and replacing it with J? In some cases, you may want to check the value of the index variable and use that as part of your computations. Remember, for the following example, that the Mod function returns the remainder of the first number when divided by the second. Odd numbers have a remainder of 1 when divided by 2.
Do Kathy = 1, 10 If ( Mod ( Kathy, 2 ) .Eq. 0 ) Then Write(*,*)'Kathy is even.' Else Write(*,*)'Kathy is odd.' End If End Do
Sometimes you only want to carry out an operation in certain cases. For instance, you wouldn't want to take the square root of a number Y if you find out that Y is negative. So, instead of writing
X = Sqrt ( Y )you might prefer to write
If ( Y .Ge. 0.0 ) Then X = Sqrt ( Y ) End If
Now the program will first check to see whether Y is nonnegative. If this is true, the statements between the words Then and End If will be carried out. Otherwise, execution will jump to the statement following End If.
Now, suppose you want to do what we normally do if Y is nonnegative, but otherwise, print out a message warning the user. Then we need the If/Then/Else statement.If ( Y .Ge. 0.0 ) Then X = Sqrt ( Y ) Write (*,*) 'The Square Root of Y is ', X Else Write (*,*) 'Your number was negative!' Write (*,*) 'We cannot take the square root of a negative number.' End IfThe statements which are controlled by the If and Else statements may be any number of legal statements, including more If statements. Now let's look at a case where we want to check two conditions. Let's say we're given a number X, and we're going to plot the point (X, 1/X). Naturally, we have to check whether X is zero. However, because our vertical axes are only marked for the range [-100, 100], we'll also have to check whether X is so small that 1/X is too big to plot. Here, we will use the Else If statement:
10 Continue Write (*,*) 'Enter a value for X' Read (*,*) X If ( X .Eq. 0.0 ) Then Write (*,*) 'Sorry, we can"t compute 1/X!' Go to 10 Else If ( 1.0 / X .Gt. 100.0 .Or. 1.0 / X .Lt. -100.0 ) Then Write (*,*) 'Sorry, 1/X is too big to plot!' Go to 10 End IfNotice how we used the Or statement in the second check. We can stack up Else If statements as a way to choose one set of statements out of several groups. For instance, suppose you were counting up the money in the cash register at the end of the day, and you wanted to total the number of each denomination and the total amount of money. Let's suppose you type in the denomination of each bill to the program, except that when you type "0", that means the program should print out the total and stop.
program cash Integer Fives Integer Ones Integer Sum Integer Tens Integer Value Ones = 0 Fives = 0 Tens = 0 Sum = 0 10 Continue Write (*,*) 'Enter denomination (1, 5 or 10), or 0 to stop' Read (*,*) Value If ( Value .Eq. 1 ) Then Ones = Ones + 1 Sum = Sum + 1 Go to 10 Else If ( Value .Eq. 5 ) Then Fives = Fives + 1 Sum = Sum + 5 Go to 10 Else If ( Value .Eq. 10 ) Then Tens = Tens + 1 Sum = Sum + 10 Go to 10 Else Write (*,*) 'The current total is ', sum Stop End If End
We started out saying that a program operates by executing one statement, and then the next. But we saw thatDo and If statements can alter this process, by repeating some statements, or avoiding other. An even more powerful statement is available, the Go To statement which allows us to hop to any labeled statement in the program. Its form is
Go To 60where "60" (or some other number) is the label of a Continue statement somewhere else in the module:
60 ContinueIf the Go To statement is executed, it immediately jumps to the numbered statement, and picks up there. One reason for using Go To statements is that Fortran does not have a Do While statement. If you want to keep dividing a number by 2 until the number is between 2 and 1, you'd like to say
Do While ( X .Ge. 2.0 ) [ This is NOT legal Fortran! ] X = X / 2.0 End Dobut you'll have to write it this way instead:
10 Continue If ( X .Ge. 2.0 ) Then X = X / 2.0 Go To 10 End If
Program Log2 ! ! LOG2 is a program which computes the integer part of the ! logarithm base two of the number X. That is, LOG2 returns the ! number of times we can divide X by 2 before getting a value ! between 1 and 2. ! ! If X is less than 1, we return a negative value, meaning we had ! to multiply rather than divide. ! ! For example, LOG2(10)=3, because we can divide 10 by 2 just 3 ! times before getting a result between 1 and 2. ! Integer Logtwo Real X Write (*,*) 'This program computes the integer part of' Write (*,*) 'the logarithm base 2 of any positive number.' 10 Continue Write (*,*) 'Enter a number X, or 0 to stop:' Read (*,*) X Logtwo = 0 If ( X .Gt. 0.0 ) Then Go To 20 End If ! ! If the user gave us a negative number, ask for a better one! ! If ( X .Lt. 0.0 ) Then Write (*,*) 'LOG2 - Illegal input!' Write (*,*) 'The input value of X must be positive' Write (*,*) 'but your value was ', X Write (*,*) ' ' Write (*,*) 'Try again!' Go to 10 ! ! But if the user gave us 0, quit. ! Else Write (*,*) 'The program is stopping because you typed 0.' Stop End If ! ! Here is where we work on getting the logarithm of a positive number. ! 20 Continue If ( X .Ge. 1.0 .And. X .Lt. 2.0 ) Then Write (*,*) 'LOG2 of X is ', Logtwo Go to 10 Else ! ! If a number is bigger than 2, divide it in half once. ! If ( X .Ge. 2.0 ) Then X = X / 2.0 Logtwo = Logtwo + 1 Go To 20 ! ! If a number is smaller than 1, double it. ! Else If ( X .Lt .1.0 ) Then X = 2.0 * X Logtwo = Logtwo - 1 Go To 20 End If End If End
Notice how the first Go To statement is used to "skip ahead". If X is positive, we skip over some statements that print an error message and return.
The next two Go To statements are used to jump backwards, because they are intended to be used to repeat an action until some condition is true. In this case, we want to keep dividing or multiplying X by 2, until we get a value between 1 and 2. Notice that both of these Go To statements jump to the same statement 20. You don't have to have a separate "landing place" for each Go To. The last Go To leapfrogs backward over the entire program and starts again, asking the user for a new value. Now let's look over the If/Then/Else structure of the program.The If statements that we have are "nested"; that is, inside of the If statements are more {If statements. Here's the "outer" set:
If ( X .Ge. 1.0 .And. X .Lt. 2.0 ) Then ... Else ... End IfIn the first case, X is already in the range we want, but in the second case, we're going to have to divide or multiply. We don't know which until we check, and we do that with more If statements:
If ( X .Ge. 2.0 ) Then ... Else If ( X .Lt. 1.0 ) Then ... End IfIf X is greater than or equal to 2, it gets caught in the first "trap", is divided by 2, and sent back to statement 20 to be tested again to see if it's small enough yet. If X is less than 1, it gets multiplied by 2 and sent back to statement 20 to be tested to see if it's large enough yet. Finally, note that there is a potential flaw in Log2. Suppose we changed the final Write statement to
Write (*,*) 'LOG2 of ', X,' is ', LogtwoIf you give the program an input value of 10, the output will be:
LOG2 of 1.25 is 3when we would expect the program to print
LOG2 of 10 is 3
Of course, what's happening is that the Log2 program is modifying the value of X as it computes the logarithm, exactly as we told it to do. But we can easily forget that, and think that the variable called {X will keep the value we gave it initially. We could fix this problem by adding a new variable, called, say, Xcopy, which would save the input value so we can print it out later.
The Write statement prints out the value of one or more variables in the program. The Write statement has many forms, but we will concentrate on a particularly simple form, which always prints to the screen, and makes its own decisions about how many decimal places to use when printing real numbers.
You can use Write statements in your program to:Write (*,*) X Write (*,*) 'The cost is ', Cost,' and the rate is ', Rate Write (*,*) 'This is a message to be printed.' Write (*,*) 'Here is the value of Z ', Z Write (*,*) X1, X2, X3, Y1, Y2, Y3, Z1, Z2, Z3A Write statement of this form will print the value of the variables. Each Write statement may try to write everything in its list on one line. Sometimes, as in the last sample statement above, you can try to write too much on one line, and the result will be unsightly. If you need more control over the form of your output, you may want to look into the Format statement. Then you can specify the number of decimal points used, you can force tables of numbers to line up, and so on. We will try to avoid discussing the Format statement in these notes; it's one of the parts of Fortran you don't absolutely need in your first programs.
Some programs carry out a calculation from beginning to end without any help from the user. The more interesting and more useful "interactive" programs cooperate with a user. Depending on the program, the user may get to type in a single number, or perhaps lists of data to be sorted, or numbers to be added up, and so on.
In order to find out what the user wants, the program gets information that the user types on the keyboard by using the Read statement. The Read statement tells the program to expect to receive one or more items of information. In the cases we will look at, this information is typed by the user as the program waits. Advanced programmers can use alternate forms of the same Read statement, and have the program get its information from files instead. The Read statement can be very complicated, and we will try to avoid all those complications by teaching you a few very simple forms of the statement, such as:Read (*,*) Variable Read (*,*) X, Y, ZA Read statement expects the user to type one or several values, which will be assigned to the variable or variables on the list. You should always have a Write statement just before a Read statement, that will print out a message telling the user that the program needs some information. When the program expects you to give it several values, you can type those values with spaces or commas separating them, or you can even put them on separate lines. They must be given in the order specified in the list. You can Read a value into a variable whenever you like, and as often as you like. Whatever value the variable had before is "wiped out", and the new value is used.
Very often we have a set of data values that belong together, such as a table of temperatures, a list of grades, or the concentration of some chemical in groundwater at regularly spaced points. Now we could simply try to name each piece of information separately, say as "Grade1", "Grade2" and so on. But it will turn out that it's very hard to write good, flexible programs without having a more general way of handling lists of data.
An array allows us to group a set of data values under a single name, while retaining the ability to retrieve or change any individual value. An array is like other variables in most ways. In particular, an array has a name and a type, such as Real or Integer.
However, an array has two extra properties that give it its power: its rank and extent. The rank of an array tells us whether the data is arranged as a list, a table, or an even more complicated object. For now, let us only consider arrays of rank 1, sometimes called "vectors", "lists", or "one dimensional arrays". The extent of the array is then roughly speaking just how many entries it can hold, or in other words its size.
The extent of an array is specified in the declaration statement. Here are some simple declarations for arrays:
Real X(100) Integer Grade(20) Integer Kids(15)The declaration of X tells us that it is going to be used to store a list of up to 100 separate Real values. These entries will be numbered from 1 to 100.
In some cases, it may not be convenient to start counting the array entries at 1. For instance, I might be sorting families by the number of children they have. I might set up an array called Kids to do this. I would want to store the number of families who have 5 children in entry 5 of Kids. But since some families have no kids, I'd need to store a value in entry 0 of Kids. I can't do that the declaration I used above, sets aside entries 1 through 15, but not 0. In order to get an entry numbered zero, I'd have to use the following declaration:
Integer Kids(0:15)in which case I'll have entries labeled 0 through 15. Similar declarations can be used to change the range of the entry labels to, say, all the integers from 4 to 15. Once we've declared an array, we need to know how to store values in it, change them, and get them out. Well, it turns out to be easy. All we have to do is specify which entry we want to work with, which we do by adding an index to the name. For instance, the seventh entry in the Grade array can be accessed by writing Grade(7). To add 1 to it, we simply write:
Grade(7) = Grade(7) + 1To get the average of the first three entries in Grade, we would type:
Average = ( Grade(1) + Grade(2) + Grade(3) ) / 3.0
Now that we have arrays, we can suddenly create hundreds or thousands of data items with a single declaration statement. But won't it take thousands of statements to set all the entries to some initial value? We will see that the Do statement will allow us to replace the ridiculous statements:
X(1) = 1.0 X(2) = 2.0 ... X(100) = 100.0by the terse
Do I = 1, 100 X(I) = Real ( I ) End DoSimilarly, we can easily write calculations involving array elements if we can express them using indices:
Average = 0.0 Do I = 1, N Average = Average + X(I) End Do Average = Average / Real ( N )
We've agreed that one way to set all the entries of an array of 100 elements to zero would be to type 100 assigment statements, but who wants to write a program like that? The right thing to do is use the Do statement, and use the counter variable to specify which entry of the array to zero out next:
Do I = 1, 100 X(I) = 0 End DoNow suppose that we had wanted, instead, to store the first 100 odd numbers into X? Then we'd just have to use the counter variable I in a formula on the right hand side as well:
Do I = 1, 100 X(I) = 2*I + 1 End DoSimilar loops can be used to copy one array into another:
Do I = 1, 100 X(I) = Y(I) End Door to add a multiple of one array to another:
Do I = 1, 100 X(I) = X(I) + 2 * Y(I) End Door to compute the "dot product" of two vectors, that is, the sum of the products of pairs of corresponding entries:
Dot = 0 Do I = 1, 100 Dot = Dot + X(I) * Y(I) End doBy the way, anything a Do statement can do can also be done by a Go To statement. For instance, we could replace the previous example by:
I = 1 Dot = 0 60 Continue If ( I .Le. 100 ) Then Dot = Dot + X(I) * Y(I) I = I + 1 Go to 60 End If
Integer Grade(40,30) Real Chemical(10,5)The declaration of Chemical tells us that we are setting aside space for a total of 50 values. The fact that we have two numbers in the parentheses tells us that Chemical is a "table" or ``matrix'', with rows and columns. Entries in a two dimensional array can be changed or read the way one dimensional arrays can be, as long as we specify both indices. To set the value in the 6-th row and first column of Chemical, we might type something like
Chemical(6,1) = 77.0We used a Do loop to access all the entries of a one dimensional array. To do the same thing with a two dimensional array, we will need a pair of Do loops, one inside the other. You must also use different names for the two different Do loop indices:
Program Table Integer I Integer J Integer X(3,4) ! ! Set the values of the array. ! Do I = 1, 3 Do J = 1, 4 X(I,J) = 10*I + J End Do End Do ! ! Print the values of the array. ! Do I = 1, 3 Write (*,*) (X(I,J), J = 1, 4) End Do Stop Endwhich will result in storing the following values into X:
11 12 13 14 21 22 23 24 31 32 33 34
If you just want to read or write one or two entries of an array, or table, you can use Read and Write statements in the way we've described earlier. But let's look at what you need to do if you want to print out the whole array or table.
For a one dimensional array, here are some examples:
Write (*,*) 'The value of X(2) is ', X(2) Write (*,*) X(1) Write (*,*) X Write (*,*) (X(I), I=1, 10)
The first two statements are nothing new. They simply print out a single value from the array. But the third statement is actually a request to print out every entry in the array. Since we aren't specifying how this should be done, we may not be satisfied with the way the numbers are printed out, but this is an easy way to get all the data printed quickly.
The fourth statement requests that we just print entries 1 through 10 of the array. Notice how we have to use a variable, I, as the index of X, followed by the lower and upper limits that I can take. This structure is similar to the way a Do loop is set up, and in fact, is called an implicit Do loop.
If you wanted to print all the entries of an array, but wanted just one number per line, you could use a regular Do loop like this:
Do I = 1, N Write (*,*) X(I) End Do
For two dimensional arrays, the situation is similar. We can print a single value, a row or column, a range of values, or the whole table, using statements like:
Write (*,*) 'The last entry of Grade is ', Grade(40,30) Write (*,*) 'Grades for test # 3 were ', (Grade(I,3), I=1, 40) Write (*,*) 'John"s grades were ', (Grade(17,J), J=1, 30) Write (*,*) 'Partial grades: ', ((Grade(I,J), J=1, 5), I=1, 10 ) Write (*,*) 'Entire grade array ', GradeIt's dangerous to try to dump out a two dimensional array without printing any other information, since it's hard to tell where one column ends and the next begins. If we print out using a double Do loop, we can print out the row and column information as well, and make the data a little easier to read:
Do I = 1, 40 Do J = 1, 30 Write (*,*) I, J, Grade(I,J) End Do End DoRead statements can also be used with lists and tables, and they have to be changed in exactly the same ways that Write statements are changed, so we won't discuss them further here!
Program Vector ! ! This program demonstrates some simple operations with vectors. ! A vector is simply a set or list of data, stored under a single ! variable name. Each item in the list can be accessed by ! specifying its "index". ! Integer I, Jumble(10), Product, Sofar(10) ! ! Here's how a DO loop may be used to move through a vector. In ! this case, we would like to assign a value to each entry of the ! vector, and we do this by specifying a "general" entry ! Jumble(I), where I is to go from 1 to 10. Our formula, 2*I-1, ! will actually store successive odd numbers in the Jumble vector. ! Do I = 1, 10 Jumble(I) = 2*I - 1 End Do ! ! You can print out a short array this way: ! Write (*,*) 'Here"s what happens when we print Jumble using' Write (*,*) 'the statement Write (*,*) Jumble:' Write (*,*) ' ' Write (*,*) Jumble ! ! You can print out an array one entry at a time using a DO loop: ! Write(*,*)' ' Write(*,*)'Here"s what happens when we print Jumble using' Write(*,*)'the statement Write (*,*) Jumble(I):' Write(*,*)' ' Do I = 1, 10 Write (*,*) Jumble(I) End Do ! ! You can use each entry in a vector, one entry at a time, using ! a DO loop. ! Product = 1 Do I = 1, 10 Product = Product * Jumble(I) End Do Write(*,*) 'The product of all the entries is ', Product ! ! What happens if we add up entries of Jumble? Let's store the ! first entry, then the sum of the first and second, then the sum ! of the first, second and third, and so on. We'll save the ! values in another vector. ! Sum=0 Do I = 1, 10 Sum = Sum + Jumble(I) Sofar(I) = Sum End Do ! ! Let's print the sums out now. ! Write(*,*)' ' Do I = 1, 10 Write(*,*)'The sum of the first ',I,' entries is ', Sofar(I) End Do Stop End
The Parameter statement is used to define the value of a constant. The only difference between a constant and a variable is, naturally, that a constant is not allowed to be changed. To define a constant, you should simply follow the declaration of the type of the variable by a statement that assigns it its permanent value, as in:
Real Pi Parameter ( Pi = 3.14159265 )You can then use Pi just like a variable, as long as you don't try to change its value. Actually, the most useful feature of the Parameter statement is for declaring the size of an array. Fortran requires you to specify the size of an array as a constant, but in many cases, it would be convenient to declare it as a variable, or at least as a symbolic constant. That way, it's much easier to change the declaration if I want to work with larger arrays, for instance. Also, many times, there are several arrays which all share the same size. Storing that size as a constant makes it possible to redimension all the arrays with a single change. For instance, here is part of the declarations of arrays in a program that is going to set up and solve a linear system of maximum size 10:
Real A(10,10) Integer Pivot(10) Real Rhs(10) Real Sol(10)If I want to increase the problem size to 20, I have to change each 10 separately. If I use a Parameter statement, I can do this in a much cleaner way:
Integer Maxn Parameter (Maxn=10) Real A(Maxn,Maxn) Integer Pivot(Maxn) Real Rhs(Maxn) Real Sol(Maxn)
A program that is large or complicated can be simplified by using a subroutines. A subroutine is a "mini-program", which is used by the main program to carry out some specific part of the overall task. There is an almost identical concept, called a Function, which has a few special features not used in subroutines. We will discuss them shortly.
The most common reasons for using subroutines are:Program Topcat Write (*,*) 'This is Topcat speaking! Is Garfield home?' Call Garfield Write (*,*) 'This is Topcat again. Is Underdog around?' Call Underdog Write (*,*) 'This is Topcat again. I"m ready for a nap!' Stop End Subroutine Garfield Write (*,*) 'Garfield speaking. I"m in charge now!' Call Underdog Write (*,*) 'This is Garfield, and I"m stepping down now!' Return End Subroutine Underdog Write (*,*) 'Underdog here! Leave me alone!' Return EndExecution of this program begins in the main Topcat program. The main program carries out its first task, the Write statement, and then calls Garfield. Topcat must now wait until Garfield is done before proceeding to the next statement. Garfield now has control. It prints out a message and calls Underdog. Again, Garfield must wait while Underdog does its work, which is simply to print out a complaint. Then control passes back to Garfield, and shortly thereafter to Topcat, which has been waiting all this time! Topcat then prints another message, and calls Underdog directly, and then finishes up. This process, in which one portion of a program pauses and lets another subroutine take over for a while, is called the transfer of control, or transfer of execution. Using subroutines is another way to interrupt the normal transfer of control, which is from one statement to the next consecutive statement.
In the Topcat example, there was no "communication" between the main program and the subroutines. To get a real benefit from subroutines, we will need to be able to set up some sort of communication between the calling program and the subroutine. This will allow the program to "send" the subroutine numbers or other data, and the subroutine to "return" the results of its operations on that data.
The method that is used to pass information to subroutines involves what is called an "argument list". An argument list is a list of variable names, surrounded by parentheses. When a program calls a subroutine, the names used in the argument list of the Call statement will specify what information the program wants to send to the subroutine. A Call statement with arguments looks something like this:Call Fred(X, Y, Bananas)Here X, Y and Bananas are names of variables used in the main program, whose values are to be sent to subroutine Fred. The subroutine also has an argument list, and this list specifies the names that the subroutine will use to refer to the same variables. The subroutine lists its arguments using a Subroutine statement, such as:
Subroutine Fred(A, B, Apples)Here A, B, and Apples are names of variables which are used in subroutine Fred. In order for the main program to use a subroutine, it must set up a Call statement that matches the Subroutine statement. The number and type of the variables must be the same, but there is no need to use the same names. In effect, variables may get temporarily "renamed" when they are sent to the subroutine.
Program Bill Real Tea, Coffee, Water, Chicken Real Beef, Cake, Pie, Icecream Real Price1, Price2 ! ! Set the prices for food. ! Tea = .50 Coffee = .45 Water = 0.0 Chicken = 1.75 Beef = 2.50 Cake = 1.50 Pie = 1.25 Icecream = 1.75 Call Waiter ( Tea, Chicken, Pie, Price1 ) Write (*,*) 'First order will cost ', Price1 Call Waiter ( Water, Beef, Cake, Price2 ) Write (*,*) 'Second order will cost ', Price2 Stop End Subroutine Waiter ( Item1, Item2, Item3, Cost ) Real Item1, Item2, Item3 Real Total, Tip, Cost Total = Item1 + Item2 + Item3 Tip = 0.15 * Total Cost = Total + Tip Return End
The Waiter subroutine looks like a program, but we can tell that it is not a program. There are the obvious differences of the Subroutine and Return statements, and the variables Item1, Item2, and Item3 are not given initial values in Waiter itself. Moreover, Waiter never prints out any results, which means it would be a pretty useless program by itself!
So Waiter is just a portion of a computation. The initialization of the data must be done elsewhere, and the results must be used or printed elsewhere. The program that calls Waiter is responsible for defining the data, and using the results.
Now notice how the main program, Bill, calls Waiter two times. In both cases, the names that Bill uses for the variables are not the same as what Waiter calls them! However, we can understand what's going on, as long as we make the following correspondence:What Bill calls: has the following name in Waiter Tea Water Item1 Chicken Beef Item2 Pie Cake Item3 Price1 Price2 Cost
Notice that the names may be different, but that the corresponding variables Tea and Water and Item1 have the same type, Real in this case. Subroutine argument names don't matter, but the position in the argument list, and their values and types do matter.
Notice also that we have established two-way communication here. The first three arguments are "input" quantities to Waiter, but the final one is "output", that is, Waiter sets the value of Cost, or modifies it. So it really is possible to send raw data to a subroutine and have the finished results returned. Finally, note that a subroutine like Waiter is in many respects a "closed off" world. The only communication with the outside world is done through the Subroutine statement. But variables like Tip and Total, which do not appear in the argument list of the Subroutine statement, are completely private. The Bill program cannot alter them, or print them out, or use them in a formula.Statement labels are completely private to a subroutine. The main program can have a statement labeled 10, and Waiter can have a different statement labeled 10, without any conflict. For the same reason, if the main program has a statement labeled 10, the Waiter program can not jump there with a Go To statement.
The subroutine requires a declaration for the variable, just as for any variable. Since it's an array, we'll also need to use some parentheses in the declaration, with a size indicator inside. But exactly how we indicate the size of the array depends on how the subroutine is to be used.
Subroutine Barney(Vec, Table) Real Vec(10), Table(50,10)
Subroutine Fred(Vec, Nvec, Table, Nrow, Ncol) Integer Nvec Real Vec(Nvec) Integer Nrow, Ncol Real Table(Nrow, Ncol)
Using the second option will make our calls to subroutines more lengthy, and it will introduce another opportunity for mistake, but it allows us to write a very general purpose subroutine! For instance, it means we can write a subroutine that will add up the entries of any Real vector:
Subroutine Vsum ( N, X, Sum ) Integer N Integer I Real Sum Real X(N) Sum = 0.0 Do I = 1, N Sum = Sum + X(I) End Do Return End
Program Vmax ! ! This program demonstrates how vectors of different length may be ! sent to the same subroutine. The subroutine simply needs to ! have the additional information about the length of the vector. ! Integer I Integer Imax Integer N1 Integer N2 Real Value Real Vec1(5) Real Vec2(10) Write (*,*) ' ' Write (*,*) 'Here is vector 1:' Write (*,*) ' ' N1 = 5 Do I = 1, 5 Vec1(I) = I*I - 7*I + 20 Write (*,*) I, Vec1(I) End Do Call Vecmax ( Vec1, N1, Imax, Value ) Write (*,*) 'Maximum is ', Value, ' in entry ', Imax Write (*,*) ' ' Write (*,*) 'Here is vector 2:' Write (*,*) ' ' N2 = 8 Do I = 1, 8 Vec2(I) = -I*I + 5*I + 24 Write (*,*) I, Vec2(I) End Do Call Vecmax ( Vec2, N2, Imax, Value ) Write (*,*) 'Maximum is ', Value,' in entry ', Imax Stop End Subroutine Vecmax ( Vec, N, Imax, Vmax ) ! ! Vecmax accepts a vector Vec of N items. It finds Imax, the ! location of the largest item, and Vmax, the value of the largest item. ! Integer N Integer I Integer Imax Real Vec(N) Real Vmax Imax = 1 Vmax = Vec(1) Do I = 2, N If ( Vec(I) .Gt. Vmax ) Then Imax = I Vmax = Vec(I) End If End Do Return End
A function is typically a formula that returns a predictable result based on input it receives. We have already seen that Fortran has functions, since we've talked about the "intrinsic" mathematical functions like Abs and Sqrt.
If you have any formulas that are used repeatedly in your computation, you may find it worthwhile to try framing those formulas in terms of a Fortran Function. A Function that is written by the programmer is called an external function, and the keyword External should be used to declare the Function in any module where it is used.
What are the differences between a Function and a Subroutine? Let's first look at the differences from the inside, that is, the differences in how a Function of Subroutine is written. Suppose we want to compute the area of a box using its length and width. We could write either:
Subroutine Box ( Length, Width, Area ) Real Area Real Length Real Width Area = Length * Width Return Endor:
Function Area ( Length, Width ) Real Area Real Length Real Width Area = Length * Width Return End
The most surprising thing is how similar the two codes are! But let's look at the differences, which all have to do with the way the name Area is used. In the subroutine, Area is just another variable, which happens to be an output quantity. The subroutine has a name, "Box", which was just made up, and isn't used anywhere within the body of the subroutine.
On the other hand, the function is named Area, declares the type of Area, and computes Area based on its input quantities. Notice that Area is NOT a variable in the argument list, where you would normally expect it to be for a subroutine.
This is the unique feature of a Function: There is a special variable, whose value is to be computed by the Function, and whose name is the name of the Function. Instead of passing a list of inputs and outputs back and forth, the Function receives inputs in the argument list, and returns a single output, as the value of its name.
Now let's compare how functions and subroutines appear from the outside, that is, when a program uses them. We can ask them both to compute the area of a box, using the following code:
Program Funsub Real Area Real Area1 Real Area2 Real Length Real Width External Area Length = 2.0 Width = 5.3 Write (*,*) 'Length=', Length, ' Width=', Width Call Box ( Length, Width, Area1 ) Write (*,*) 'Box computes the area as ', Area1 Area2 = Area ( Length, Width ) Write (*,*) 'Area computes the area as ', Area2 Stop End
We see that in both cases, we pass information to the function or subroutine in the same way, through an argument list. But we get information out of the function in a new way, by using the name of the function as though it were a variable, whose value could be used in formulas or assigned to another variable.
The game of Life is played on an imaginary rectangular checkerboard of some given size. The game begins with some squares of the checkerboard containing a single marker, and the other squares empty. At each step of the game, we replace the current pattern on the checkerboard by a new pattern, adding or removing markers according to a set of simple rules. Each box or cell can be empty, or have just one marker in it. The rules tell us which markers to add or delete, and we say a marker has died, or a new one has been born.
The rules for making the next pattern from the current one are: Look at each cell individually. Examine the cell's eight immediate neighbors:NW | N | NE ---+---+--- W | | E ---+---+--- SW | S | SE
+---+---+---+---+ | X | | X | | +---+---+---+---+ | | X | | | +---+---+---+---+ The starting pattern | | | | X | +---+---+---+---+ | | | | X | +---+---+---+---+ +---+---+---+---+ | | X | | | +---+---+---+---+ | | X | X | | +---+---+---+---+ Step 1. Only one marker survives from the | | | X | | original pattern. +---+---+---+---+ | | | | | +---+---+---+---+ +---+---+---+---+ | | X | X | | +---+---+---+---+ | | X | X | | +---+---+---+---+ Step 2. No markers die. Two are born. | | X | X | | +---+---+---+---+ | | | | | +---+---+---+---+
Program Life ! ! On a 10 by 10 board, place 0's and 1's randomly, leaving the ! border 0. 1 is a live cell, 0 a dead cell. On each step, a ! living cell "dies" if it has 0, or 1, or more than 3 living ! cell neighbors. On each step, an dead cell comes alive if it ! has exactly 3 living cell neighbors. ! Integer Nrow Integer Ncol Parameter ( Nrow = 10 ) Parameter ( Ncol = 10 ) Integer Cell(Nrow,Ncol) Integer I Integer Istep Integer Old(Nrow,Ncol) Call Init ( Cell, Old, Nrow, Ncol ) Istep = 0 Call Display ( Cell, Istep, Nrow, Ncol ) Do I = 1, 5 Call Compute ( Cell, Old, Nrow, Ncol ) Istep = I Call Display ( Cell, Istep, Nrow, Ncol ) End Do Stop End Subroutine Init ( Cell, Old, Nrow, Ncol ) ! ! Initialize the values in Cell to random 0 and 1 values. ! Make sure the boundary is 0. ! Integer Nrow Integer Ncol Integer Cell(Nrow,Ncol) Integer I Integer Iseed Integer J Integer Old(Nrow,Ncol) Real Random External Random Iseed = 1993 Do I = 1, Nrow Do J = 1, Ncol If ( I .Eq. 1 .Or. I .Eq. Nrow .Or. & J .Eq. 1 .Or. J .Eq .Ncol ) Then Cell(I,J) = 0 Else Cell(I,J) = Nint ( Random ( Iseed ) ) End If Old(I,J) = Cell(I,J) End Do End Do Return End Subroutine Compute ( Cell, Old, Nrow, Ncol ) ! ! Update the cell information for the next step. ! Integer Nrow Integer Ncol Integer Cell(Nrow,Ncol) Integer I Integer J Integer Newval Integer Old(Nrow,Ncol) Do I = 1, Nrow Do J = 1, Ncol Old(I,J) = Cell(I,J) End Do End Do Do I = 1, Nrow Do J = 1, Ncol If ( I .Eq. 1 .Or. I .Eq. Nrow .Or. & J .Eq. 1 .Or. J .Eq. Ncol ) Then Newval=0 Else Nabors = Old(I-1,J-1)+Old(I-1,J ) + Old(I-1,J+1) + & Old(I, J-1) + Old(I, J+1) + & Old(I+1,J-1)+Old(I+1,J ) + Old(I+1,J+1) If ( Nabors .Le. 1 ) Then Newval = 0 Else If ( Nabors .Eq. 2 ) Then Newval = Old(I,J) Else If ( Nabors .Eq. 3 ) Then Newval = 1 Else Newval = 0 End If End If Cell(I,J) = Newval End Do End Do Return End Subroutine Display ( Cell, Istep, Nrow, Ncol ) ! ! Display prints out the cell information. ! Integer Nrow Integer Ncol Integer Cell(Nrow,Ncol) Integer I Integer Istep Write (*,*) ' ' Write (*,*) 'Step ',Istep Write (*,*) ' ' Do I = 1, Nrow Write (*,'(10i1)') ( Cell(I,J), J = 1, Ncol ) End Do Return End Function Random ( Iseed ) ! ! Random returns a "random" value. Before the first call to ! Random, set Iseed to some large, nonzero value. ! Integer Iseed Real Random Intrinsic Real Iseed = Iseed * 125 Iseed = Iseed - ( Iseed / 2796203 ) * 2796203 Random = Real ( Iseed ) / 2796203.0 Return End
Notice that the actual main program is very skimpy! It simply declares some arrays and variables, and "orchestrates" the computation by calling subroutines.
Notice the "travels" of the Cell array. It is declared in the main program, initialized in Init, updated by Compute, and printed by Display. It really gets around! And each of the subroutines can be defined by what it does to the Cell array.
For the Cell array, we chose to represent the absence of a marker by the value 0, and the presence of a marker by the value 1. This made it very easy to compute the number of "living" neighbors of a given cell by simply adding up their values. If we had chosen different values (say 1 for no marker, and 2 for a marker), we would have had a more complicated time figuring out the number of neighbors.
To keep things simple, we also added a boundary layer of cells that were always zero, and then we only updated the 8 by 8 inner block of cells. This allowed us to use the same simple formula for the number of neighbors in all cases. When you add the neighbors up in your head, you automatically adjust for the special cases that occur along the boundary. But to keep the program simple, we had to make the data a little more complicated.
Note also that we had to have an Old array, to store the current value of the pattern. This is because the Game of Life needs a copy of all the old values around until it is completely done determining the new values.
Finally, the Write statement in subroutine Display
Write (*,'(10i1)') (Cell(I,J), J=1, Ncol)is an example of a formatted Write statement. We are requesting that the data be printed out as rows of Integer values, each taking up just one space, and we are asking that up to 10 values be printed on one line.
Normally, we would use the simpler line:
Write (*,*) (Cell(I,J), J=1, Ncol)but then the information is printed out in a way that's hard to read. If you want to have more control over your Write statements, then it's time for you to look up the Format statement!
You can return to the HTML web page.