4.2 Introduction to GLIMMER programming techniques

Although GLIMMER is written in Fortran, a very widely used language in the geosciences, many of the techniques employed in the model will be unfamiliar to even seasoned Fortran programmers. What follows is a brief description of some of these more advanced techniques. For further information about the topics covered, the reader is directed to Metcalf and Reid (1999) and Decyk et al. (1997).

4.2.1 Fortran Modules

Module definition

The basic building block of the GLIMMER structure is the Fortran 90/95 module, which is a way of collecting together subroutines, functions type definitions and variables into a single scope2 . The module may then be used within another piece of code, so that the names in the modules scope are available in the other piece of code. A module block is defined like this:

    module module-name

        module variable declarations
        derived-type definitions

    contains

        function and subroutine declarations

    end module
The contains is omitted if no subroutines or functions are present. It is good practice to put each module in a separate file, and give the filename the same name as the module.
Using modules

A module is accessed by another piece of code with the use statement. Thus, if the module foo contains the subroutine bar, another piece of code may make use of it in this way:

    program program-name

        use foo

        implicit none

        variable declarations

        call bar

    end program program-name
Of course, subroutines, functions and modules can all contain use statements as well.
Privacy in modules

By default, all the names in a module’s scope become available to any program element that references it with a use statement. There are circumstances where this is undesirable, and so Fortran 90/95 provides a way to define names as public or private. The public and private statements may both be present in the first block of a module (i.e. before the contains, if present). The list of variable and/or subroutine names follows. For example:

    private :: foo, bar, blah
It is good practice to be clear about what parts of a module form a public interface, and define this formally. A good way of doing this is to set the default to private, and then set specific names as public:

    private
    public :: foo, bar
This technique is useful for avoiding name conflicts when two modules might define internal variables or subroutines with the same name (e.g. pi for the value of π). Also, public subroutines are best named in an un-generic way, by prefixing their names with the name of the module. For instance, the module glimmer_daily_pdd contains the public subroutines glimmer_daily_pdd_init and glimmer_daily_pdd_mbal.

4.2.2 Derived types

A derived type is a way of collecting together an arbitrary set of variables and arrays into a composite variable type, instances of which can be addressed with a single name. The concept of derived types takes us some way towards so-called object-oriented programming (OOP) techniques, though there are some important OOP techniques that are not implemented in Fortran 90/95.

A derived type is defined in this kind of way:

    type typename
        real :: realvar
        integer :: intvar
        real,pointer,dimension(:,:) :: realarr => null()
    end type typename
So, a derived type can contain any scalar variables, and also pointers (either scalar pointers or array pointers). Fortran 90/95 does not permit derived type elements to be allocatable arrays, but since the behaviour of pointer arrays is very similar, this isn’t a serious problem.

The type definition doesn’t actually create any variables though. To do that, you need to use the type declaration to create an instance of the derived type (known as a structure), just as with any other type of variable:

    type(typename) :: fred, jim, bob
This creates three structures of type typename, called fred, jim and bob. Structures can be handled like ordinary variables, and passed to subroutines in argument lists, etc. The individual elements may be addressed using the % operator:

    fred%realvar = 4.5
    jim%intvar = fred%intvar + 7
    allocate(bob%realarr(nx,ny))

Note that by default, mathematical operators have no meaning when applied to derived types; writing bob = fred + jim isn’t allowed. If you want to add the elements of fred to the elements of jim, it’s necessary to write a function to do so, so that you would, for example, write bob = typename_add(fred,jim) It is possible to define the meaning of the + operator so that it uses the function typename_add to perform the addition (a process known as operator overloading), but describing how is beyond the scope of this document. In any case, derived types in GLIMMER are not generally used in a way such that arithmetic operations would make sense.

Usually, derived type definitions are put into modules (before contains), so that they can easily be used in different pieces of code. The power that this combination of type definitions and modules gives is described in the next section.

4.2.3 Object-orientation with modules and derived types

As the name implies, the object is the central concept of object-oriented programming. In traditional OOP terminology, a class defines a type of object. The class has particular attributes, which might be numerical values, strings or whatever (i.e. they’re variables that are part of the class), and methods, which are things you can ask an object to do to itself (i.e. methods are functions or subroutines which are part of a class).

Explaining OOP with examples is notoriously tricky because the easiest examples of classes are those that mirror classes of objects in the real world, but this inevitably seems a bit contrived. So, although it seems a bit ridiculous, here’s one such example…Imagine a class which describes a domestic oven. What are the attributes of an oven, that differentiate one oven from another? These could be the size of the oven, it’s type (electric or gas), as well those things that describe its present state: the thermostat setting, the actual oven temperature, whether the heating element is on or off, and whether the door is open or not

Secondly, what kind of actions can we perform on the oven? Most likely, we want to be able to open and close the oven door, check the present temperature, and set the thermostat. These actions are the methods.

In terms of Fortran 90, we can use the mechanism of modules and derived types to implement a somewhat limited form of OOP:

  module domestic_oven

    type oven
      real :: width,height,depth
      real :: thermostat
      real :: temperature
      logical :: element      ! .true. for no, .false. for off
      logical :: door         ! .true. for open, .false. for closed
      character(10) :: oven_type
    end type oven

  contains

    subroutine oven_set_thermostat(self,temp)

      type(oven) :: self
      real :: temp

      self%temperature = temp

    end subroutine oven_set_thermostat

     plus other functions/subroutines

  end module domestic_oven
