Fortran2
'Fortran2: Getting the most from Fortran'
Following On
These notes follow on from the Fortran1 course. You may want to look back at those notes and examples, or if you're feeling confident, just dive in here!
To get the example programs login in to a Linux box and type:
svn co http://source.ggy.bris.ac.uk/subversion-open/fortran2/trunk fortran2
Allocatable Arrays
We'll begin by looking at 'allocatable arrys.
Previously, we declared the size of our arrays when we wrote our code. This is all well and good, and at least we knew where we stood. However, this exact specifiation of the shape of all things can become a painful limitation. Suppose you were writing a program to process a list of University employees. The number of employees will almost certainly vary from one month to the next. So, we can only know the number of records to read in at runtime. This sort of thing starts to crop up a lot when we aim to write more general-purpose, flexible & robust programs.
Fear not! however, as trusty Fortran90 is here to help. In Fortran90 we can write array declarions of the form:
<type>,allocatable,dimension(:) :: my1dAllocatableArray ! a 1D array of type <type>, which I will give a size to later
Let's go to an example:
cd fortran2/examples/example1
and take a look inside the file allocatable.f90.
In this example, we have a 2D grid and corresponding arrays of longitude and latitude coordinates, the sizes of which we will read from a namelist file at runtime. We will then allocate an appropriate amount of space, fill in the oordinate values according to the number of grid-cells specified in the file and report the whole lot to standard-out. Our arrays are declared with the allocatable attribute:
real,allocatable,dimension(:) :: x_coords ! x-coordinate values real,allocatable,dimension(:) :: y_coords ! y-coordinate values real,allocatable,dimension(:,:) :: data ! 2D data on grid
We've covered namelists before, so reading the values of the variables nx and ny is straighforward. Once we know these values we can request memory space for our arrays. Note that it is an excellent idea to check whether this request was satisfied. If it were not for some reason--if you request a huge amount of memory, your computer may not be able to oblige--we probably want the program to stop, lest bad things happen!
allocate(x_coords(nx),stat=errstat) if (errstat /= 0) then write (*,*) 'ERROR: could not allocate x_coords array' stop endif
Next is a spot of arrithmetic to fill out the coordinate arrays properly. We want cell-centre coordinates, with longitude running from 0 to 360 degrees and lattitude from -90 to +90 degrees.
So we're done? Almost, but not quite! It is vital that once you're finished with it, you clear up any memory that you allocated earlier. If you don't, bad things are very likely to happen. So the last few lines look like:
if(allocated(data)) deallocate(data) if(allocated(x_coords)) deallocate(x_coords) if(allocated(y_coords)) deallocate(y_coords)
Here, we're a bit smarter than the average pup, and we've checked to see if an array has indeed been allocated before deallocating it. It would be unwise to try to deallocate something which had not been allocated in the first place.
So there we are. Sorted. Allocatable arrays. Try varying the values in input.nml first, and then procede to writing a few allocatable arrays of your own.
User Derived Types
Next up, let's look at another very useful feature provided by good old Fortran90---user-derived types:
cd ../example2
Way back at the start of Fortran1 we looked at the half dozen or so intrinsic types offered by Fortran90. It's not so much that this set of types is becoming a limitation (as we found with static array sizes) but more that for a large program things get a bit, well, messy! "Not the end of the world", you may say. And you're right. However, mess does lead to bugs and bugs are a huge problem, so actually messy, spaghetti-like code, is a significant problem that we want to avoid at all costs.
Now, user-derived types offer us something simple, yet potentially extremely powerful--we can group things which are related into a single composite type. That is, we can group all the things that belong together into a single entity and then pass that entity around as work is done by our program. This is a huge bonus when designing and maintaining a reasonably sized program. As we'll find out with growing experience, user-derived types are, pretty much, the best thing since sliced bread!
(User-derived types represent a first step down the road towards object-oriented programming. A step in the right direction in terms of designing programs.)
OK, enough of the spiel. Let's take a look at user-types.f90.
I recently bought a DAB radio and I'm as pleased as punch with it. One of the great features is that you an select a radio station by name, push a button and bingo!, you're tuned in. No more scrolling around the dial trying to get clear reception. That makes it really easy to 'station hop' and find something good on. Now, imagaine we were designed a program to implement a DAB radio. It's natural that we would want to keep the radio station name and tuning frequency (still needed, of course, but only by the machine as it now does the tuning for you) together in a single bundle. They belong together, after all, and are of different intrinsic types--a chacter string and a real number. In the program it looks like:
type station real :: frequency ! MHz character(len=maxstr) :: name ! str end type station
It wouldn't be much of a radio unless it had a number of stations. We can use an (allocatable) array of things of our new derived type to store all the required station information:
type(station),allocatable,dimension(:) :: stations ! array of stations
I've made the array allocatable for flexibility, or perhaps out of habit. I guess we could grow it if more stations became availble..
After allocating the array (and importantly checking to see whether we hit a problem), all I need to do is fill out the array and print it to the screen. Note the use of % in the syntax.
We can pass derived-tpes around, into and out of subroutines for example, just like intrinsic types. This enables us to write very elegant and maintainable programs. If we discover that we need some more information added to our radio station type, we just add it and the rest of the program can remain unchanged--very handy!
Have a go now at writing a subroutine which takes the radio station array as an argument. Then add something new to the derived type and access it in your subroutine.
Modules
cd ../exampe3
In this example, we'll take a look at another very useful feature provided by Fortran90--modules. This is
Variables and subroutines. Use (with only attributes). Group together related info/routines.
Using separate files
Will do that - JPR
Libraries
Will do this too - JPR
Appendices
Including NetCDF
Have written examples, will write up notes - GW.