Every Ferret external function contains an ~_init subroutine which describes the external function's arguments and result grid and a ~_compute subroutine which actually performs the calculation. If the code you wish to implement is written in C, then the ~_compute function will be a Fortran wrapper that calls the C routine. Three other subroutines are available for requesting memory allocation; creating axis limits for the result variable which are extended with respect to the defined region (useful for derivative calculations, etc.); and creating custom axes for the result.
For the following discussion we will assume that our external function is called efname (with source code in a file named efname.F). Examples are also taken from the external functions examples/ directory which you installed when you downloaded the external functions code. This section will briefly describe the work done by the ~_init and ~_compute subroutines. The individual utility functions called by these subroutines are described in the section on Utility Functions below.
When you name your external functions, be aware that Ferret will search its internal function names before the external function names. So if you use a name that is already in use, your function will not be called. Use SHOW FUNCTION from Ferret to list the names that already are in use
11.4.1 The ~_init subroutine (required)
subroutine efname_init (id)
This subroutine specifies basic information about the external function. This information is used when Ferret parses the command line and checks the number of arguments; when it generates the output of SHOW FUNCTION/EXTERNAL; and in determining the result grid.
The following code from examples/subtract.F shows a typical example of an ~_init subroutine. For an example with more arguments please look at examples/add_9.F. For an example where a result axis is reduced with respect to the equivalent input axis take a look at examples/percent_good_t.F.
SUBROUTINE subtract_init(id)
INCLUDE 'ferret_cmn/EF_Util.cmn'
INTEGER id, arg
* *************************
* USER CONFIGURABLE PORTION
*
CALL ef_set_desc(id,'(demonstration function) returns: A - B' )
CALL ef_set_num_args(id, 2) ! Maximum allowed is 9
CALL ef_set_axis_inheritance(id, IMPLIED_BY_ARGS,
. IMPLIED_BY_ARGS, IMPLIED_BY_ARGS, IMPLIED_BY_ARGS)
CALL ef_set_piecemeal_ok(id, NO, NO, NO, NO)
arg = 1
CALL ef_set_arg_name(id, arg, 'A')
CALL ef_set_axis_influence(id, arg, YES, YES, YES, YES)
arg = 2
CALL ef_set_arg_name(id, arg, 'B')
CALL ef_set_axis_influence(id, arg, YES, YES, YES, YES)
*
* USER CONFIGURABLE PORTION
* *************************
RETURN
END
11.4.2 The ~_compute subroutine (required)
subroutine efname_compute (id, arg_1, arg_2, ..., result, wkr_1, wrk_2, ...)
This subroutine does the actual calculation. Arguments to the external function and any requested working storage arrays are passed in. Dimension information for the subroutine arguments is obtained from Ferret common blocks in ferret_cmn/EF_mem_subsc.cmn. The mem1lox:mem1hix, etc. values are determined by Ferret and correspond to the region requested for the calculation.@Body Text = In the ~_compute subroutine you may call other subroutines which are not part of the efname_compute.F source file.
SUBROUTINE subtract_compute(id, arg_1, arg_2, result)
INCLUDE 'ferret_cmn/EF_Util.cmn'
INCLUDE 'ferret_cmn/EF_mem_subsc.cmn'
INTEGER id
REAL bad_flag(EF_MAX_ARGS), bad_flag_result
REAL arg_1(mem1lox:mem1hix, mem1loy:mem1hiy,
. mem1loz:mem1hiz, mem1lot:mem1hit)
REAL arg_2(mem2lox:mem2hix, mem2loy:mem2hiy,
. mem2loz:mem2hiz, mem2lot:mem2hit)
REAL result(memreslox:memreshix, memresloy:memreshiy,
. memresloz:memreshiz, memreslot:memreshit)
* After initialization, the 'res_' arrays contain indexing information
* for the result axes. The 'arg_' arrays will contain the indexing
* information for each variable's axes.
INTEGER res_lo_ss(4), res_hi_ss(4), res_incr(4)
INTEGER arg_lo_ss(4,EF_MAX_ARGS), arg_hi_ss(4,EF_MAX_ARGS),
. arg_incr(4,EF_MAX_ARGS)
* **************************
* USER CONFIGURABLE PORTION
*
INTEGER i,j,k,l
INTEGER i1, j1, k1, l1
INTEGER i2, j2, k2, l2
CALL ef_get_res_subscripts(id, res_lo_ss, res_hi_ss, res_incr)
CALL ef_get_arg_subscripts(id, arg_lo_ss, arg_hi_ss, arg_incr)
CALL ef_get_bad_flags(id, bad_flag, bad_flag_result)
...
*
* USER CONFIGURABLE PORTION
* *************************
RETURN
END
Please see the "Loop Indices" section for the example calculation.4.3
11.4.3 The ~_work_size subroutine (required when work arrays are defined)
This routine allows the external function author to request that Ferret allocate memory (working storage) for use by the external function. The memory allocated is passed to the external function when the ~compute subroutine is called. The working storage arrays are assumed to be REAL*4 arrays; adjust the size of the arrays for other data types. See the sample code under ef_get_coordinates for an example of allocating a REAL*8 work array. The working storage is deallocated after the ~compute subroutine returns.
When working storage is to be requested, a call to ef_set_num_work_arrays must be in the ~init subroutine:
SUBROUTINE efname_init (id)
...
CALL ef_set_num_work_arrays (id,2)
A maximum of 9 work arrays may be declared. At the time the ~work_size subroutine is called by Ferret, any of the utility functions that retrieve information from Ferret may be used in the determination of the appropriate working storage size.
Here is an example of a ~work_size subroutine:
SUBROUTINE efname_work_size(id)
INCLUDE 'ferret_cmn/EF_Util.cmn'
INCLUDE 'ferret_cmn/EF_mem_subsc.cmn'
INTEGER id
*
* ef_set_work_array_lens(id, array #, X len, Y len, Z len, T len)
*
INTEGER nx, ny, id
INTEGER arg_lo_ss(4,1:EF_MAX_ARGS), arg_hi_ss(4,1:EF_MAX_ARGS),
. arg_incr(4,1:EF_MAX_ARGS)
CALL ef_get_arg_subscripts(id, arg_lo_ss, arg_hi_ss, arg_incr)
NX = 1 + (arg_hi_ss(X_AXIS,ARG1) - arg_lo_ss(X_AXIS,ARG1))
NY = 1 + (arg_hi_ss(Y_AXIS,ARG1) - arg_lo_ss(Y_AXIS,ARG1))
CALL ef_set_work_array_lens(id,1,NX,NY,1,1)
CALL ef_set_work_array_lens(id,2,NX,NY,1,1)
RETURN
In the argument list of the ~compute subroutine, the work array(s) come after the result variable. Declare the workspace arrays using index bounds wrk1lox:wrk2hix, ... which were set by the ef_set_work_array_lens call above.
SUBOUTINE efname_compute (arg_1, result, workspace1, workspace2)
...
* Dimension the work arrays
REAL workspace1(wrk1lox:wrk1hix, wrk1loy:wrk1hiy,
. wrk1loz:wrk1hiz, wrk1lot:wrk1hit)
REAL workspace2(wrk2lox:wrk2hix, wrk2loy:wrk2hiy,
. wrk2loz:wrk2hiz, wrk2lot:wrk2hit)
11.4.4 The ~_result_limits subroutine (required if result has a custom or abstract axis)
The result limits routine sets the limits on ABSTRACT and CUSTOM axes created by the external function.
An example ~result_limits routine might look like this:
SUBROUTINE my_result_limits(id)
INCLUDE 'ferret_cmn/EF_Util.cmn'
INTEGER id, arg, NF
*
INTEGER arg_lo_ss(4,EF_MAX_ARGS), arg_hi_ss(4,EF_MAX_ARGS),
. arg_incr(4,EF_MAX_ARGS)
INTEGER lo, hi
CALL ef_get_arg_subscripts(id, arg_lo_ss, arg_hi_ss, arg_incr)
arg = 1
lo = 1
hi = (arg_hi_ss(T_AXIS,arg) - arg_lo_ss(T_AXIS,arg) + 1)/ 2
call ef_set_axis_limits(id, T_AXIS, lo, hi)
RETURN
END
11.4.5 The ~_custom_axes subroutine (required if result has a custom axis)
The ~custom_axes subroutine allows the external function author to create new axes that will be attached the the result of the ~compute subroutine. An example of such a function might take time series data with a time axis and create, as a result, a Fourier transform with a frequency axis.
The ~custom_axes subroutine must be used with care because not all the Ferret internal information is available to the external function at the time Ferret calls this routine. Ferret must determine the grid on which a variable is defined before it actually evaluates the variable. This is fundamental to the delayed evaluation framework -- the aspect of Ferret that makes it possible to work with multi-gigabyte data sets while having minimal awareness of memory limitations. The ~custom_axes routines are called at the time that Ferret determines grid. Certain types of information are not available to Ferret (or to you, as author of an external function) during this time. The information which is not available is
1. the values of arguments to the function (capability to get the value of a scalar argument is being implemented for a future version)
2. context information specified with SET REGION
3. context information set with command qualifiers such as
CONTOUR/X=130e:80w
Items two and three are because this information is mutable -- it may be changed when the function is actually invoked.
The context information which IS available is
1. information that is actually contained in the function call, such as the X limits of
LET myvar = MY_EFN(v[x=130e:80w])
2. information that is embedded in nested variable definitions, such as the X limits of
LET tmp_var = v[x=130e:80w]
LET myvar = MY_EFN(tmp_var)
If no context information is available through these means then the context information supplied by the call to ef_get_arg_subscripts will be the full span (low and high limits) of the relevant axes.
If your axis is a calendar time axis, the units that you specified in the call to ef_set_custom_axis should be the time-increment units only, e.g. "year", "month", "minute". The time origin must be set after calling the function. See the last example below.
Examples:
You can set an axis explicitly in subroutine my_fcn_custom_axes:
SUBROUTINE my_fcn_custom_axes(id)
INCLUDE 'ferret_cmn/EF_Util.cmn'
INTEGER id
CALL ef_set_custom_axis(id, T_AXIS, 0.0, 1000.0, 25.0, 'Hertz', NO)
RETURN
END
Also, you can define an axis using information about the argument, as in the FFT functions which set up a frequency axis based on the input time axis (somewhat simplified here):
SUBROUTINE ffta_sample_custom_axes(id)
INCLUDE 'ferret_cmn/EF_Util.cmn'
INTEGER id
INTEGER nfreq_lo_l, nfreq_hi_l
INTEGER arg_lo_ss(4,EF_MAX_ARGS), arg_hi_ss(4,EF_MAX_ARGS),
. arg_incr(4,EF_MAX_ARGS)
INTEGER arg
INTEGER nfreq, nd
REAL yquist, freq1, freqn
REAL boxsize(1)
arg = 1
CALL ef_get_arg_subscripts(id, arg_lo_ss, arg_hi_ss, arg_incr)
CALL ef_get_box_size(id, arg, T_AXIS, arg_lo_ss(T_AXIS,arg),
. arg_lo_ss(T_AXIS,arg), boxsize)
nfreq_lo_l = arg_lo_ss(T_AXIS,arg)
nfreq_hi_l = arg_hi_ss(T_AXIS,arg)
nd = abs(nfreq_hi_l - nfreq_lo_l) + 1
nfreq = nd/2
yquist = 1./(2.*boxsize(1)) ! Nyquist frequency
freq1 = 1.* yquist/ float(nfreq)
freqn = 1.001*yquist
C Set label for the frequency axis CYC/units.
outunits = 'cyc/day'
CALL ef_set_custom_axis (id, T_AXIS, freq1, freqn, freq1, outunits, NO)
RETURN
END
If the function defines a time axis, then the time origin may be defined after calling the function.
SUBROUTINE my_fcn_custom_axes(id)
INCLUDE 'ferret_cmn/EF_Util.cmn'
INTEGER id
...
CALL ef_set_custom_axis(id, T_AXIS, loval, hival, del, 'days', NO)
RETURN
END
Then in the Ferret session, call the function to define a variable on this time axis, and then define its time origin.
yes? LET var = my_fcn(arg1, arg2)
yes? SET AXIS/T0="1-jan-2000" `var,RETURN=taxis`