Callbacks

Knitro needs to evaluate the objective function and constraints (function values and ideally, their derivatives) at various points along the optimization process. In order to pass this information to the solver, you need to provide a handle to a user-defined function that performs the necessary computation. This is referred to as a callback.

Callbacks in Knitro require you to supply several function pointers that Knitro calls when it needs new function, gradient or Hessian values, or to execute a user-provided newpoint routine. For convenience, the function, gradient and Hessian callback routines take the same list of arguments.

If your callback requires additional parameters, you are encouraged to create a structure containing them and pass its address as the userParams pointer. Knitro does not modify or dereference the userParams pointer, so it is safe to use for this purpose.

The C language prototypes for the Knitro callback functions used for evaluations and the newpoint feature are defined in knitro.h. The prototype used for evaluations is:

typedef int KTR_callback (
        const int             evalRequestCode,
        const int             n,
        const int             m,
        const int             nnzJ,
        const int             nnzH,
        const double * const  x,
        const double * const  lambda,
              double * const  obj,
              double * const  c,
              double * const  objGrad,
              double * const  jac,
              double * const  hessian,
              double * const  hessVector,
              void   *        userParams);

while the prototype used for the newpoint callback is:

typedef int KTR_newpt_callback (
                    KTR_context_ptr       kc,
                    const int             n,
                    const int             m,
                    const int             nnzJ,
                    const double * const  x,
                    const double * const  lambda,
                    const double          obj,
                    const double * const  c,
                    const double * const  objGrad,
                    const double * const  jac,
                          void   *        userParams);

The prototype used for evaluations in the nonlinear least-squares API is:

typedef int KTR_lsq_callback(const int            n,
                             const int            m,
                             const int            nnzJ,
                             const double * const x,
                                   double * const res,
                                   double * const jac,
                                   void *         userParams);

Note that this last callback type does not use the evalRequestCode parameter.

The evalRequestCode input indicates which callback utility Knitro would like you to perform and can take on any of the following values:

  • KTR_RC_EVALFC (1): Evaluate functions f(x) (objective) and c(x) (constraints).
  • KTR_RC_EVALGA (2): Evaluate gradient of f(x) and the constraint Jacobian matrix.
  • KTR_RC_EVALH (3): Evaluate the Hessian H(x,\lambda).
  • KTR_RC_EVALHV (7): Evaluate the Hessian H(x,\lambda) times a vector.
  • KTR_RC_EVALH_NO_F (8): Evaluate the Hessian H(x,\lambda) without the objective component included.
  • KTR_RC_EVALHV_NO_F (9): Evaluate the Hessian H(x,\lambda) times a vector without the objective component included.

See the Derivatives section for details on how to compute the Jacobian and Hessian matrices in a form suitable for Knitro.

The callback functions for evaluating the functions, gradients and Hessian or for performing some newpoint task, are set as described below. Each user callback routine should return an int value of 0 if successful, or a negative value to indicate that an error occurred during execution of the user-provided function.

/* This callback should modify "obj" and "c". */
int KTR_set_func_callback (KTR_context_ptr kc, KTR_callback * func);

/* This callback should modify "res". */
int KTR_lsq_set_res_callback(KTR_context_ptr kc, KTR_lsq_callback * const fnPtr);

/* This callback should modify "objGrad" and "jac". */
int KTR_set_grad_callback (KTR_context_ptr kc, KTR_callback * func);

/* This callback should modify "jac". */
int KTR_lsq_set_jac_callback(KTR_context_ptr kc, KTR_lsq_callback * const fnPtr);

/* This callback should modify "hessian" or "hessVector",
   depending on the value of "evalRequestCode". */
int KTR_set_hess_callback (KTR_context_ptr kc, KTR_callback * func);

/* This callback should modify nothing. */
int KTR_set_newpt_callback (KTR_context_ptr kc, KTR_newpt_callback * func);

Note

To enable newpoint callbacks, set newpoint = user. This callback should only be used for continuous problems.

