Using User-Defined Models

Defining and Using a C Language External Function

The PROTO procedure enables you to register, in batch, external functions written in the C or C++ programming languages for use in SAS. In order to use an external function, it must be called from a SAS language function or subroutine. For more information about the PROTO procedure, see the Base SAS Procedures Guide.

For example, the following SAS statements create a user-defined forecasting model for a simple linear trend line called LINEARTREND_C that is written in the C language and store this external function in the catalog WORK.CHPFUSER.

proc proto package=work.chpfuser.cfuncs;
   double lineartrend_c( double   zvalue,
                         double * actual,
                         int      actualLength,
                         double * predict,
                         double * std,
                         double * lower,
                         double * upper,
                         int      predictLength );
   externc lineartrend_c;
      double lineartrend_c( double   zvalue,
                            double * actual,
                            int      actualLength,
                            double * predict,
                            double * std,
                            double * lower,
                            double * upper,
                            int      predictLength ) {

      int      t, n;
      double   value, sumxy, sumx, sumx2, sumy, sumy2;
      double   det, constant, slope, stderr, width;
      double   error, sume2;
      n      = 0;
      sumx   = 0;
      sumx2  = 0.;
      sumxy  = 0.;
      sumy   = 0.;
      sumy2  = 0.;
      for ( t = 0; t < actualLength; t ++ ) {
         value = actual[t];
         n     = n     + 1;
         sumx  = sumx  + t;
         sumx2 = sumx2 + t*t;
         sumxy = sumxy + t*value;
         sumy  = sumy  + value;
         sumy2 = sumy2 + value*value;
      }

      det = (n*sumx2 - sumx*sumx);
      constant = (sumx2 * sumy - sumx * sumxy) / det;
      slope    = (-sumx  * sumy + n    * sumxy) / det;
      for ( t = 0; t < predictLength; t ++ ) {
         predict[t] = constant + slope*t;
      }

      sume2 = 0;
      for ( t = 0; t < actualLength; t ++ ) {
         value = actual[t];
         error = value - predict[t];
         sume2  = sume2 + error*error;
      }

      stderr   = sqrt(sume2 / (n-2.));
      width    = zvalue*stderr;
      for ( t = 0; t < predictLength; t ++ ) {
         std[t]     = stderr;
         lower[t]   = predict[t] - width;
         upper[t]   = predict[t] + width;
      }
   return(0);
   }
externcend;
run;

The following SAS statements create a user-defined forecasting model for a simple linear trend called LINEARTREND and store this subroutine in the catalog WORK.HPFUSER. The catalog WORK.CHPFUSER contains functions or subroutines that are used in LINEARTREND. The user-defined forecasting model has the following subroutine signature:

SUBROUTINE <SUBROUTINE-NAME> ( <ARRAY-NAME>[*], <ARRAY-NAME>[*],
                               <ARRAY-NAME>[*], <ARRAY-NAME>[*],
                               <ARRAY-NAME>[*] );

where the first array, ACTUAL, contains the time series to be forecast, the second array, PREDICT, contains the return predictions, the third array, STD, contains the returned prediction standard errors, the fourth array, LOWER, contains the return lower confidence limits, and the fifth array, UPPER, contains the return upper confidence limits. The LINEARTREND subroutine calls the external function LINEARTREND_C. The DIM function returns the length of the array. For example, the DIM(ACTUAL) function returns the length of the time series array; and the DIM(PREDICT) returns the length of the prediction array. DIM(PREDICT) DIM(ACTUAL) represents the forecast horizon or lead.

proc fcmp outlib=work.hpfuser.funcs
          inlib=work.chpfuser;
   subroutine lineartrend( actual[*], predict[*],
                           std[*], lower[*], upper[*] );
      outargs actual, predict, std, lower, upper;
      zvalue = probit(1-0.05/2);
      ret = lineartrend_c( zvalue, actual, DIM(actual),
                           predict, std, lower, upper, DIM(predict));
   endsub;
quit;

In order to use a user-defined forecasting model, an external forecast model specification must be specified using the HPFEXMSPEC procedure. For example, the following SAS statements create an external model specification called LINEARTREND and store this model specification in the catalog WORK.MYREPOSITORY. Since the user-defined forecasting model uses two parameters, CONSTANT and SLOPE, the NPARMS=2 option is specified in the EXM statement. The number specified in this option is used in computing statistics of fit.

