The general approach for extending the NCL function set is as follows:
ncl 0> system("ls " + ncargpath("lib"))If libncl.a is not one of the libraries listed by the above command contact your site administrator.
This section uses an example to demonstrate how to extend the function set. In this example we will write a function that evaluates a polynomials given a value and arrays of polynomial coefficients. The intended NCL function interface is:
function poly( a[*]:float, b[*]:float, c[*]:float, x[1]:float )
The following is the C source the function poly in a file called poly.c :
void poly_C(float *outval, float *a, float *b, float *c, float x, int n) { int i; for(i = 0; i < n ; i++) { outval[i] = (a[i] * x * x) + (b[i] * x) + c[i]; } }
void *NclGetArgValue( int arg_num, /* IN: argument number to retrieve */ int n_args, /* IN: total number of arguments */ int* n_dims, /* OUT: number of dimensions of parameter */ int* dimsizes, /* OUT: dimension sizes of parameter */ NclScalar* missing, /* OUT: missing value if any */ int *has_missing, /* OUT: whether or not a missing value is present in the parameter */ NclBasicDataTypes *type, /* OUT: data type of parameter */ int access_type /* IN: Either 0 for don't care, 2 for a read or 1 for a write*/ );
The input argument arg_num is the number of the argument requested. The arguments are numbered from left to right (as they appear in NCL) starting at 0 through n-1 where n is the total number of arguments to the function. The input argument n_args is the total number of arguments for the NCL function. The final input parameter is access_type. Access_type tells NCL whether or not the parameter being accessed will be modified or not. This is necessary to support NCL's pass-by-value parameter passing scheme.
The actual data associated with the parameter is returned as a void pointer by the NclGetArgValue function which must be cast to the appropriate type. The parameters n_dims and dimsizes represent the rank of the parameter. The type parameter is set to one of the following basic data types. NclBasicDataTypes is defined in "NclDataDefs.h".
typedef enum { NCL_none = 0, NCL_short = 01, NCL_int = 02, NCL_long = 04, NCL_float = 010, NCL_double = 020, NCL_char = 040, NCL_byte = 0100, NCL_string = 0200, NCL_numeric = 0400, NCL_logical = 01000, NCL_obj = 02000 } NclBasicDataTypes;
The contents of the has_missing parameter is set to 1 if the parameter possibly contains missing values. There are some situations where it is possible to have has_missing set and not actually have a missing value in the data. This occurs when a subsection of a variable, which has missing values, is subscripted and the portion subscripted does not actually contain missing values. However, if the has_missing parameter is set to 0 then there are no missing values. The value of the missing value is returned in the union NclScalar. NclScalar is defined in "NclDataDefs.h". It is a union of all the primitive types in NCL.
typedef union _NclScalar { double doubleval; float floatval; int intval; long longval; short shortval; char charval; string stringval; byte byteval; logical logicalval; obj objval; }NclScalar;
Finally return values are passed back to the NCL environment via the NclReturnValue. NclReturnValue provides and interface for passing back data. It currently does not support passing back coordinates arrays or attributes, with the exception of missing values.
The prototype for the NclReturnValues function is:
NhlErrorTypes NclReturnValue( void *value, /* Pointer to data to be returned */ int n_dims, /* Number of dimensions of the data */ int* dimsizes, /* Dimension sizes of the data */ NclScalar* missing, /* If non-NULL is the missing value for the data otherwise NULL if no missing values in data */ NclBasicDataTypes type, /* Ncl basic type identifier */ int copy_data /* True if data should be copied before returned */ );One major goal of designing the wrapper function should be to establish a scheme for handling missing values, if the function you are adding does not already handle them. In the following example missing values are filtered out and propagated through to the output.
The source for the wrapper function follows:
#include <stdio.h> /* * The following are the required NCAR Graphics include files. * They should be located in ${NCARG_ROOT}/include */ #include <ncarg/hlu/hlu.h> #include <ncarg/hlu/NresDB.h> #include <ncarg/ncl/defs.h> #include <ncarg/ncl/NclDataDefs.h> #include <ncarg/ncl/NclBuiltInSupport.h> /* * Declare C function version */ extern void poly_C(float *outval, float *a, float *b, float *c, float x, int n); NhlErrorTypes poly_W( void ) { float *a; int n_dims_a,dimsizes_a[NCL_MAX_DIMENSIONS],has_missing_a; NclScalar missing_a; float *b; int n_dims_b,dimsizes_b[NCL_MAX_DIMENSIONS],has_missing_b; NclScalar missing_b; float *c; int n_dims_c,dimsizes_c[NCL_MAX_DIMENSIONS],has_missing_c; NclScalar missing_c; float *x; NclScalar missing_x; float *output_val; NclScalar tmp_missing; int has_missing_x; int i; /* * Retrieve parameters */ /* * Note any of the pointer parameters can be set to NULL, which * implies you don't care about the its value. In this example * the type parameter is set to NULL because the function * is later registered to only accept floating point numbers. * Also the parameter x is later registered to be a single * dimension floating point value so dimension sizes and the * number of dimensions are not needed. */ a = (float*)NclGetArgValue( 0, 4, &n_dims_a, dimsizes_a, &missing_a, &has_missing_a, NULL, 2 ); b = (float*)NclGetArgValue( 1, 4, &n_dims_b, dimsizes_b, &missing_b, &has_missing_b, NULL, 2 ); c = (float*)NclGetArgValue( 2, 4, &n_dims_c, dimsizes_c, &missing_c, &has_missing_c, NULL, 2 ); x = (float*)NclGetArgValue( 3, 4, NULL, NULL, &missing_x, &has_missing_x, NULL, 2 ); /* * This is the only dimension size check needed since the function * is registered to only accept single dimension parameters. */ if((dimsizes_a[0] == dimsizes_b[0])&&(dimsizes_b[0] == dimsizes_c[0])) { /* * The case where x is a missing value should be handled. One option * is to print an error message and return NhlFATAL. However a better * option is to return an array of size n_dims_a filled with missing * values. */ if((has_missing_x) && (missing_x.floatval == *x)) { output_val = (float*)malloc(sizeof(float)*dimsizes_a[0]); for(i = 0; i < n_dims_a; i++) { output_val[i] = *x; } return(NclReturnValue( (void*)output_val, n_dims_a, dimsizes_a, &missing_x, NCL_float, 0 )); } /* * The following section allocates the output memory and calls the * poly function */ output_val = (float*)malloc(sizeof(float)*dimsizes_a[0]); poly_C(output_val,a,b,c,*x,dimsizes_a[0]); /* * A scheme for handling missing values in the input. NCL's algebra * handles missing value by propagating the missing value of the left * most term in an expression to the result whenever any term contains * a missing value. */ if(has_missing_a || has_missing_b || has_missing_c) { /* * Chooses suitable missing value by checking from left to right for * missing values. */ if(has_missing_a) { tmp_missing.floatval == missing_a.floatval; } else if (has_missing_b) { tmp_missing.floatval == missing_b.floatval; } else { tmp_missing.floatval == missing_c.floatval; } /* * Checks for missing values in the input and changes output value to missing * value if any of the terms contain missing values */ for(i = 0; i < dimsizes_a[0]; i++) { if((has_missing_a)&&(a[i] == missing_a.floatval)) { output_val[i] = tmp_missing.floatval; } if((has_missing_b)&&(b[i] == missing_b.floatval)) { output_val[i] = tmp_missing.floatval; } if((has_missing_c)&&(c[i] == missing_c.floatval)) { output_val[i] = tmp_missing.floatval; } } /* * Returns value to NCL */ return(NclReturnValue( (void*)output_val, n_dims_a, dimsizes_a, &tmp_missing, NCL_float, 0 )); } else { /* * Returns value to NCL */ return(NclReturnValue( (void*)output_val, n_dims_a, dimsizes_a, NULL, NCL_float, 0 )); } } else { NhlPError(NhlFATAL,NhlEUNKNOWN,"poly: the dimension sizes of parameters a, b and c must be identical"); return(NhlFATAL); } }
The next step in extending the NCL function set is to write a function called NclAddUserFuncs. This function takes no arguments and does not return anything. Its purpose is to allow the user to call NclRegisterFunc and NclRegisterProc to tell NCL the NCL string name of the function, what the function pointer to call is, how many parameters the function takes and what the type and dimensionality of the parameters are.
There are three C functions that must be called to register a C wrapper function as as an NCL built-in function. For each function being registered in NclAddUserFuncs the function NewArgs must be called to initialize an private array that will hold the descriptions of each parameter. NewArgs takes a single integer argument that is the number of arguments for the function to be registered. It returns a void pointer which must be used in subsequent calls to SetArgTemplate and NclRegisterFunc. The next function called is SetArgTemplate. It must be called for each parameter to the function. SetArgTemplate takes the pointer returned by NewArgs, the current parameter number, the parameters type, number of dimensions and dimension sizes as input. SetArgTemplate does not return a value it simply modifies the private array returned by NewArgs. Once each parameter has defined by SetArgTemplate then either the function NclRegisterFunc or NclRegisterProc can be called depending on whether the C function being added is meant to be an NCL function or procedure. The prototypes for these four functions are located in the include file NclBuiltIns.h which should be located in ${NCARG_ROOT}/include/ncarg/ncl:
void *NewArgs( int n /* Total number of parameters to function being registered */ ); void SetArgTemplate( void* args, /* Array returned by NewArgs */ int arg_num, /* Argument number being defined */ char* type_name, /* string representing type, set to NclANY if any type is accepted */ int n_dims, /* number of dimensions of argument, set to NclANY if any number of dimensions supported */ int* dimsizes /* dimension size for each dimension, set to NclANY if all dimension sizes are supported */ ); extern void NclRegisterFunc( NclPubBuiltInProcWrapper thefuncptr, /* The C wrapper function */ void* args, /* The array returned by NewArgs and initialized by successive calls to SetArgTemplate */ char* fname, /* String name of function to be used by NCL */ int n_args /* Total number of arguments */ );
The following is the source for the function NclAddUserFuncs. More that one user function can be registered from with-in this function.
#include <stdio.h> #include <ncarg/hlu/hlu.h> #include <ncarg/hlu/NresDB.h> #include <ncarg/ncl/defs.h> #include <ncarg/ncl/NclBuiltIns.h> /* * Declare wrapper function */ extern NhlErrorTypes poly_W( #if NhlNeedProto void #endif ); void NclAddUserFuncs #if NhlNeedProto (void) #else () #endif { void *args; int dimsizes[NCL_MAX_DIMENSIONS]; int nargs = 0; /* * Create private argument array */ args = NewArgs(4); /* * Configure first three parameters identically as single dimension float * arrays of any size */ SetArgTemplate(args,nargs,"float",1,NclANY);nargs++; SetArgTemplate(args,nargs,"float",1,NclANY);nargs++; SetArgTemplate(args,nargs,"float",1,NclANY);nargs++; /* * Configure fourth parameter as single scalar floating point value */ dimsizes[0] = 1; SetArgTemplate(args,nargs,"float",1,dimsizes);nargs++; /* * Register wrapper function pointer and argument templates */ NclRegisterFunc(poly_W,args,"poly",nargs); return; } /* * NOTE: the following function stubs must be included with the * source for function NclAddUserFuncs. Future releases of this * documentation will describe their uses. For now though NCL * WILL NOT compile WITHOUT them. */ void NclAddUserFileFormats #if NhlNeedProto (void) #else () #endif { return; } void NclAddUserHLUObjs #if NhlNeedProto (void) #else () #endif { return; }
nhlcc -o ncl ${NCARG_ROOT}/lib/libncl.a AddUserFuncs.c poly_w.c poly.c -L${NCARG_ROOT}/lib -lnetcdf -ldf -llAddUserFuncs.c contains the function NclAddUserFuncs and the function stubs NclAddUserFileFormats and NclAddUserHLUObjs, poly_w.c contains the wrapper function and poly.c contains the actual function source. The extra libraries on the end are for netcdf, HDF and lex respectively which are used by ncl. The following is a list of all libraries need in the order they should linked in to compile line:
libhlu.a HLU library libncarg_gksC.a NCAR Graphics gks C bindings library libncarg.a NCAR Graphics LLU library libncarg_gks.a NCAR Graphics GKS library libncarg_c.a NCAR Graphics common code libl.a System LEX library libnetcdf.a NetCDF library libX11.a X11 library libdf.a HDF library libF77.a NOTE: This library is part of the C-Fortran interface libraries it might be different for different architectures consult the FORTRAN programmers manual for the machine you are using. libI77.a NOTE: This library is part of the C-Fortran interface libraries it might be different for different architectures consult the FORTRAN programmers manual for the machine you are using. libU77.a NOTE: This library is part of the C-Fortran interface libraries it might be different for different architectures consult the FORTRAN programmers manual for the machine you are using. libm.a System Math library libc.a C library