ARJO SEGERS
KNMI
Date: 3 March 2009
The GO library provides general purpose entities for Fortran90 programs.
In summary, the GO library provides:
The library consists of a single module to provide access to routines, functions, and data types. In addition, a few shell scripts are provided for specialized tascs.
An integer status argument is present in most routines:
subroutine aaa( status ) integer, intent(out) :: status ! wrong! if ( 1 /= 0 ) then write (*,'("ERROR - this is a serious error ...")') write (*,'("ERROR in aaa"); status=1; return end if ! ok status = 0 end subroutine aaaThe value should be checked on return to test on succesful completion:
call aaa( status ) if (status/=0) then; write (*,'("ERROR in prog")'); status=1; return; end ifTo make the make the code more readible, define a preprossor macro for this check-and-trace line:
#define IF_ERROR_THEN(action) if (status/=0) then; write (*,'("ERROR in prog")'); action; return; end if call aaa( status ) IF_ERROR_RETURN( status=1 )
The GO-library takes the form of a module:
use GO
The GO module (source file go.f90) is in fact a shell around a bunch of sub modules. The sub modules should not be used directly, but accessed through the main module only.
Standard file units, might be compiler depended.
Since the standard file units might be used by other modules to write (error) messages, this is the most basic of all GO modules.
Access the entities of this module via the main module (prefered) or directly:
use GO, only : ... use GO_FU, only : ...
integer, parameter :: goStdIn = 0 integer, parameter :: goStdOut = 5 integer, parameter :: goStdErr = 6
integer, parameter :: goFuRange(2) = (/0,999/)Free file units selected by 'goGetFu' in module 'GO_File' are within this range; if all are in use, an error is issued. This range might become a variable in future, such that free file units are selected in a range that is not used by other parts of the code.
The module is implemented in source file go_fu.F90 .
Tools for (error) messages.
Basic idea is to write messages to the character buffer 'gol' and let the the actual printing be managed by a special routine. This is especially usefull if the program runs on multiple processors:
write (gol,'("This is processor : ",i2)') myid; call goPrWith the appropriate settings, the output might be:
[00] This is processor : 0 [03] This is processor : 3 [02] This is processor : 2 [01] This is processor : 1
Access the entities of this module via the main module (prefered) or directly:
use GO, only : ... use GO_Print, only : ...
character(len=256) :: gol
call GO_Print_Init( status, apply=.true., & prompt_pe=.true., pe=0, & trace=.false. )Arguments:
call GO_Print_Done( status )Arguments:
write (gol,'("Hello!")'); call goPr
write (gol,'("this is a serious error ...")'); call goErr write (gol,'("in program")'); call goErr; status=1; returnThis will produce the following output:
[00] ERROR - this is a serious error ... [00] ERROR - in programAt the moment, error messages are treated the same as normal messages.
call goLabel( 'mymod/myroutine' ) ... call goLabel()The labels can be used to show in which routine info is printed or where exactelly an error occured. If option 'trace' in the initialization is true, labels are always printed; nice for debugging. By the first command, a new label is pushed on a stack to let the program known that a new part of the code is reached; the second command (without arguments) indicates that the end of this part is reached and the label is popped from the stack. Messages printed in between the above commands will be indented; the height of the stack determines the indention.
[00] <myprog> [00] This is the main program. [00] <mymod/aaa> [00] This is routine aaa. [00] <mymod/bbb> [00] This is routine bbb. [00] (mymod/bbb) [00] (mymod/aaa) [00] Normal end. [00] (myprog)The current label is also unset by a second call to 'goErr' if 'gol' has not been filled in between:
subroutine aaa( status ) integer, intent(out) :: status call goLabel( 'mymod/aaa' ) call bbb( status ) if (status/=0) then; call goErr; status=1; return; end if status=0; call goLabel() end subroutine aaa subroutine bbb( status ) integer, intent(out) :: status call goLabel( 'mymod/bbb' ) write (gol,'("this is a serious error ...")'); call goErr call goErr; status=1; return status=0; call goLabel() end subroutine bbbThis will produce the following output (no trace):
[00] ERROR - this is a serious error ... [00] ERROR - in mymod/bbb [00] ERROR - in mymod/aaa [00] ERROR - in myprog
The following options should be implemented soon, since they have been implemented in older versions:
The module is implemented in source file go_print.f90 .
General objects for character strings.
Access the entities of this module via the main module (prefered) or directly:
use GO, only : ... use GO_String, only : ...
call goSplitLine( 'ab#cd', s1, '#', s2, status )The input string is split at the first occurance of '#'; the leading part is returned in s1, and the remainder without '#' in s2. One or both of s1 and s2 might be empty.
subroutine goSplitLine( line, s1, c, s2 ) character(len=*), intent(in) :: line character(len=*), intent(out) :: s1 character(len=1), intent(in) :: c character(len=*), intent(out) :: s2 integer, intent(inout) :: status end subroutine goSplitLine
line = 'abc, 123' call goReadFromLine( line, x, status [,sep=','] )The character string line is split at the first komma (or the character specified by the optional argument sep), the contents of the leading part is read into the variable x and the remainder is returned in line. Currently only variables of standard type could be read.
subroutine goReadFromLine( line, x, status, sep ) character(len=*), intent(inout) :: line <xtype>, intent(out) :: x integer, intent(inout) :: status character(len=*), intent(in), optional :: sep end subroutine goReadFromLine <xtype> = integer | real | logical | character(len=*)
bb = 'default' call goVarValue( 'aa=1;bb=xyz;cc=U123', ';', 'bb', '=', bb, status )The character line is split at the specified seperations (here ';'), and examined on the occurance of the specified name (here 'bb') followed by an assignment character ('=') . The value is storred in the last variable before the status argument. If the name is not found, the value remains what it was. Interface:
subroutine goVarValue( line, sep, var, is, val, status ) character(len=*), intent(in) :: line character(len=1), intent(in) :: sep character(len=*), intent(in) :: var character(len=1), intent(in) :: is <xtype>, intent(inout) :: x integer, intent(inout) :: status end subroutine goVarValue( line, sep, var, is, val, status ) <xtype> = integer | character(len=*) Return status: <0 : variable not found, val remains the same 0 : variable found, val reset >0 : error
s = goNum2Str( i [,fmt='(i6)'] )Returns a length 6-character string with the representation of the integer value i in the first characters. An optional format could be provided, for example to include leading zero's. Interface:
character(len=6) function goNum2Str( i, fmt ) integer, intent(in) :: i character(len=*), intent(in), optional :: fmt end function goNum2Str
s2 = goUpCase( s1 ) s2 = goLoCase( s1 )Interfaces:
function goUpCase( s ) character(len=*), intent(in) :: s character(len=len(s)) :: goUpCase end function goUpCase function goLoCase( s ) character(len=*), intent(in) :: s character(len=len(s)) :: goLoCase end function goLoCase
call goWriteKeyNum( key, 'sh', 159 ) ! sh159Interface:
subroutine WriteKeyNum( res, key, num ) character(len=*), intent(out) :: res character(len=*), intent(in) :: key integer, intent(in) :: num end subroutine WriteKeyNum
call goTab2Space( s )
The module is implemented in source file go_string.F90 .
General objects for date manipulation.
Access the entities of this module via the main module (prefered) or directly:
use GO, only : ... use GO_Date, only : ...
A derived type is provided to store a date:
type(TDate) :: twith fields:
character(len=4) :: calender ! see 'calenders' integer :: year, month, day, hour, min, sec, mili
A second type is defined for time steps:
type(TIncrDate) :: dtwith fields for days, hours, minutes, seconds, and mili-seconds. The fields of an incremental data might have any value, even negative. Note that for this type a value day=1 has the interpretation of 24 hours, while it has the interpretation of 'first day', thus 0 hours, for a regular date.
A number of different calender types is supported:
To initialize a new date structure, a few routines are available.
Use routine NewDate to initialize some fields and to fill the rest with zero's. If no calender is specified, the default value 'greg' is used (see section about defaults).
t = NewDate( year=2000, month=1, ..., calender='greg' )Interface:
function NewDate( year, month, day, hour, min, sec, mili, zone, calender ) type(TDate) :: NewDate integer, intent(in), optional :: year, month, day integer, intent(in), optional :: hour, min, sec, mili integer, intent(in), optional :: zone character(len=*), intent(in), optional :: calender end function NewDateUse the specific order of the optional fields to quickly set the 'largest' values only, for example the date:
t = NewDate( 2000, 1, 2 )A special funtion creates a date that represents 'any' time; useful to specify that some data is constant in time and thus valid for any time. A special inquiry function is available to test wether a date is any:
t = AnyDate() l = IsAnyDate(t)
Use routine IncrDate to create an incremental date:
t = IncrDate( day=2, hour=12 )Interface:
function IncrDate( day, hour, min, sec, mili ) type(TIncrDate) :: IncrDate integer, intent(in), optional :: day, hour, min, sec, mili end function IncrDate
Fill the time from the system clock in a date structure; the result is of calender type 'wall':
t = SystemDate()
Use the Set routine to set some specific fields of a date structure:
call Set( t [,year=2000] [,month=1] [,day=2] & [,hour=0] [,min=0] [,sec=0] [,mili=0] & [,zone=0] [calender='greg'] )Use the specific order of the optional fields to quickly set the 'largest' values only, for example the date:
call Set( t, 2000, 1, 2 )
Normalize hours to 0,..,23, minutes to 0,..,59, etc:
call Normalize( t )
To check if all fields are consistent with eachother, use:
call Check( t )
Similar routines are implemented for date increments.
Use the Get routine to extract some specific fields of a date structure:
call Get( t [,year=year] [,month=month] [,day=day] & [,hour=hour] [,min=min] [,sec=sec] [,mili=mili] & [,zone=zone] [calender=calender] )Use the specific order of the optional fields to quickly extract the 'largest' values only, for example the date:
call Get( t, year, month, day )
A similar routine is implemented for date increments.
A few inquiry functions are provided.
The logical function LeapYear tells you if the year has a Februari 29 :
l = LeapYear( t )
Two integer functions are provided to count the total number of days in a month or a year:
i = Days_in_Month( t ) i = Days_in_Year( t )
An integer function is provided to return the day number, counting from 1 (Januari 1) to 360, 365, or 366 (last of December):
i = DayNumber( t )
Operator + is redefined to be able to add two dates to each other. Both should be of same calender type, unless one is an increment:
to = t1 + t2 to = t + dt dto = dt1 + dt2
Operator - is redefined to substract a date from another. Both should be of same calender type, unless the substracted date (t2) is an increment:
to = t1 - t2 to = t - dt dto = dt1 - dt2
Operator * has been redefined to multiply a date increment with a real or integer number. If necessary, a remaining fraction of miliseconds is rounded to the nearest integer.
dto = dt * 2 dto = 2 * dt dto = dt * 1.5 dto = 1.5 * dt
Operator / is redefined to devide a date incrment by a real or an integer number. If necessary, a remaining fraction of miliseconds is rounded to the nearest integer.
dto = dt / 2 dto = dt / 1.5
Logical operators are defined to compare two dates with eachother; both should be of same calender type:
t1 == t2 t1 >= t2 t1 > t2 t1 <= t2 t1 < t2
The total number in a certain unit is returned by rTotal (real value) or iTotal (integer value, error if the sum could only be expressed as fraction). Currently supported units are 'year', 'month', 'day', 'hour', 'min', 'sec', and 'mili'. If the total number is not wel defined for a certain date (how to assign a fraction of years to the date of today?), an error message is produced. Date increments are supported too.
r = rTotal( t, 'year'|'month'|... ) i = iTotal( t, 'year'|'month'|... )
A linear interpolation in time is represented by:
f(t) = alfa1 * f(t1) + alfa2 * f(t2)Given the dates t, t1, and t2, the fractions alfa1 and alfa2 are set by the following routine:
InterpolFractions( t, t1, t2, alfa1, alfa2, status ) type(TDate), intent(in) :: t type(TDate), intent(in) :: t1 type(TDate), intent(in) :: t2 real, intent(out) :: alfa1 real, intent(out) :: alfa2 integer, intent(out) :: status
To obtain a pretty formatted print of the value of a date, the 'Pretty' routine is provided. Output differs based on the calender type. Also implemented for date increments.
character(len=36) function Pretty( t ) type(TDate), intent(in) :: t calender output ---------------------------- ---------------------------------------- (date) 'wall' 1:23:45:678 03 feb 2001 (GMT+02:00) 'greg', '366', '365', '360' 2001/02/03 1:23:45:678 (date increment) 2 days 1:23:45:678
Two routines are provided to write messages including a date to the 'gol' buffer from the 'GO_Print' library. Add a call to 'goPr' or 'goErr' to actually display the message. Example:
call wrtgol( 'time : ', t ); call goPr call wrtgol( 'range : ', t1, ' - ', 't2' ); call goPrprovides:
[00] time : 2001/01/01 00:00:00:000 [00] range : 2001/01/01 00:00:00:000 - 2001/01/01 03:00:00:000
For setting some default values, the subroutine 'go_DateDefaults' is available. All arguments are optional. Yet, only the calender type might be set.
call goDateDefaults( [calender='greg'] )
The module is implemented in source file go_date.F90 .
Selects free file units.
Read lines from a commented text file, skipping comments and empty lines.
Access the entities of this module via the main module (prefered) or directly:
use GO, only : ... use GO_File, only : ...
Use the following routine to select a free (unopened) file unit:
call goGetFU( fu, status ) integer, intent(inout) :: fu integer, intent(out) :: statusThe routines searches for unopened file units with the range specified by 'goFuRange' from module 'GO_FU', excluding the standard units. If all units are already opened, an error message is returned.
A commented text file might look like:
! ! This is data file. ! ! number of data values: NDATA 3 ! data values: DATA 1.0 2.0 3.0To be able to read from this file without bothering about comments and empty lines, a special file type is introduced:
type(TTextFile) :: file
To open the file, use:
subroutine Init( file, 'data.txt', iostat, status, comment ) type(TTextFile), intent(out) :: file character(len=*), intent(in) :: filename integer, intent(out) :: iostat character(len=*), intent(in), optional :: status character(len=1), intent(in), optional :: commentThe 'iostat' argument is the 'usuall' status argument, thus on return non-zero in case of errors. The 'status' argument is the same as used by Fortran's 'open' command to specify wether a file should already exist (status='old'), should not exist yet and will be created ('new') or might exist or not ('unknown').
The optional comment is a single character; lines in the file that start with this character are skipped while reading.
To close the file, use:
subroutine file_Done( file, status ) type(TTextFile), intent(inout) :: file integer, intent(out) :: status
To read one line, skipping empty and comment lines, use:
subroutine ReadLine( file, s, status ) type(TTextFile), intent(inout) :: file character(len=*), intent(out) :: s integer, intent(out) :: statusReturn status<0 means that no values are read since end of file is reached; errors are indicated by status>0.
The module is implemented in source file go_file.F90 .
Read settings from a resource file.
In the GO library, an 'rcfile' or 'resource' file is a text file with settings for a program, with a format following the X-resource conventions:
! This is an example resource file. ! Use line of the form: ! ! <name> : <value> ! ! Some conventions: ! * comment lines start with '!' ! * logical values are 'T' or 'F' (without quotes ...) ! * character strings do not contain quotes prog.n : 20 input.file : /data/test.dat prog.debug : T
A type has been derived to get access to a rcfile:
type TrcFile
To open a rcfile, use:
subroutine Init( rcfile, fname, status ) type(TrcFile), intent(out) :: rcfile character(len=*), intent(in) :: fname integer, intent(out) :: statusTo close it again, use:
subroutine Done( rcfile, status ) type(TrcFile), intent(inout) :: rcfile integer, intent(out) :: status
To read the value asigned to a key, use:
subroutine ReadRc( rcfile, key, x, status, default ) type(TrcFile), intent(in) :: rcfile character(len=*), intent(in) :: key <type>, intent(out) :: x integer, intent(out) :: status <type>, intent(in), optional :: defaultCurrent implemented types:
<type> = integer|real|character(len=*)|logicalIf a key has not been found, an error messages is returned unless a default is provided.
The shell scripts 'go_readrc' and 'go_pprc' are available to read from a rcfile withing shell scripts and to preprocess the rcfile respectively.
The module is implemented in source file go_rc.F90 .
Interface to routines of some common system routines. These routines are not part of the Fortran standard, but almost always supplied. There might be subtile compiler (vendor) specific differences however, and therefor this module was made. Preprocessor statements in select the compiler specific code.
Currently only implemented for:
Access the entities of this module via the main module (prefered) or directly:
use GO, only : ... use GO_System, only : ...
subroutine goSystem( command, status ) character(len=*), intent(in) :: command integer, intent(inout) :: status end subroutine goSystem
subroutine goExit( status ) integer, intent(in) :: status end subroutine goExit
integer function goArgC() end function goArgC
subroutine goGetArg( nr, value, status ) integer, intent(in) :: nr character(len=*), intent(out) :: value integer, intent(out) :: status end subroutine goGetArg
The module is implemented in source file go_system.F90 .