Knitro also provides a special callback function for output printing. By default Knitro prints to stdout or a knitro.log file, as determined by the outmode option. Alternatively, you can define a callback function to handle all output. This callback function can be set as shown below

int KTR_set_puts_callback (KTR_context_ptr kc, KTR_puts * puts_func);

The prototype for the Knitro callback function used for handling output is

typedef int KTR_puts (char * str, void * user);

In addition to the callbacks defined above, Knitro makes additional callbacks available to the user for features such as multi-start and MINLP. Please see a complete list and description of Knitro callback functions in the Knitro API section in the Reference Manual.

For information on setting callbacks in the object-oriented interface, see the Object-oriented interface reference.

Example

Consider the following nonlinear optimization problem from the Hock and Schittkowski test set.

\min \; 100 - (x_2 - x_1^2)^2 + (1 - x_1)^2     \\
1 \leq x_1 x_2, \; 0 \leq x_1 + x_2^2, \; x_1 \leq 0.5 .

This problem is coded as examples/C/problemHS15.c.

Note

The Knitro distribution comes with several C language programs in the directory examples/C. The instructions in examples/C/README.txt explain how to compile and run the examples. This section overviews the coding of driver programs using the callback interface, but the working examples provide more complete detail.

Every driver starts by allocating a new Knitro solver instance and checking that it succeeded (KTR_new() might return NULL if the Artelys license check fails):

#include "knitro.h"

/*... Include other headers, define main() ...*/

KTR_context     *kc;

/*... Declare other local variables ...*/

/*---- CREATE A NEW KNITRO SOLVER INSTANCE. */
kc = KTR_new();
if (kc == NULL) {
        printf ("Failed to find an Artelys license.\n");
        return( -1 );
}

The next task is to load the problem definition into the solver using KTR_init_problem(). The problem has 2 unknowns and 2 constraints, and it is easily seen that all first and second partial derivatives are generally nonzero. The code below captures the problem definition and passes it to Knitro:

/*---- DEFINE PROBLEM SIZES. */
n = 2;
m = 2;
nnzJ = 4;
nnzH = 3;

/*... allocate memory for xLoBnds, xUpBnds, etc. ...*/

/*---- DEFINE THE OBJECTIVE FUNCTION AND VARIABLE BOUNDS. */
objType = KTR_OBJTYPE_GENERAL;
objGoal = KTR_OBJGOAL_MINIMIZE;
xLoBnds[0] = -KTR_INFBOUND;
xLoBnds[1] = -KTR_INFBOUND;
xUpBnds[0] = 0.5;
xUpBnds[1] = KTR_INFBOUND;

/*---- DEFINE THE CONSTRAINT FUNCTIONS. */
cType[0] = KTR_CONTYPE_QUADRATIC;
cType[1] = KTR_CONTYPE_QUADRATIC;
cLoBnds[0] = 1.0;
cLoBnds[1] = 0.0;
cUpBnds[0] = KTR_INFBOUND;
cUpBnds[1] = KTR_INFBOUND;

/*---- PROVIDE FIRST DERIVATIVE STRUCTURAL INFORMATION. */
jacIndexCons[0] = 0;
jacIndexCons[1] = 0;
jacIndexCons[2] = 1;
jacIndexCons[3] = 1;
jacIndexVars[0] = 0;
jacIndexVars[1] = 1;
jacIndexVars[2] = 0;
jacIndexVars[3] = 1;

/*---- PROVIDE SECOND DERIVATIVE STRUCTURAL INFORMATION. */
hessIndexRows[0] = 0;
hessIndexRows[1] = 0;
hessIndexRows[2] = 1;
hessIndexCols[0] = 0;
hessIndexCols[1] = 1;
hessIndexCols[2] = 1;

/*---- CHOOSE AN INITIAL START POINT. */
xInitial[0] = -2.0;
xInitial[1] =   1.0;