proc hpfexmspec modelrepository=work.myrepository
                specname=lineartrend
                speclabel="User defined linear trend";
   exm nparms=2;
run;

The HPFSELECT procedure can be used to create a model selection list that contains an external model specification as a possible candidate model. For example, the following SAS statements create a model selection list called MYSELECT and store this model selection list in the catalog WORK.MYREPOSITORY. The keyword _PREDICT_ indicates the returned predictions, the keyword _STDERR_ indicates the returned prediction standard errors, the keyword _LOWER_ indicates the returned lower confidence limits, and the keyword _UPPER_ indicates the returned upper confidence limits.

proc hpfselect modelrepository=work.myrepository
               selectname=myselect
               selectlabel="My Select List";
   spec lineartrend /
      exmfunc(
      "lineartrend(_actual_ _predict_ _stderr_ _lower_ _upper_)"
      );
run;

To use the user-defined forecasting model defined by the FCMP procedure in the HPFENGINE procedure, the CMPLIB option must list the catalogs that contains the SAS language functions and routines and C language external functions. For more information about the FCMP procedure, see the Base SAS Procedures Guide.

For example, the following SAS statement specifies the SAS catalogs that are required to use a user-defined forecasting model.

options cmplib=( work.hpfuser work.chpfuser);

At this point:

  • A C language external function has been defined, LINEARTREND_C, and stored in the SAS catalog, WORK.CHPFUSER.

  • A SAS language subroutine has been defined, LINEARTREND, which calls the external function, LINEARTREND_C, and stored in the SAS catalog, WORK.HPFUSER.

  • An external model specification, LINEARTREND, has been stored in the model repository, WORK.MYREPOSITORY.

  • A model selection list, MYSELECT, has been stored in the model repository, WORK.MYREPOSITORY.

  • The search path for the SAS language functions and subroutines has been set to WORK.HPFUSER and WORK.CHPFUSER.

The HPFENGINE procedure can now use the user-defined forecasting routine.

For example, the following SAS statements forecast the monthly time series contained in the SASHELP.AIR data set. This data set contains two variables DATE and AIR. The MODELREPOSITORY= WORK.MYREPOSITORY option of the PROC HPFENGINE statement specifies the model repository, and the GLOBALSELECTION=MYSELECT options specifies the model selection list.

proc hpfengine data=sashelp.air
               out=proout
               outfor=profor
               outstat=prostat
               modelrepository=myrepository
               globalselection=myselect;
   id date interval=month;
   forecast air;
run;

The OUT= data set contains the original data extrapolated by the simple linear trend model (values returned in the _PREDICT_ array), and the OUTFOR= data set contains the forecasts (values returned in the _PREDICT_, _STDERR_, _LOWER_, and _UPPER_ array) and the prediction errors. The OUTSTAT= data set contains the statistics of fit based on the prediction errors and the NPARMS=2 option of the external model specification.

Figure 2 shows the results of a PROC COMPARE run that compares the forecasts from the OUTFOR= data sets from these two implementations of the same algorithm.

Figure 2: Comparison of OUTFOR Data Sets

                                                                                
                                                                                
                              Observation Summary                               
                                                                                
                         Observation      Base  Compare                         
                                                                                
                         First Obs           1        1                         
                         Last  Obs         156      156                         
                                                                                
        Number of Observations in Common: 156.                                  
        Total Number of Observations Read from WORK.CMPFOR: 156.                
        Total Number of Observations Read from WORK.PROFOR: 156.                
                                                                                
        Number of Observations with Some Compared Variables Unequal: 0.         
        Number of Observations with All Compared Variables Equal: 156.          
                                                                                
                                                                                
                           Values Comparison Summary                            
                                                                                
        Number of Variables Compared with All Observations Equal: 8.            
        Number of Variables Compared with Some Observations Unequal: 0.         
        Total Number of Values which Compare Unequal: 0.                        
        Total Number of Values not EXACTLY Equal: 102.                          
        Maximum Difference: 5.6843E-14.                                         
                                                                                
                                                                                


Last updated: March 05, 2026