next up previous contents
Next: Future Work Up: qtcm User's Guide Previous: Troubleshooting   Contents

Subsections


Developer Notes

Introduction

This section describes programming practices and issues related to the qtcm package that might be of interest to users wishing to add/change code in the package. Please see the package API documentation, which includes the source code, for details.


Changes to QTCM1 Fortran Files

The source code used to generate the shared object files used in this Python package is unchanged from the pure-Fortran QTCM1 model source code, except in the following ways:

In addition, the Fortran files setbypy.F90, wrapcall.F90, and varptrinit.F90 are added. The routines in these files, however, just add more flexibility and functionality to the model; they do not automatically affect any model computations. See Section 6.3 for details.


New Interfaces and Fortran Functionality

As described in Section 6.2, the Fortran files setbypy.F90, wrapcall.F90, and varptrinit.F90 are added to the QTCM1 source directory. The first two files define the Fortran 90 modules (SetbyPy and WrapCall) needed to interface the Python and Fortran levels. The last file defines a new Fortran subroutine varptrinit that associates QTCM1 model pointer variables at the Fortran level. In a pure-Fortran run of QTCM1, this occurs in subroutine varinit; for a compiled_form='parts' run, since the functionality of the Fortran varinit is now in the Python varinit method, a separate Fortran pointer association subroutine needed to be defined. The Fortran subroutine varptrinitis called as the varptrinit function of the compiled_form='parts'.so extension module.


Fortran Module SetbyPy

Design Description

This module defines functions and subroutines used to read variables from the Fortran-level to the Python-level, and in setting Fortran-level variables using the Python-level values. These routines are used by Qtcm methods get_qtcm1_item and set_qtcm1_item(and dependencies thereof) to ``get'' and ``set'' the Fortran-level variables. Note that the Fortran module SetbyPy is referred to in lowercase at the Python level, i.e., as the attribute __.qtcm.setbypy of a Qtcm instance.

Because Fortran variables are not dynamically typed, separate Fortran functions and subroutines need to be defined to get and set variables of different types.6.2The Qtcm methods get_qtcm1_itemand set_qtcm1_item know which one of the Fortran routines to call on the basis of the type and rank of the value for the field variable in the defaults submodule. This is why all field variables need to have defaults defined in defaults. For array variables, the field variable defaults also provide the rank of the Fortran-level variable being gotten or set. However, the array default values do not have to have the same shape as the Fortran-level variables; on the Python-side, variable shape adjusts to what is declared on the Fortran-side. Thus, if you change the resolution of the compiled QTCM1 model, you do not have to change the dimensions of the field variable values in defaults.

The Qtcm method get_qtcm1_item directly calls the SetByPy routines. The Qtcm method set_qtcm1_item makes use of private instance methods that make the calls to the SetByPy routines.

For scalar field variables, SetByPy provides functions and subroutines that provide the value of the variable on output. For array field variables, SetByPydynamic module arrays are used to pass array variables in and out; I could not get the SetByPy Fortran routines to set locally defined dynamic arrays (that is, locally within a function or subroutine).6.3In the SetByPy module, these dynamic arrays are defined as follows:

Real, allocatable, dimension(:) :: real_rank1_array
Real, allocatable, dimension(:,:) :: real_rank2_array
Real, allocatable, dimension(:,:,:) :: real_rank3_array