A module like this will usually include a subroutine to initialise objects; in this case, you would expect to be able to specify the size of the oven, its type, initial temperature, etc.

So much for computer models of domestic ovens; what use is OOP to ice modellers? Well, using the technique described above is what allows GLIMMER to be used to run several regional ice models simultaneously. It also makes adding new mass-balance models to GLINT much easier. In fact, the principles of OOP are used all over the GLIMMER code, so it is worth getting to grips with them.

4.2.4 Example of OOP in Glimmer

A good, self-contained example of GLIMMER programming style, and the use of OOP is the way that the daily PDD mass-balance scheme (in glimmer_daily_pdd.F90) has been implemented. Here, the parameters of the scheme are stored in the derived type glimmer_daily_pdd_params, and the public interface of the module is limited to this derived type, and two subroutines (glimmer_daily_pdd_init and glimmer_daily_pdd_mbal). Everything else is kept private within the module. The initialisation subroutine uses the standard GLIMMER configuration file reader to get input parameters, and the GLIMMER logging mechanism to output data to the screen and/or file. The object-like structure means that it is easy to use the daily PDD model within some other piece of code: all one needs to do is declare an instance of glimmer_daily_pdd_params, initialise it appropriately, and then call the glimmer_daily_pdd_mbal subroutine. Also, although the PDD model is currently initialised from file, it would be easy to write an alternative initialisation routine within the module to get the parameters from a subroutine argument list, extending the capabilities of the module rather than changing the present interface.

4.2.5 Pointers

The final more advanced topic covered here is the Fortran pointer. Thankfully, the name is self-explanatory — a pointer is a thing that points to another thing. This might not sound like much use, but since pointers can be made to point at different things as the program runs, and can have memory allocated for them to point to, they can be used to create flexible, dynamic data structures. The thing that a pointer points to is called a target.

Pointer basics

Pointers are declared much like an ordinary variable, but with the pointer attribute. For example, to declare an integer pointer, one would write this:

    integer, pointer :: foo => null()
The second part of this statement (=>null()) initialises the pointer to null (i.e. pointing to nothing). This is only available in Fortran 95, but it is highly desirable to use it, as otherwise the pointer’s target is undefined3 .

Note that although we have declared a pointer with this statement, we can’t use it as a variable yet, as it isn’t pointing at an integer-sized chunk of memory. There are two ways of rectifying this: either the pointer can be made to point to an existing variable, or a new block of memory can be allocated to hold the target. These two methods are known as pointer assignment and pointer allocation, respectively.

Pointer assignment

Pointing a pointer at something is very simple. The pointer assignment statement uses the => operator:

    a => b
This statement sets pointer a to point to target b. For this to be valid Fortran, a must have the pointer attribute, while b must be either a variable with the target attribute, or another pointer. If the target is a pointer, then the first pointer is set to point at the second pointer’s target. This means that

    a => b
    c => a
is equivalent to

    a => b
    c => b
Pointer allocation

A pointer can be made to point to a newly-allocated chunk of memory using the allocate statement, in the same way as an allocatable array is handled:

    allocate(a)
By using the pointer assignment described above, other pointers may be made to point at the same piece of memory:

    b => a
Un-pointing pointers, and avoiding memory leaks.

A pointer can be nullified (made to point to nothing) in two ways: using the null function, or the nullify statement:

    p => null()
    nullify(p)
Care has to be taken with this, however. Recall the memory allocation example given above:

    allocate(a)
    b => a
It is significant that the memory location that a and b now point to doesn’t have a name of its own, which means that if a and b are subsequently both made to point to something else, or to nothing, the target variable becomes impossible to recover. Consider this sequence of two statements:

    allocate(a)
    a => null()
Here, a block of memory is allocated, and a is made to point to it. Then, a is made to point to nothing. However, the block of memory allocated in the first statement hasn’t been deallocated, and is now unrecoverable — and unusable — since we don’t have a name to refer to it by. This is known as a memory leak, and it is a Bad Thing. It’s not disastrous if a few integer-sized blocks of memory suffer this fate, but if large amounts of memory are lost like this, it is quite possible for the program to crash or become very slow as a result.

The proper way to avoid memory leaks is to deallocate memory before it becomes dereferenced, using the deallocate statement4 :

    allocate(a)
    deallocate(a)
    a => null()
The reason that this isn’t done automatically when a pointer is nullified is because there may be other pointers still pointing to that memory location. The process of working out which chunks of memory have been ‘orphaned’ and so need to be deallocated (known as garbage collection) is complex, and compiled languages like Fortran don’t usually do it automatically. Avoiding memory leaks therefore depends on careful program design.
Linked lists

A major use for pointers is the linked list, a flexible data structure whose size and structure can be changed dynamically, without having to reallocate it from scratch (which is the case with allocatable arrays).

The principle of the linked list is that each element of the list points to the next element. Extra elements can be added to the end of the list by allocating more memory, and making the final element point to it. Other actions can be done by manipulating the pointers of the various elements.

A typical linked list might use the following derived type:

    type list
        type(list) :: next => null()
        type(list) :: previous => null()
        integer :: value
    end type list
So, the type contains pointers that point to the next and previous elements of the list. Describing how to implement subroutines and functions to construct, read and modify the list is beyond the scope of this document, but a full example is provided in Metcalf and Reid (1999).

GLIMMER makes several uses of linked lists and pointer techniques, most notably in the handling of configuration and output files.