Go to the previous, next section.

Fortran Subroutines

The simplest interface between Ingrid and FORTRAN code is to write a subroutine that Ingrid calls. Ingrid supplies data from streams as the arguments to the subroutine, and Ingrid uses the data from output arguments to feed other streams. Unless there is a strongly compelling reason to write a main program, writing a subroutine is definitely the way to proceed.

This section gives two words. The first, FGIfunc, is the general way of creating a stream filter: you can create a filter that does arbitrarily complicated manipulations of streams. It requires that you write some Ingrid code that specifies how the grids, names, etc, of the streams should be manipulated.

The second word, FGIscalarFilter, makes it possible for Ingrid to call a FORTRAN scalar function, i.e. it is a function of N scalar arguments. Ingrid will apply this function to scalars or streams or a limited mixture.

FGIfunc

FGIfunc(NAME,SUB,NIN,NOUT)
( -- ) FGIfunc tells Ingrid how to call the subroutine SUB. NIN is the number of input streams, and NOUT is the number of output streams. NAME is defined within the current object.
Ultimately this is simple, but it requires a lot of explanation. Given a subroutine
SUB(PARAM, IN1, ..., INn, OUT1, ..., OUTm)
where IN1 is a chunk of data from the first input stream, IN2 is a chunk of data from the second stream, etc, and OUT1, ..., OUTm correspond to chunks of the output streams, we have the following FGIfunc call:
EXTERNAL SUB
...
CALL FGIfunc('sub',SUB,n,m)
This creates a word sub that has the following stack behavior:
( IN1 ... INn PARAM -- )
i.e. it takes n streams from the stack, IN1 through INn, and a parameter block PARAM. We now need to make m calls to NewBuffer, one for each output buffer. These streams will correspond to the output arrays of SUB.

This is actually less restrictive that it appears: PARAM can be any of several different types of Ingrid objects that have FORTRAN equivalents: integer, real, name, integerarray, realarray, namearray, and structures. IN1 ... INn can be any of those (except structure), but they can also be streams. OUT1 through OUTm are always streams.

For example, suppose we want to write a simple filter that operates on XY blocks of data, reading in an XY block and returning the same size XY block. And suppose we have a stream that is a function of XYT.

So we have a FORTRAN program

SUBROUTINE SUB(p,IN,OUT)
STRUCTURE /myS/
    INTEGER NX
    INTEGER NY
END STRUCTURE

RECORD /myS/ p

REAL IN(p.nx,p.ny) , OUT(p.nx,p.ny)

C given IN, we cleverly compute OUT
...

RETURN
END
In our main program, after we call createIngrid, we call FGIfunc,
EXTERNAL SUB
...
CALL FGIfunc('mysub',SUB,1,1)
Now suppose we have a stream called FRED that is a function of XYT. We can apply SUB to it as follows
FRED                    % our input stream FRED of XYT
X Y 2 REORDER           % this makes the first coordinate X,
                        % the second coordinate Y,
                        % and the stream is now in chunks of XY.
X >npts                 % gets the number of points from X
Y >npts                 % gets the number of points from Y
2 integerarray astore   % stores the two integers into an integer array
mysub                   % feeds the (possibly) reordered FRED into SUB
                        % with the parameters [ nx ny ]
LastStream              % since the grids of the output stream are the
                        % same as the input stream, we use it as a parent
                        % of our output stream.
X >npts Y >npts mul     % calculate the size of the output buffer
NewBuffer               % create a new buffer which is filled by SUB
X Y CONTOUR             % make a series of plots of the results
In practice, we present a clean interface to the outside world by writing a procedure, namely
mygreatsub
( stream -- stream' ) filters bad streams into good streams.
And we have the following definition
/mygreatsub { X Y 2 REORDER
              X >npts
              Y >npts
              2 integerarray astore
              mysub
              LastStream
              X >npts Y >npts mul
              NewBuffer
              (filtered by mygreatsub) addhistory
             } def
Then when we want to use it, we simply say
FRED           % the input stream
mygreatsub     % filtered by mygreatsub
X Y CONTOUR    % a series of contour plots

On the other hand, suppose we wanted to write the filter so that we could apply it to any two dimensions, not just X and Y. Then we would have a slightly different definitions of mygreatsub, namely

mygreatsubII
( stream grid1 grid2 -- stream' ) filters bad streams into good streams along grids grid1 and grid2.
And we have the following definition
/mygreatsubII { 2 REORDER
              X >npts
              Y >npts
              2 integerarray astore
              mysub
              LastStream
              X >npts Y >npts mul
              NewBuffer
              /bufferSIRecord SIRecord def
              (filtered by mygreatsub) addhistory
             } def
Then when we want to use it, we simply say
FRED                 % the input stream
X Y mygreatsubII     % filtered by mygreatsub
X Y CONTOUR          % a series of contour plots
A minor change in coding, but much more generality.

Though it is quit unecessary for this example, structures offer an alternative way of passing parameters to FORTRAN subroutines. The syntax becomes a bit cleaner, and we can pass a mix of integers, real numbers, strings, etc. The rewritten definition of mygreatsubII would be as follows:

First define an Ingrid structure that corresponds to the FORTRAN structure.

/mysubS structure
        /NX integer
        /NY integer
        endstructure
        def
Then make a new definition for mygreatsubII.
/mygreatsubII 
   { 2 REORDER             % use the two grids to REORDER the stream so
                           %   that the two fastest varying grids are the
                           %   grids we want to operate along.
     dup                   % we make an extra copy of the input stream
                           %   for NewBuffer (above we used LastStream).
       mysubS new          % create a new RECORD with structure mysubS
       store begin         % start storing data
        X >npts NX         % store NX
        Y >npts NY         % store NY
       end                 % end storing
       mysub               % feeds stream to FORTRAN subroutine mysub
     X >npts Y >npts mul   % number of points in output buffer
              NewBuffer    % uses copy of input stream and npts
                           %   to create the output buffer
     (filtered by mygreatsub) addhistory
  } def

FGIscalarFilter

FGIscalarFilter(NAME,FUNCTION,N,DEFS)
( -- ) defines NAME so that it applies the scalar function FUNCTION to n input streams, returning an output stream. NAME is defined within the current object.

NAME (stream_1 ... stream_n -- stream ) will then filter n input streams, applying DEFS to the output stream.

NAME (num_1 ... num_n -- real ) also works if all the input arguments are numbers, will return the result. In this case DEFS are not used.

NAME (stream_1 ... stream_m num_m+1 ... num_n) also works on sets of streams and numbers mixed, though all the streams have to come first.

NAME
Fortran character array which should contain the name the function will be called in Ingrid.
FUNCTION
Fortran REAL FUNCTION(A1, ... ,AN) which should be declared REAL*4. Must be declared EXTERNAL in order for the call to FGIscalarFilter to be successful.
N
number of arguments (all inputs, thus also the number of input streams).
DEFS
a string containing the commands to document. Usually '/name /functionname def'.

NAME combines the histories and fullnames of the input streams, prepending the name defined above (try one of the functions built in to Ingrid, like cos or sin).

Go to the previous, next section.