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 aaa
The 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 if
To 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 goPr
With 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; return
This will produce the following output:
[00] ERROR - this is a serious error ...
[00] ERROR - in program
At 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 bbb
This 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 ) ! sh159
Interface:
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) :: t
with 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) :: dt
with 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 NewDate
Use 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 goPr
provides:
[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) :: status
The 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.0
To 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 :: comment
The '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) :: status
Return 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) :: status
To 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 :: default
Current implemented types:
<type> = integer|real|character(len=*)|logical
If 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 .