Subprograms


Why does Fortran require both a "stop" and "end" or a "return" and an "end" back to back? That seems redundant.

Actually, the use of STOP and RETURN in this case is an old and admittedly redundant programming practice. As we get into the use of IF and related statements you will see the potential for using STOP and RETURN on their own. The Fortran 77 and 90 standards state that if an END statement is reached in a main program it will terminate execution (same as STOP), and if an END is reached in a subprogram, it has the same effect as a RETURN.

What is the difference between a Subprogram and a Subroutine.

A subroutine is one type of a subprogram. A Function is another, and a BLOCKDATA routine is a third.

Subprograms. What do they do and how do the help program.

They do anything that the main program can do. They help you organize your total program (main program + subprograms) by grouping specific tasks in a well defined location. They can save you repeating similar program structures at several places in your code. Also, for many tasks someone has already written a subprogram to do the job and you save a lot of work by picking up their subprogram and plugging it into your work. You will see an example of this when we start solving systems of linear equations (say 10 equations and 10 unknowns). You will define the equations, then let somebody else's subprogram solve them.

Please explain Function Subprograms.

What you have so far is just meant as an introduction. I'll be talking about them several more times during the semester. In the mean time, play with my example in newton3.f . It will get you safely through the homework. As an exercise try adding a second function at the end of the file called FUNCTION G(x), that sets G(x)=x4-2x2+1, and add lines in the main program to evaluate G(0), G(1), G(2) and print these values.

I am still unsure exactly how to put these functions and subroutine to good use. What exactly do they do and why are the better than a normal straight-out program?

First functions. The intrinsic "sin" function is a good example of why to use a function. There are many times that I want to use the sine of some angle within a larger expression. For example the height at time "t" of a shell fired with initial speed, v0, and elevation angle, angrad, can be computed with the assignment statement:

      height = v0*sin(angrad) - 0.5*g*t**2
The use of the function in this line is a natural match with what I would write as an equation of motion, and saves me preceding the evaluation of height with the many Fortran lines needed to generate a good approximation to the sine of angrad. Somebody else worried about the programming to get sin(angrad). This function is special only in that it used more often than most others. If I'm working with a heat conduction problem, I might have a statement requiring the use of a zero order Bessel function.

      T= 300.+10.*exp(5.*time)*Bess0(4.*r)
Fortran doesn't provide Bessel functions in its set of intrinsic functions, but it lets me write my own.

      function bess0(x)
