wrapfort(n) 0.1 "Ftcl Library"
wrapfort - Quick wrapper for Fortran code
TABLE OF CONTENTS
SYNOPSIS
DESCRIPTION
EXAMPLES
COMMANDS
FORTRAN 95 ASPECTS
KEYWORDS
package require Tcl 8.4
package require wrapfort 0.1
Wrapfort has a simple goal: make it easy to wrap the
wealth of (numerical) software written in Fortran for use within Tcl
programs. It is in a way akin to Critcl which does similar
things for C, but Wrapfort does not necessarily involve
on-the-fly compilation (see the note below).
The goal of Wrapfort is achieved in the following way:
-
It makes a number of assumptions about the routines that need to be
wrapped. These greatly simplify the automatic generation of code to
interface between Fortran and Tcl.
-
It takes a high-level description of the arguments including not just
the type of the arguments, but also their role in the interface of the
routine.
-
Whatever it can not handle automatically, it leaves to you to provide
the details.
This document describes the input to Wrapfort in detail, but
to understand how to use it, you must have some idea of how it works and
what ideas lie behind the generated code. Therefore we start with a
few examples.
Note:
Wrapfort does not differ that much in its approach to Critcl.
In a next version it will be integrated with Critcl.
The difference right now is that Wrapfort prepares all the
files for building a shared library or DLL that can be loaded into Tcl
as any loadable extension, but it does not actually start the build
process. That way, you can select your own compiler and linker for
instance.
The first example is very simple:
-
We have a Fortran routine that looks like this:
|
subroutine simple( x, y )
integer :: x, y
y = 2*x
return
end
|
-
We want to access this routine from within Tcl and the interface we want
for the simple command is:
|
set x 3
set y [simple $x]
puts $y ;# Gives 6
|
instead of:
|
set x 3
simple $x y
puts $y ;# Gives 6
|
which does not seem very Tclish.
-
So, rather than change an argument, we want to return the value that the
Fortran routine assigns to its second argument.
We can easily do this with Wrapfort:
-
The interface of the routine has no aspects specific to Fortran 95 (the
body does, but that is a mere syntactical detail).
-
The routine is not contained in a module. If that was the case, it would
be difficult to access it from C. (See FORTRAN 95 ASPECTS
for more information.) Subroutines or functions that use derived types,
assumed-shape arrays (but not assumed-size arrays) and so on also require
some more work.
We use the fproc command to generate a C function that can be
used as a Tcl command. This C function calls the Fortran routine to the
actual work:
|
Wrapfort::fproc simple simple {
integer x input
integer y result
code {Actually call the routine in this way} {
simple( &x, &y );
}
}
|
The first argument is the name of the corresponding Tcl command, the
second argument is the name of the Fortran routine (they do not need to
be the same). The third argument describes how the Tcl command must be
generated:
-
The line "integer x input" defines the variable x to be of integer type
and to be input to the Tcl command.
-
The second line, "integer y result" indicates that variable y must be
used to set the result of the Tcl command. It does not appear in the
argument list for the Tcl command.
-
The last part is simply a literal piece of C code that does no more
than call the Fortran routine in the right way (notice the ampersands).
What happens when you run this is that fproc generates C term to
store the contents of the first (and only) argument to the Tcl command
in the local variable x. Then the Fortran routine simple is
called, thus setting the local variable y to a value twice that of x.
As y is associated with the result of the Tcl command, the last part of
the C code sets the result field in the Tcl interpreter to just that
value.
Here is the result:
|
/* Wrapper for simple - Tcl-command Simple */
#ifdef FTN_UNDERSCORE
# define simple simple_
#endif
#ifdef FTN_ALL_CAPS
# define simple SIMPLE
#endif
void __stdcall simple();
static int c__simple( ClientData client_data, Tcl_Interp *interp,
int objc, struct Tcl_Obj * CONST objv[] ) {
long x;
long y;
if ( objc != 2 ) {
Tcl_SetResult( interp, "Wrong number of arguments", NULL );
return TCL_ERROR;
}
if ( Tcl_GetLongFromObj( interp, objv[1], &x ) != TCL_OK ) {
Tcl_SetResult( interp, "Argument 1 must be integer", NULL );
return TCL_ERROR;
}
simple( &x, &y );
Tcl_SetObjResult( interp, Tcl_NewLongObj(y) ) ;
/* Nothing to be done for: x */
/* Nothing to be done for: y */
return TCL_OK;
}
|
The fproc command not only takes care of extracting the values
from
the Tcl_Objs that are passed and putting the result back, but also of
checking the number of arguments, types of the arguments and cleaning
up afterwards.
It also takes care of most platform-dependent aspects of interfacing C
and Fortran.
The final result is a set of source files and a makefile that may need
some tuning (select the right C and Fortran compilers) and then all is
ready to build the DLL or shared library.
This was a particularly simple example. Let us look at something more
complicated and practical: the MINPACK library (see: http://www.netlib.org).
The MINPACK library is a typical example of a set of numerical routines
that can be very useful for a Tcl program too. It is also typical in the
way the interfaces for such libraries have been designed. MINPACK is
written in FORTRAN 77 and therefore you need to pass it several work
arrays.
One routine in this library is HYBRD1. It attempts to find a
root of a system of N (non-)linear equations in N variables. This
routine has the following signature:
|
SUBROUTINE HYBRD1(FCN,N,X,FVEC,TOL,INFO,WA,LWA)
|
where:
-
FCN is a subroutine implementing aspects of the sytem of equations for
which we want to find a root.
-
N is the number of elements in the vector X (the dimension of the vector
space covered by the subroutine FCN)
-
X is the initial estimate of the root and on return the final
result.
-
FVEC is an array which holds the function value at X during the
computation and at the end.
-
TOL is the tolerance parameter: the computation stops when the relative
error is smaller than this value.
-
INFO is a flag that indicates how the computation finished. There are
five different values, each documented in the MINPACK documentation.
-
WA is a work array needed to hold intermediate results
-
LWA is the length of the work array, which must be at least
(N*(3*+13))/2.
Before we go on and describe the subroutine FCN, let us consider what a
Tcl command wrapping this routine would have to look like. The
subroutine FCN would become a Tcl procedure, the initial vector X would
be a list of numbers, TOL could be an optional argument, defaulting to,
say, 1.0e-6. We do not want to make the work arrays WA and FVEC visible,
so they will be handled internally (as will the parameters N and LWA).
Rather than override the vector X with the final solution, we will
return that solution as a list.
This means that the procedure, let us call it "findRoot", looks like:
|
proc findRoot {fcn xinit {tol 1.0e-6}} {
...
return $xfinal
}
|
One issue remains: INFO. We have to decide how to provide the
information stored in this parameter. Let us take a simple approach:
The values 1 (found a solution) and 3 (tolerance too small) are regarded
as good results. Anything else simply throws an error.
Here is a call to fproc to generate the wrapper for this routine:
|
Wrapfort::fproc findRoot hybrd1 {
external fcn {}
double-array x input
double tol {optional-input 1.0e-6}
double-array fvec {allocate size(x)}
integer n {assign size(x)}
double-array wa {allocate (n*(3*n+13))/2}
integer info local
integer lwa {assign size(wa)}
double-array xfinal {result size(x)}
integer i local
code {Actually call the routine in this way} {
hybrd1( fcn, &n, x, fvec, &tol, &info, wa, &lwa );
for (i = 0; i < n; i ++) {
xfinal[i] = x[i];
}
/* Check the result - set a message if there was an error */
if ( info != 1 && info != 3 ) {
WrapErrorMessage( "No satisfactory result achieved" );
}
}
}
|
Most of this should be clear from the description of the routine given
above. We could have used the keyword input-output for the
variable x, but by introducing the new array xfinal instead, we
demonstrate that you can do more in the code section than just call the
routine - and how to use local variables.
On return from the routine hybrd1 we check the variable info and set an
error message via the macro WrapErrorMessage. This uses the predefined
variable _rc_ to record the error. The wrapper routine continues its
work, frees any allocated memory and then returns the right return code.
The second part of generating a wrapper for HYBRD1 is a bit more
involved: the external routine FCN. It has a straightforward signature:
|
SUBROUTINE FCN(N,X,FVEC,IFLAG)
INTEGER N,IFLAG
DOUBLE PRECISION X(N),FVEC(N)
----------
CALCULATE THE FUNCTIONS AT X AND
RETURN THIS VECTOR IN FVEC.
----------
RETURN
END
|
The variable IFLAG should not be changed, unless you want to stop the
computation, in which case it should be set to a negative value.
But we want to pass the name of a Tcl procedure. So the task of the
generator is:
-
Create a routine with the above signature that can be called from
Fortran. This routine will have a fixed name.
-
Within this routine call the Tcl procedure that was passed to the Tcl
command "findRoot" with the appropriate arguments.
The Tcl procedure could look like this:
|
proc fcn {x} {
... Compute the values of each function and return as a list.
... If we have to decide the computation is not going well,
... return an error
}
|
Now, the fexternal command will generate the source term to do
this:
|
Wrapfort::fexternal fcn {
fortran {
integer n input
double-array x {input n}
double-array fvec {output n}
integer iflag output
}
toproc {
x input
fvec result
}
onerror {
iflag = -1;
}
}
|
It is important to note that the name "fcn" given as the first argument
must match the name of the external procedure given in the call to fproc.
This is the way the various Fortran and C routines and functions "know"
what to call.
As an aside: the interface for fexternal is different than that
for fproc, because fexternal has to generate two
very different interfaces. It is easier to distinguish what goes where
in this way. Note that the variables x and fvec mentioned in the
fortran and toproc sections are the same!
The Wrapfort package defines the following commands:
- ::Wrapfort::fsource pkgname filename
-
Set the name of the C source file to be generated. Should be used before
any other Wrapfort commands, as it opens the source file for writing.
- string pkgname (in)
-
Name of the package (to be used in the "package require" command)
- string filename (in)
-
Name of the C source file that will be written
- ::Wrapfort::fproc tclname fortname description
-
Create a wrapper function/Tcl command to a Fortran routine.
- string tclname (in)
-
Name of the Tcl command to be created
- string fortname (in)
-
Name of the Fortran routine to be wrapped
- list description (in)
-
List of triples, each describing an aspect of the interface (see below
for more details)
- ::Wrapfort::fexternal interface description
-
Create a wrapper routine for the given interface, so that a Tcl command
can be used.
- string interface
-
Name of the interface to be wrapped. It must be the name of
the dummy routine argument in the Fortran routine, generated via fproc
that will call this procedure. The call to fexternal must
come first (to get proper C code).
- list description
-
List of sections with triplets each decribing an aspect of the interface
(see below for more details)
The description of the Fortran routine for the [fproc] command
consists of a list of elements grouped in triplets:
-
The first is used as the data type of the Fortran argument or local
variable:
-
integer, real, double, string and logical are the basic types,
corresponding to the obvious Fortran types.
-
integer-vector, real-vector and double-vector represent one-dimensional
arrays on the Fortran side, lists on the Tcl side.
-
integer-matrix, real-matrix and double-matrix represent two-dimensional
arrays on the Fortran side, lists of lists on the Tcl side (each
element is supposed to have the same length).
A special value is "code" - it is used to specify the C code that will
call the Fortran routine with the right arguments.
-
The second is the name of the argument or variable. If the "type" is
"code", then this part is ignored. You can use it then for some
additional comments.
-
The last part is a specification of how to treat the argument/variable.
The first word is one of:
-
input - This indicates an input argument for the Fortran routine
-
optional-input - This indicates an input argument for the Fortran
routine that is optional on the Tcl side (should therefore be one of the
last arguments). The second word is the default value.
-
result - This indicates that after the call to the Fortran routine the
value of this variable/argument must be copied into the result field, in
other words: the return value of the Tcl command.
If it is a vector or an array, the second word must indicate the size
(possibly via an expression using the size() function).
-
local - The variable is only used locally for some auxiliary
computations or it is part of the interface of the Fortran routine, but
it is not passed on to the Tcl side.
-
allocate - The variable is a local work array, for passing information
to and from the Fortran routine. The C code should take care of filling
it with the correct information, but allocation and deallocation is
automatically taken care of. The second word should indicate the size of
the array.
-
assign - Assign the value of the expression in the second word at
initialisation.
For the [fexternal] command the description is somewhat more
involved, as it consists of several parts. Each part consists of
a keyword and a list containing the detailed description of that part.
The keywords are:
-
fortran - description of the Fortran API (the arguments and their types
and sizes)
-
toproc - description of the Tcl procedure that will be called (the
arguments and their role)
-
onerror - a fragment of C code, to be run if the Tcl procedure
throws an error
-
prolog, epilog, localvars - PM
The description of the fortran section consists of triplets
again:
-
The first item is the data type expected by the Fortran routine.
-
The second is the name of the argument.
-
The third indicates the role (input or output) and, if case of an array,
it is a list with teh role keyword and an expression for the size of the
array.
The toproc section has a list of names and roles (input or
result).
And the onerror section contains a fragment of C term that will
be called if the Tcl procedure throws an error. This way the Fortran
routine that is called can handle the error appropriately.
In order to successfully use the Wrapfort package, you need to
know a few things about the interfacing between Fortran and C.
The easiest situation is that the Fortran interface uses only FORTRAN 77
features:
-
All arrays are assumed-size, not assumed-shape
-
No derived types are passed
-
The routine is not contained in a module
In that case there are only three things to worry about:
-
The naming conventions and the calling conventions may differ. This is
taken care of by the generated code.
-
Passing strings requires an extra hidden argument. The position of this
argument depends on the Fortran compiler.
Note: This is something that Wrapfort currently does
not handle.
-
Fortran passes all variables by reference, whereas C passes variables by
reference or by value. This means that in the code fragment you specify
with the [fproc] command, you need to use the ampersand (&) in the
right places - namely, with every scalar argument.
If you want to wrap a Fortran routine that uses any of the Fortran 90/95
features that can not be used from C, then you will have to supply an
interface routine written in Fortran that takes care of this. For
instance:
|
subroutine handle_array( array )
real, dimension(:) :: array
...
end subroutine
|
will have to wrapped via a second routine:
|
subroutine handle_array_f77( n, array )
integer :: n
real, dimension(n) :: array
!
! The interface is needed because of the dimension(:)
!
interface
subroutine handle_array( array )
real, dimension(:) :: array
end subroutine handle_array
end interface
call handle_array( array )
end subroutine
|
This technique is also required, if the routine is contained in a
module:
|
subroutine handle_array_f77( n, array )
use array_handling ! Contains handle_array
integer :: n
real, dimension(n) :: array
call handle_array( array )
end subroutine
|
Fortran, generating code, interface, numerical libraries