Skip to main content
  • Home
  • Documentation
  • FAQ
  • Downloads
  • Support
  • Current Release Notes
  • Ferret Users Guide
    • Users Guide Index
    • Commands Reference
    • 1. Introduction
    • 2. Data Set Basics
    • 3. Variables & Expressions
    • 4. Grids & Regions
    • 5. Animations & Gif Images
    • 6. Customizing Plots
    • 7. Handling String Data Symbols
    • 8. Working with Special Data Sets
    • 9. Computing Environment
    • 10. Converting to NetCDF
    • 11. Writing External Functions
    • Glossary
    • Appendix A: Functions
    • Appendix B: PPLUS Guide
    • Appendix C: Ferret-Specific PPLUS Enhancements
  • Previous Release Notes
  • Tutorials and Demos
    • Ferret Tour
    • DSG files: Discrete Sampling Geometries Demo
    • Ferret sorting demo
    • Fast Fourier Transforms demo
    • Empirical Orthogonal Functions demo
    • Ferret objective analysis demo
    • Ferret Palette Demo
    • Map projections
    • Ferret polygon vector demo
    • Ferret Graticules demo
    • Ferret Polytube Demo
    • Ferret Polymark Demo
    • Ferret Constant-Array demo
    • Ferret land_detail demo
    • COADS Tour
    • Levitus Tour
    • Use OPeNDAP
    • Ferret binary read demo
  • PyFerret
    • PyFerret Downloads and Install ../../faq/ferret-faqs.html
    • What is PyFerret?
    • Why use PyFerret?
    • PyFerret for the Ferret user
    • PyFerret command syntax: quick-start notes
    • PyFerret for the Python user
    • Graphics in PyFerret ?
    • New Ferret functionality
    • PyFerret Python functions and constants
    • PyFerret Python objects and methods
    • Ferret external functions in Python
    • Ferret Fortran external functions
    • PyFerret metadata-and-data dictionaries
  • OPeNDAP
    • OPeNDAP usage in Ferret
    • Use OPeNDAP Demo
    • Test OPeNDAP

11.4 ANATOMY OF AN EXTERNAL FUNCTION


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. 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`