For all field variables, scalar or array, the SetByPy module has a fourth module variable defined, is_readable, that the Fortran get and set routines will set to .TRUE. if the variable is readable and .FALSE. if not (it's declared as a logical variable). This Fortran variable can be used to prevent Python from accessing pointer variables that aren't yet associated to targets.

In general, SetByPy routines make use of Fortran constructs to enable them to accomodate all possible variables of a given type and shape. However, for string scalars, the SetByPy function getitem_strhas to have a return value of a predefined length, in order to work properly. That length is given by the parameter maxitemlen and is set to 505 (the value is chosen to be larger than all filename variables described in Section 6.2 and to be easily found in the .F90 files).

Module Structure

If you're a Fortran programmer, you can probably get all the information in this section from just reading the setbypy.F90 file directly. This description of the module structure, however, permits me to highlight what you need to do if you want to make additional compiled QTCM1 variables accessible to Python Qtcm objects.

Each of the routines in the module Contains block is essentially a list of if/elseif statements. The list tests for the name of the variable of interest (a string), and gets or sets the compiled QTCM1 model variable corresponding to that name. For pointer array variables, a test is also made as to whether or not the variable has been associated. If not, the variable is not readable and is_readable is set to .FALSE. accordingly.

If you wish to add another compiled QTCM1 model variable to be accessible to Qtcm instance methods get_qtcm1_itemand set_qtcm1_item, just add another if/elseif, like the other if/elseif blocks, in the Fortran set and get routines corresponding to the QTCM1 variable type (scalar vs. array, and real, integer, or string). On the Python side, add an entry in defaults corresponding to the new field variable you've created access to. I would strongly recommend making the Python name of your new field variable (given in defaults) be the same as the compiled QTCM1 model variable name.


Fortran Module WrapCall

Most of the time, if you want to call a compiled QTCM1 model subroutine from the Python level, you will use the version of the subroutine that is found in this Fortran module. Note that the Fortran module WrapCall is referred to in lowercase at the Python level, i.e., as the attribute __.qtcm.wrapcall of a Qtcm instance.

All the routines in this module do is wrap one of the compiled QTCM1 model routines. For instance, WrapCall subroutine wadvcttq is defined as just:

Subroutine wadvcttq
   Call advcttq
End Subroutine wadvcttq

All subroutines in this module begin with ``w'', with the rest of the name being the Fortran QTCM1 subroutine name. The calling interface for the ``w'' version is the same as the Fortran QTCM1 original version. There are no subroutines in this module that do not have an exact counterpart in the Fortran QTCM1 code, and thus this module's subroutines sole purpose is to call other subroutines in the compiled QTCM1 model.

These wrapper routines are needed because f2py, for some reason I can't figure out, will not properly wrap Fortran routines (that are then callable at the Python level) that create local arrays using parameters obtained through a Fortran usestatment. Thus, as an example, a Fortran subroutine foowith the following definition:

subroutine foo
   use dimensions
   real a(nx,ny)
   [\ldots]
end subroutine foo

where nx and ny are defined in the module varsdimensions, will return an error, with the result that the extension module will not be created, or an extension modules that yields output that is different from running the pure-Fortran version of QTCM1.

By wrapping these calls into this file, I also avoid having to separate out the Fortran QTCM1 subroutines into separate .F90 files. For Fortran subroutines that you want callable from the Python level, f2py seems to require each Fortran subroutine to be in its own file of the same name (e.g., the version of driver.F90 for this package). If several Fortran subroutines are all found in a single .F90 files, f2py seems unable to create wrappers for those subroutines.

Python qtcm and Pure-Fortran QTCM1 Differences

This section describes differences between how the qtcmpackage and the pure-Fortran QTCM1 assign some varables. A list of changes to the QTCM1 Fortran Files for use in the qtcmpackage is found in Section 6.2.


QTCM1 driverinit

In the pure-Fortran version of QTCM1, by default, the following variables are set by reference (as given below), not by value, in the driverinitroutine:6.4
lastday=daysperyear
viscxu0=viscU
viscyu0=viscU
visc4x=viscU
visc4y=viscU
viscxu1=viscU
viscyu1=viscU
viscxT=viscT
viscyT=viscT
viscxq=viscQ
viscyq=viscQ

Thus, in pure-Fortran QTCM1, if you change daysperyear, viscU, etc. and recompile (as needed), you will automatically change lastday, viscxu0, etc. (Though, in the pure-Fortran QTCM1, the default values may be overwritten by namelist input values.)

The driverinit routine is eliminated in the Python qtcm package. Instead, inital values of field variables are specified in the defaults submodule and set by value to attributes of the Qtcm instance. Thus, for instance, in a Qtcm instance, lastday is set to 365 by default, not to some variable daysperyear. For the diffusion and viscosity terms, the Qtcm instance attributes corresponding to those terms are set to literals.6.5

In contrast, in the pure-Fortran QTCM1, driverinit declares local variables viscU, viscT, and viscQ, and reads values into those variables via the input namelist. Those values are then used to set viscxu0, viscyu0, etc., as described above. In pure-Fortran QTCM1, viscU, viscT, and viscQare not directly accessed anywhere else in the model. Thus, viscU, viscT, and viscQ are not defined as field variables in the qtcm package, and Qtcm instances do not have attributes corresponding to those names. Additionally, if you wish to change a viscosity parameter visc* (given above), the parameter for each direction must be set one-by-one even if the flow is isotropic.

The varinit Routine

One of the functions of the pure-Fortran QTCM1 varinitsubroutine is to associate the pointer variables u1, v1, q1, and T1. For the extension modules in the qtcmpackage, a Fortran subroutine varptrinit is added that can also do this association. This subroutine is called in the Qtcm instance method varinit (which duplicates and extends the function of its pure-Fortran counterpart, enabling alternative ways of handling restart).

The varptrinit is not accessed via wrapcall. Remember that wrapcall contains only those routines that were in the original pure-Fortran QTCM1 code, and that we want to have access to at the Python level.

The qtcm Method of Qtcm

The Qtcm method qtcm duplicates the functionality of the qtcm subroutine in the pure-Fortran QTCM1 model. There are a few differences, however. First, the qtcm method for Qtcm instances does not include a call to cplmean, which uses mean surface flux for air-sea coupling. This state is consistent with the pure-Fortran QTCM1 pre-processor macro CPLMEAN being off. Thus, if you wish to use mean surface flux for air-sea coupling, you will have to revise the qtcmmethod of Qtcm to call cplmean. You'll also have to check for any other code additions needed that are associated with the CPLMEAN macro.

Second, the qtcm method for Qtcm instances does not include the option of not using the atmospheric boundary layer model. This is consistent with macro NO_ABL being off. If you wish to have no atmospheric boundary layer model, change the run list atm_bartr_mode so that the wsavebartr and wgradphis routines are not called. You'll also have to check for any other code additions needed that are associated with the NO_ABL macro.

Miscellaneous Differences

Considerations When Adding Fortran Code

In this section I describe issues to consider if you wish to add your own compiled code to the package as separate extension modules. (This is different from creating new standard extension modules, which is described in Section 6.6.):


Creating New Standard Extension Modules

The steps involved in creating the standard extension modules (e.g., _qtcm_full_365.so, etc.) on installation are given in Section 2.4. The makefile provided in /buildpath/src uses a Fortran compiler to create the object code, runs f2pyto create the shared object file in src, and moves the shared object files into ../lib, overwriting any pre-existing files of the same name. In this section, I describe the makefile and f2py in a little more detail, in case you wish to create standard extension modules with additions from the ones the default makefile creates.


Makefile Rules

This section describes the rules of the makefile found in the src directory of the qtcm distribution. This makefile is used by the Python package to create the extension module (.so files) imported and used by qtcm objects (as described in Section 2.4). The makefile will, in general, be used only during qtcminstallation, but if you wish to recompile the QTCM1 libraries and make changes in the Python extension module, you'll want to use/change this makefile.

clean
Removes old files in preparation for compiling new extension modules.

libqtcm.a
Creates library libqtcm.a that contains all QTCM1 object files in the directory src,, except setbypy.o, wrapcall.o, varptrinit.o, and driver.o. This archive is compiled with the netCDF libraries. Previous versions of libqtcm.a are overwritten.

_qtcm_full_365.so
Creates the extension module _qtcm_full_365.so. f2py is run on applicable code in src, and the extension module is moved to ../lib. Any previous versions of ../lib/_qtcm_full_365.so are overwritten.

_qtcm_parts_365.so
Creates the extension module _qtcm_parts_365.so. f2py is run on applicable code in src, and the extension module is moved to ../lib. Any previous versions of ../lib/_qtcm_parts_365.so are overwritten.


Using f2py

This section briefly describes how f2py is used in the makefile during the creation of the extension modules. F2py is a program that generates shared object libraries that allow you to call Fortran routines in Python. F2py comes with Python's NumPy array handling package, so you do not need to install anything extra if you have NumPy already installed.

To create the extension modules in qtcm using the makefile described in Section 6.6.1, I use a method similar to the ``Quick and Smart Way'' described in the f2py manual. For the _qtcm_full_365.so extension module, the f2py call is:

f2py -fcompiler=$(FC) -c -m _qtcm_full_365 driver.F90 $\backslash$
setbypy.F90 libqtcm.a $(NCLIB)

and for the _qtcm_parts_365.so extension module, the call is:

f2py -fcompiler=$(FC) -c -m _qtcm_parts_365 $\backslash$
varptrinit.F90 wrapcall.F90 setbypy.F90 $\backslash$
libqtcm.a $(NCLIB)

For both calls, FC and NCLIB are the environment variables in the makefile specifying the Fortran compiler and netCDF libraries, respectively. The -m flag specifies the extension module name (without the .so suffix). The .F90 files specify the files that have modules and routines that will be accessible at the extension module level, and the rest of the Fortran files in QTCM1 are compiled and archived in a library libqtcm.a. For f2py to work properly, the .F90 files may define only one module or routine.

If you add Fortran files containing new modules, and you wish those modules to be accessible at the Python level, compile your new code with f2py. If we have a file of such new code, newcode.F90, the f2py call to create the _qtcm_parts_365.so extension module will become:

f2py -fcompiler=$(FC) -c -m _qtcm_parts_365 $\backslash$
varptrinit.F90 wrapcall.F90 setbypy.F90 $\backslash$
newcode.F90 $\backslash$
libqtcm.a $(NCLIB)

If you write new Fortran code for the compiled QTCM1 model that will not be accessed from the Python-level, just add the object code filename to the variable QTCMOBJS in the makefile; you don't have to do anything else. If you are adding Fortran code to existing Fortran modules, it's even easier: You don't need change the makefile. Note that for 64 bit processor machines, you may have to use f2py with the -fPIC flag; see Section 2.8.4 for details on how the lines above will change.

Two Examples

A Function: Let's say you have written a piece of Fortran code called myfunction.F90 that contains one function called myfunction, and you want to have this function callable from the Python level through the Qtcm instance method __qtcm.myfunction. Do the following:

  1. Move myfunction.F90 to src in the qtcm distribution directory /buildpath.

  2. Add myfunction.o to the end of the object file list lines after the target names _qtcm_full_365.so and _qtcm_parts_365.so.

  3. In the _qtcm_full_365.so and _qtcm_parts_365.so target descriptions, add myfunction.F90 to the beginning of the list of .F90 names in the f2py lines.

A Module: Let's say you have written a piece of Fortran code called mymodule.F90 that contains the Fortran module MyModulecontaining multiple routines and variables. You want to have those routines and variables callable from the Python level through the Qtcm instance attribute __qtcm.mymodule. The steps to add MyModule to the extension modules are exactly the same as for a single function, with mymodule being substituted in the makefile everywhere you have myfunction.

Attributes and Methods in Qtcm Instances

In this section I describe some attributes, particularly private ones, that may be of interest to developers. As is the convention in Python, private attributes and methods are prepended by one or two underscores, with two underscores being the ``more'' private attribute. Please see the package API documentation for details about all variables, including private variables.

Public num_settings Submodule Attributes/Methods

Private qtcm Submodule Attributes

This submodule of the package qtcm is the module that defines the Qtcm class.


Private Qtcm Attributes

Creating Documentation

The distribution of qtcm comes with the full set of documentation in readable form (PDF and HTML). The documentation consists of two kinds: this User's Guide and the API documentation. The User's Guide is written in LATEX. The PDF version is generated directly from LATEX, and the HTML version is created by LATEX2HTML.

I use the make_docs shell script in doc creates all these documents. Briefly, that script does the following:

The make_docs script cannot be used without customizing it to your system, so please DO NOT USE IT if you do not know what you are doing. You could easily wipe out all your documentation by mistake.


next up previous contents
Next: Future Work Up: qtcm User's Guide Previous: Troubleshooting   Contents
Johnny Lin 2008-09-12