.
.    (a few hundred boring lines here that you don't want to see)
.
      return
      end
The nicest part of this is that I'm going to use this Bessel function many times in a given program, and use it in a lot of programs over the years. One set of Fortran Statements generates the values I need anywhere in the program. In new programs, all I have to do is to include a reference to the function in any appropriate Fortran statement, and include a copy of the Bessel function subprogram taken from one of my older programs (or my personal library of useful functions and subroutines). New work is minimal.

Look at htcoef1.f for a simple example of a function. More will follow during the semester.

The subroutine is appropriate when I'm doing things that don't naturally produce a number to be used in the middle of some formula. The best reason for the subroutine is when this group of statements will be used many times by your program (see htcoef2.f). However, it is also a good idea to group lines that perform a well defined task into a subroutine, even if they are used just once. It adds to the clarity of your program, helping to create an outline structure. You will gain further appreciation for the value of subprograms as the semester goes on.

In my main program, the argument list for a function is "(x,dfdx), and in the function it is "(x,deriv)". What happens if I use the variable "deriv" from the function in the main program? Will it have the same value as dfdx or will it give me an error?

Here's a sample of what I think you're asking

      program test
      implicit none
      real x,y,dfdx,deriv,f
      x=10.
      y=f(x,dfdx)
      print *, ' x = ',x,'  y = ',y, '  dfdx = ', dfdx,
     1  '  deriv = ',deriv
      stop
      end
      function f ( x, deriv)
      implicit none
      real x,dfdx,deriv,f
      deriv = 2*x
      f = x**2
      return
      end
If you compile and execute this program you will usually get no compiler error messages a result of:

x = 10.0000 y = 100.000 dfdx = 20.0000 deriv = 0.00000E+00 What has happened is that the variable named "deriv" in the main program is totally unrelated to "deriv" in the function "f". Because no value has been assigned to "derive" in the main program. It simply contains the value of 0.0 that was loaded into it's location in memory when the program was created. By the way, you should not rely on zeros being loaded into variables if you don't tell the computer anything else. The variables "dfdx" in the main program and "deriv" in "f". are related. Because they occupy the same position in the argument list of the function and the reference to the function.

Could you go over exactly what DMIN means?

Actually you are dealing with DMIN1 and DMIN0 functions that are analogs to AMIN1 and AMIN0 functions. The "D" says that you are returning a DOUBLE PRECISION value, and for real arguments (DMIN1), they are DOUBLE PRECISION. The best bet continues to be the generic MIN function, which will sort out the needed types.

If you attempt something like MATMUL(A(2,1),B(2,1)) will it give you an error message, or do you need to build in diagnostics?

Yes, that is a Fortran syntax error. "matmul" wants arguments that are simply names of whole arrays. You have given it 2 elements of arrays as arguments.

What is the need for functions, when subroutines can do everything?

Convenience in arithmetic expressions. " y = 2*sin(x)+x**2" is easier and closer to our experience in mathematics than

      call sin (x,sinval)
      y=2*sinval+x**2
Are subroutines compiled as separate programs?

In effect yes. The compiler processes each program unit (main program, subroutine, function) alone, without thinking about the other units. Without telling you, it passes along its results to another program called a loader or linker that takes care of the final details of adjusting address references so the main program and subprograms can all talk to each other. Later in the semester, I will show you an example where I compile some subroutines by themselves, without creating an executable program. In a separate operation I will compile the main program and link it to the results of the earlier subroutine compilation.

External means you want to use a variable in the call statement for a subroutine as a function, right?

Basically yes, but the "variable" in the call statement must be the name of a function that you have created somewhere else in your program. In the Subroutine statement, the corresponding argument can have any name, but must be also declared in an EXTERNAL statement so it is recognized as a function.

I missed what an INTRINSIC statement is. What does it do?

An INTRINSIC statement makes a list of intrinsic functions (e.g. sin, cos, exp) that you want to pass as arguments to subroutines, or functions.

Why do you pass an intrinsic function as an argument to a subroutine? Why can't you just call it within your subroutine?

Normally you do just call the intrinsic function within your subroutine. The only time you pass a function name through an argument is when you are trying to improve the flexibility of your subroutine. Look at plot2.f for a crude example of this. You may need a little more math to understand my next example. I have at times needed to generate "analytic" series solutions to heat conduction problems. In Cartesian coordinates this involves a Fourier series sum of trigonometric functions. In Cylindrical coordinates the series is in multiples of Bessel functions. For common boundary conditions, the mechanics of generating the solution is the basically the same. I can create a single subroutine to produce solutions for both Cartesian and Cylindrical coordinates, and let the argument list pass the appropriate functions for the desired coordinate system.

Why doesn't Fortran have intrinsic functions for something as simple as factorial?

Two reasons. Factorial isn't all that common in heavy duty scientific and engineering applications. When it does occur, it almost always in a context where it is more computationally efficient to generate it as you go. You need 2! first then 3!, then 4!, etc. You are basically stuck doing a factorial within the context of a do loop unless you get really good and learn to write "recursive functions", but then you are just fooling yourself and writing another form of do loop. When you are taking the factorial of a large number and don't need an exact answer you can resort to Stirling's Approximation. A Fortran statement that will load the value of this approximation into the variable nfact is:

	nfact = sqrt(2*3.1415963*n)*(n/2.71828)**n 
by 20! this has a 0.4% error. Try larger numbers on the machine for a better feeling of the approximation.

What does the function REAL(x) do?

Not much if x is already a real variable. If x is and integer, the output of REAL is a real (floating point number with the same value. REAL(3) is 3.00000E+00. If x is a complex number REAL returns the real part (as opposed to the imaginary part) of x.

I am still unclear with Subprograms. Could you give a summary.

The purpose of Subprograms (Functions and Subroutines) is to isolate certain well defined calculational (or I/O) tasks in a special location. This permits easy use of these lines of programming at any point in a large program. This has the side-effect of minimizing errors in your program. Rather than entering the same 20 lines of programming at 10 different places in a large program, and introducing typos in some of these locations, I put it all in one location limiting the area I must search if an error exists. If I later decide to do this specific task in a different way, replacement of the old coding is much simpler. In addition the total program becomes much easier to understand if there is just one point where viscosity is calculated, one point where enthalpy is calculated, etc.

Functions and Subroutines are very isolated from each other and from the main program. You start by assuming that a given subprogram knows nothing about what is going on elsewhere, that variable names do not necessarily have the same meaning as they do elsewhere. You pass information into and out from subprograms via the argument list. Say that in the main program you have the line:

	z=funxy(x,y)
and later we define the function as

	function funxy(a,b)
	real a,b,funxy
	funxy=(a+b)**2
	return
	end 
When the line defining z is reached, the program makes a note of were the values that we have labeled "x" and "y" are located, and where in memory it wants the value called "funxy" tucked away. It usually puts this note in a memory location near the start of the instructions to evaluate funxy then branches to these instructions. Although funxy was written in terms of variables called "a" and "b", it checks the note from the calling program (calling program could be the main program or another subprogram) and associates "a" with the contents of the memory location named "x" in the calling program and "b" with the contents of the memory location named "y". The numbers in these locations are summed, the sum is squared, and the result is tucked away in the memory location reserved to contain the results from "funxy".

What are the relative advantages of Function Subprograms, and Statement Functions?

The Function Subprogram should be your choice when evaluation of the function takes more than one line of Fortran or the function is referenced from many places in your program, and size of your executable program is important. I am including liberal use of the line continuation option (character in column 6) in my definition of "one line of Fortran". It is not difficult to generate functions that need some IF tests to cover special cases or error processing (see viscl.f). These must be Function Subprograms. As for the space issue, you need to understand how each of these function types are implemented in machine code. The machine instructions for a Function Subprogram exist at only one location in the final executable code. When the Function Subprogram is referenced somewhere, some notes are made about the location of information and the location where program execution will continue after return from the function, then the flow of instruction execution branches to the Function Subprogram's code. All of this takes some extra computer time beyond what would be needed if the Function's programming was just imbedded in the rest of the code, but generally takes less space.

When a statement function is referenced an interesting thing happens at compilation time. The statement function basically disappears from the final executable machine code. If I write the following Fortran, with the first line as a statement function:

	g(x,a,b)=a*x+b
	x=1.0
	y=2.0
	z=g(x,2.,3.)+g(y,4.,1.)
then what is effectively generated after the compiler is through is the machine code equivalent to:

	x=1.0
	y=1.0
	z=2.*x+3.+4.*y+1.
No branches are generated to statement function coding, the statement function is substituted into the locations where it is referenced. This results in code that takes less time to execute than code using an equivalent Function Subprogram, but when used too frequently with long functions can produce longer executable files than you want.

What is the difference between a Function Subprogram and a Subroutine.

Some of the comments in the textbook are misleading on this subject. There are really only two differences. The biggest difference is that a subroutine never returns a value that is associated with its name. This means that you never need to declare a subroutine name in a type statement (REAL, INTEGER, ...). All information coming back from a subroutine passes through the argument list, or something called a COMMON block (later). However, there is nothing in these communications channels that can't be used by a Function Subprogram. A secondary difference is that a Subroutine need not have an argument list. This won't make sense to you until you learn about COMMON blocks, so I've ducked the issue thus far.

I'm confused about CALL statements. Should they be within subroutines that they are calling for?

Think of a CALL as a GO TO statement. If you say "CALL INPUT1", Fortran effectively looks for the statement called "SUBROUTINE INPUT1" and goes there to execute more instructions. Just before taking the jump, the program makes some notes so that the code in the SUBROUTINE knows about location of arguments, and knows that when it hits a RETURN statement, it should GO TO the statement in your code following "CALL INPUT1". You can see when thinking of this as a GO TO operation, there are too many opportunities for an infinite loop if SUBROUTINE INPUT1 contains a line that says "CALL INPUT1". There are also some subtle data management nightmares. To avoid all of this, older Fortran standards refused to let you include "CALL INPUT1" in SUBROUTINE INPUT1. This continues to be a good idea. However, for those who like to live on the edge Fortran 90 will let you include "CALL INPUT1" in SUBROUTINE INPUT1. This process is called recursion. I never use it, and don't recommend it. You will get another story from a certified Computer Scientist. Those folks really love recursion, and to be fair can do some fairly slick things with it.

Can you call an intrinsic function in a parameter statement?

No, except for functions specifying a variable's kind attribute. You can only use simple operations +, -, *, and /, combined with constants (1.0, 1, 1.e1, etc) and previously defined parameters. You can use **, but only if the exponent is of type integer so that the compiler can generate the answer without using the intrinsic functions "exp" and "log".


Up to other Questions and Answers / Home


Maintained by John Mahaffy : jhm@psu.edu