/*---- INITIALIZE KNITRO WITH THE PROBLEM DEFINITION. */
nStatus = KTR_init_problem (kc, n, objGoal, objType,
        xLoBnds, xUpBnds,
        m, cType, cLoBnds, cUpBnds,
        nnzJ, jacIndexVars, jacIndexCons,
        nnzH, hessIndexRows, hessIndexCols,
        xInitial, NULL);
        if (nStatus != 0)
                        /*... an error occurred ...*/

        /*... free xLoBnds, xUpBnds, etc. ...*/

Assume for simplicity that the user writes three routines for computing problem information. In examples/C/problemHS15.c these are named computeFC, computeGA, and computeH.

/*------------------------------------------------------------------*/
/*         FUNCTION callbackEvalFC                                  */
/*------------------------------------------------------------------*/
/** The signature of this function matches KTR_callback in knitro.h.
 *    Only "obj" and "c" are modified.
 */
int callbackEvalFC (
            const int               evalRequestCode,
            const int               n,
            const int               m,
            const int               nnzJ,
            const int               nnzH,
            const double * const    x,
            const double * const    lambda,
            double * const          obj,
            double * const          c,
            double * const          objGrad,
            double * const          jac,
            double * const          hessian,
            double * const          hessVector,
            void   *                userParams)
{
        if (evalRequestCode != KTR_RC_EVALFC)
            {
            printf ("*** callbackEvalFC incorrectly called with eval code %dn",
                            evalRequestCode);
            return( -1 );

        /*---- IN THIS EXAMPLE, CALL THE ROUTINE IN problemDef.h. */
        *obj = computeFC (x, c);
        return( 0 );

/*------------------------------------------------------------------*/
/*         FUNCTION callbackEvalGA                                  */
/*------------------------------------------------------------------*/
/** The signature of this function matches KTR_callback in knitro.h.
 *    Only "objGrad" and "jac" are modified.
 */

        /*... similar implementation to callbackEvalFC ...*/

/*------------------------------------------------------------------*/
/*         FUNCTION callbackEvalH                                   */
/*------------------------------------------------------------------*/
/** The signature of this function matches KTR_callback in knitro.h.
 *    Only "hessian" is modified.
 */

        /*... similar implementation to callbackEvalFC ...*/

To write a driver program using callback mode, simply wrap each evaluation routine in a function that matches the KTR_callback() prototype defined in knitro.h. Note that all three wrappers use the same prototype. This is in case the application finds it convenient to combine some of the evaluation steps, as demonstrated in examples/C/callbackExample2.c.

Back in the main program each wrapper function is registered as a callback to Knitro, and then KTR_solve() is invoked to find the solution:

/*---- REGISTER THE CALLBACK FUNCTIONS THAT PERFORM PROBLEM EVALS.
 *---- THE HESSIAN CALLBACK ONLY NEEDS TO BE REGISTERED FOR SPECIFIC
 *---- HESSIAN OPTIONS (E.G., IT IS NOT REGISTERED IF THE OPTION FOR
 *---- BFGS HESSIAN APPROXIMATIONS IS SELECTED).
 */
if (KTR_set_func_callback (kc, &callbackEvalFC) != 0)
        exit( -1 );
if (KTR_set_grad_callback (kc, &callbackEvalGA) != 0)
        exit( -1 );
if ((nHessOpt == KTR_HESSOPT_EXACT) || (nHessOpt == KTR_HESSOPT_PRODUCT)) {
        if (KTR_set_hess_callback (kc, &callbackEvalHess) != 0)
                exit( -1 );

/*---- SOLVE THE PROBLEM     */
nStatus = KTR_solve (kc, x, lambda, 0, &obj,
        NULL, NULL, NULL, NULL, NULL, NULL);
if (nStatus != KTR_RC_OPTIMAL_OR_SATISFACTORY)
        printf ("Knitro failed to solve the problem, final status = %dn", nStatus);

/*---- DELETE THE KNITRO SOLVER INSTANCE. */
KTR_free (&kc);