When creating an external function, you can get Ferret to do a lot of conformability checking for you if you "inherit axes" properly. This means that Ferret can be responsible for making sure that the arguments you pass to the function are of the proper dimensionality to be combined together in basic operations such as addition, multiplication etc. For any given axis orientation, X, Y, Z, or, T, two arguments are said to be conformable on that axis if 1) they are either of the same length, or 2) at least one of the arguments has a size of 1 on the axis. ( The terminology "size of 1" may equivalently be thought of as a size of 0. In other words, the data is normal to this axis.) When Ferret encounters a problem it will send an error message rather than passing the data to your external function which might result in a crash.
To get Ferret to do this kind of checking you should inherit axes from as many appropriate arguments as possible. For instance, in subtract.F we have the following sections of code:
subtract_init(...)
...
CALL ef_set_axis_inheritance(id, IMPLIED_BY_ARGS,
. IMPLIED_BY_ARGS, IMPLIED_BY_ARGS, IMPLIED_BY_ARGS)
...
This means that the axes of the result, and the index range of the result on those axes, will be determined by arguments.
...
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)
...
Here we specify that each result axis is dependent upon the axes from both arguments. When Ferret sees this, it knows the arguments must be conformable before it passes them to the external function.
The advantages of this approach are best understood by thinking about this example function "MY_ADD_FUNCTION," which performs a simple addition:
LET arg1 = X[x=0:1:.1]
LET arg2 = Y[Y=101:102:.05]
LET my_result = MY_ADD_FUNCTION(arg1, arg2)
The desired outcome is that "my_result" is a 2-dimensional field which inherits its X axis from arg1 and its Y axis from arg2.
If arguments and result are on the same grid, you should inherit all axes from all arguments. In general, you should inherit axes from as many arguments as possible.
11.5.2 Loop indices
Note: Array indices need not start at 1.
Because the data passed to an external function is often a subset of the full data set, array indices need not start at 1.
Note: Indices on separate arguments are not necessarily the same.
This might occur, for instance, with variables from different data sets.
Because of this, we need to ask Ferret what the appropriate index values are for the result axes and for each axis of each argument. We also need to know whether the increment for each axis of each argument is 0 or 1. An increment of 0 would be returned, for example, as the Y axis increment of an argument which which was only defined on the X axis. The data for this argument would be replicated along the Y axis when needed in a calculation.
The following section of code from subtract.F retrieves the index and increment information:
...
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)
...
Once we have this information we must make sure that we don't mix and match indices. It's possible that you can write code which will work in the very simplest cases but will fail when you try something like:
yes? let a = my_func(sst[d=1],airt[d=2])
yes? plot a[l=@ave]
The solution is straightforward if not very pretty: Assign a separate index to each axis of each argument and index them all separately. The code in subtract.F shows how to do it with two arguments:
...
i1 = arg_lo_ss(X_AXIS,ARG1)
i2 = arg_lo_ss(X_AXIS,ARG2)
DO 400 i=res_lo_ss(X_AXIS), res_hi_ss(X_AXIS)
j1 = arg_lo_ss(Y_AXIS,ARG1)
j2 = arg_lo_ss(Y_AXIS,ARG2)
DO 300 j=res_lo_ss(Y_AXIS), res_hi_ss(Y_AXIS)
k1 = arg_lo_ss(Z_AXIS,ARG1)
k2 = arg_lo_ss(Z_AXIS,ARG2)
DO 200 k=res_lo_ss(Z_AXIS), res_hi_ss(Z_AXIS)
l1 = arg_lo_ss(T_AXIS,ARG1)
l2 = arg_lo_ss(T_AXIS,ARG2)
DO 100 l=res_lo_ss(T_AXIS), res_hi_ss(T_AXIS)
IF ( arg_1(i1,j1,k1,l1) .EQ. bad_flag(1) .OR.
. arg_2(i2,j2,k2,l2) .EQ. bad_flag(2) ) THEN
result(i,j,k,l) = bad_flag_result
ELSE
result(i,j,k,l) = arg_1(i1,j1,k1,l1) -
. arg_2(i2,j2,k2,l2)
END IF
l1 = l1 + arg_incr(T_AXIS,ARG1)
l2 = l2 + arg_incr(T_AXIS,ARG2)
100 CONTINUE
k1 = k1 + arg_incr(Z_AXIS,ARG1)
k2 = k2 + arg_incr(Z_AXIS,ARG2)
200 CONTINUE
j1 = j1 + arg_incr(Y_AXIS,ARG1)
j2 = j2 + arg_incr(Y_AXIS,ARG2)
300 CONTINUE
i1 = i1 + arg_incr(X_AXIS,ARG1)
i2 = i2 + arg_incr(X_AXIS,ARG2)
400 CONTINUE
...
For external functions we introduce the concept of "axis reduction." The result of an external function will have axes which are either RETAINED or REDUCED with respect to the argument axes from which they are inherited. By default, all result axes have their axis reduction flag set to RETAINED. Every result axis which has it axis inheritance flag set to IMPLIED_BY_ARGS will have the same extent (context) as the argument axis from which it inherits. Setting the axis reduction flag to REDUCED means that the result axis is reduced to a point by the external function.
The axis reduction flag only needs to be applied when the result is reduced to a point but SET REGION information should still be applied to the external function arguments. (e.g. a function returning a status flag) In such a case the result axes should be IMPLIED_BY_ARGS and REDUCED. (as opposed to NORMAL and RETAINED)
The percent_good_t.F function is a good example of where the axis reduction flag needs to be set. This function takes a 4D region of data and returns a time series of values representing the percentage of good data at each time point. Inside the percent_good_t_init subroutine we see that the X, Y and Z axes are reduced with respect to the incoming argument:
* *************************
* USER CONFIGURABLE PORTION
*
*
CALL ef_set_desc(id,
. '(demonstration function) returns % good data at each time' )
CALL ef_set_num_args(id, 1)
CALL ef_set_axis_inheritance(id, IMPLIED_BY_ARGS,
. IMPLIED_BY_ARGS, IMPLIED_BY_ARGS, IMPLIED_BY_ARGS)
CALL ef_set_axis_reduction(id, REDUCED, REDUCED, REDUCED,
. RETAINED)
CALL ef_set_piecemeal_ok(id, NO, NO, NO, NO)
arg = 1
CALL ef_set_arg_name(id, arg, 'A')
CALL ef_set_arg_desc(id, arg, 'data to be checked')
CALL ef_set_axis_influence(id, arg, YES, YES, YES, YES)
*
*
* USER CONFIGURABLE PORTION
* *************************
This arrangement allows the user to specify an X/Y/Z region of interest and have this region information used when the argument is passed to the function. If we had specified X/Y/Z as NORMAL axes, Ferret would have understood this to mean that all region information for these three axes can be ignored when the percent_good_t function is called. This is not what we want.
11.5.4 String Arguments
Ferret can pass strings to external functions. This may be useful if you are writing external functions to write a new output format, for example, and wish to pass the output filename as an argument.
By default, all arguments are assumed to be of type FLOAT_ARG. In the ~init subroutine, the external function must tell Ferret which arguments are to be handled as strings:
arg = 1
CALL ef_set_arg_type(id, arg, STRING_ARG)
CALL ef_set_arg_name(id, arg, 'message')
CALL ef_set_arg_desc(id, arg, 'String to be written when executing.')
CALL ef_set_axis_influence(id, arg, YES, YES, YES, YES)
In the ~compute subroutine, a pointer to the string argument is passed in and dimensioned as any other argument. A text variable must be declared and a utility function is used to get the actual text string. As an example:
SUBROUTINE string_args_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(1: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)
INTEGER res_lo_ss(4), res_hi_ss(4), res_incr(4)
INTEGER arg_lo_ss(4,1:EF_MAX_ARGS), arg_hi_ss(4,1:EF_MAX_ARGS),
. arg_incr(4,1:EF_MAX_ARGS)
CHARACTER arg1_text*160
* *************************
* USER CONFIGURABLE PORTION
*
INTEGER i,j,k,l
INTEGER i1, j1, k1, l1
CALL ef_get_arg_string(id, 1, arg1_text)
WRITE(6,49) arg1_text
49 FORMAT ('The text for arg1 is : ''',a,'''')
...