The C Interface

The C interface for IPOPT is declared in the header file IpStdCInterface.h, which is found in
$IPOPTDIR/include/coin (or in $PREFIX/include/coin if the switch --prefix=$PREFIX was used for configure); while reading this section, it will be helpful to have a look at this file.

In order to solve an optimization problem with the C interface, one has to create an IpoptProblem17with the function CreateIpoptProblem, which later has to be passed to the IpoptSolve function.

The IpoptProblem created by CreateIpoptProblem contains the problem dimensions, the variable and constraint bounds, and the function pointers for callbacks that will be used to evaluate the NLP problem functions and their derivatives (see also the discussion of the C++ methods get_nlp_info and get_bounds_info in Section 3.3.1 for information about the arguments of CreateIpoptProblem).

The prototypes for the callback functions, Eval_F_CB, Eval_Grad_F_CB, etc., are defined in the header file IpStdCInterface.h. Their arguments correspond one-to-one to the arguments for the C++ methods discussed in Section 3.3.1; for example, for the meaning of $ \tt
n$, $ \tt x$, $ \tt new\_x$, $ \tt obj\_value$ in the declaration of Eval_F_CB see the discussion of ``eval_f''. The callback functions should return TRUE, unless there was a problem doing the requested function/derivative evaluation at the given point x (then it should return FALSE).

Note the additional argument of type UserDataPtr in the callback functions. This pointer argument is available for you to communicate information between the main program that calls IpoptSolve and any of the callback functions. This pointer is simply passed unmodified by IPOPT among those functions. For example, you can use this to pass constants that define the optimization problem and are computed before the optimization in the main C program to the callback functions.

After an IpoptProblem has been created, you can set algorithmic options for IPOPT (see Section 5) using the AddIpopt...Option functions. Finally, the IPOPT algorithm is called with IpoptSolve, giving IPOPT the IpoptProblem, the starting point, and arrays to store the solution values (primal and dual variables), if desired. Finally, after everything is done, you should call FreeIpoptProblem to release internal memory that is still allocated inside IPOPT.

In the remainder of this section we discuss how the example problem (4)-(7) can be solved using the C interface. A completed version of this example can be found in Ipopt/examples/hs071_c.


In order to implement the example problem on your own, create a new directory MyCExample and create a new file, hs071_c.c. Here, include the interface header file IpStdCInterface.h, along with other necessary header files, such as stdlib.h and assert.h. Add the prototypes and implementations for the five callback functions. Have a look at the C++ implementation for eval_f, eval_g, eval_grad_f, eval_jac_g, and eval_h in Section 3.3.1. The C implementations have somewhat different prototypes, but are implemented almost identically to the C++ code. See the completed example in Ipopt/examples/hs071_c/hs071_c.c if you are not sure how to do this.

We now need to implement the main function, create the IpoptProblem, set options, and call IpoptSolve. The CreateIpoptProblem function requires the problem dimensions, the variable and constraint bounds, and the function pointers to the callback routines. The IpoptSolve function requires the IpoptProblem, the starting point, and allocated arrays for the solution. The main function from the example is shown next and discussed below.

int main()
{
  Index n=-1;                          /* number of variables */
  Index m=-1;                          /* number of constraints */
  Number* x_L = NULL;                  /* lower bounds on x */
  Number* x_U = NULL;                  /* upper bounds on x */
  Number* g_L = NULL;                  /* lower bounds on g */
  Number* g_U = NULL;                  /* upper bounds on g */
  IpoptProblem nlp = NULL;             /* IpoptProblem */
  enum ApplicationReturnStatus status; /* Solve return code */
  Number* x = NULL;                    /* starting point and solution vector */
  Number* mult_x_L = NULL;             /* lower bound multipliers at the solution */
  Number* mult_x_U = NULL;             /* upper bound multipliers at the solution */
  Number obj;                          /* objective value */
  Index i;                             /* generic counter */
  
  /* set the number of variables and allocate space for the bounds */
  n=4;
  x_L = (Number*)malloc(sizeof(Number)*n);
  x_U = (Number*)malloc(sizeof(Number)*n);
  /* set the values for the variable bounds */
  for (i=0; i<n; i++) {
    x_L[i] = 1.0;
    x_U[i] = 5.0;
  }

  /* set the number of constraints and allocate space for the bounds */
  m=2;
  g_L = (Number*)malloc(sizeof(Number)*m);
  g_U = (Number*)malloc(sizeof(Number)*m);
  /* set the values of the constraint bounds */
  g_L[0] = 25; g_U[0] = 2e19;
  g_L[1] = 40; g_U[1] = 40;

  /* create the IpoptProblem */
  nlp = CreateIpoptProblem(n, x_L, x_U, m, g_L, g_U, 8, 10, 0, 
			   &eval_f, &eval_g, &eval_grad_f, 
			   &eval_jac_g, &eval_h);
  
  /* We can free the memory now - the values for the bounds have been
     copied internally in CreateIpoptProblem */
  free(x_L);
  free(x_U);
  free(g_L);
  free(g_U);

  /* set some options */
  AddIpoptNumOption(nlp, "tol", 1e-9);
  AddIpoptStrOption(nlp, "mu_strategy", "adaptive");

  /* allocate space for the initial point and set the values */
  x = (Number*)malloc(sizeof(Number)*n);
  x[0] = 1.0;
  x[1] = 5.0;
  x[2] = 5.0;
  x[3] = 1.0;

  /* allocate space to store the bound multipliers at the solution */
  mult_x_L = (Number*)malloc(sizeof(Number)*n);
  mult_x_U = (Number*)malloc(sizeof(Number)*n);

  /* solve the problem */
  status = IpoptSolve(nlp, x, NULL, &obj, NULL, mult_x_L, mult_x_U, NULL);

  if (status == Solve_Succeeded) {
    printf("\n\nSolution of the primal variables, x\n");
    for (i=0; i<n; i++)
      printf("x[%d] = %e\n", i, x[i]); 

    printf("\n\nSolution of the bound multipliers, z_L and z_U\n");
    for (i=0; i<n; i++)
      printf("z_L[%d] = %e\n", i, mult_x_L[i]); 
    for (i=0; i<n; i++)
      printf("z_U[%d] = %e\n", i, mult_x_U[i]); 

    printf("\n\nObjective value\nf(x*) = %e\n", obj); 
  }
 
  /* free allocated memory */
  FreeIpoptProblem(nlp);
  free(x);
  free(mult_x_L);
  free(mult_x_U);

  return 0;
}

Here, we declare all the necessary variables and set the dimensions of the problem. The problem has 4 variables, so we set n and allocate space for the variable bounds (don't forget to call free for each of your malloc calls before the end of the program). We then set the values for the variable bounds.

The problem has 2 constraints, so we set m and allocate space for the constraint bounds. The first constraint has a lower bound of $ 25$ and no upper bound. Here we set the upper bound to 2e19. IPOPT interprets any number greater than or equal to nlp_upper_bound_inf as infinity. The default value of nlp_lower_bound_inf and nlp_upper_bound_inf is -1e19 and 1e19, respectively, and can be changed through IPOPT options. The second constraint is an equality with right hand side 40, so we set both the upper and the lower bound to 40.

We next create an instance of the IpoptProblem by calling CreateIpoptProblem, giving it the problem dimensions and the variable and constraint bounds. The arguments nele_jac and nele_hess are the number of elements in Jacobian and the Hessian, respectively. See Appendix A for a description of the sparse matrix format. The index_style argument specifies whether we want to use C style indexing for the row and column indices of the matrices or Fortran style indexing. Here, we set it to 0 to indicate C style. We also include the references to each of our callback functions. IPOPT uses these function pointers to ask for evaluation of the NLP when required.

After freeing the bound arrays that are no longer required, the next two lines illustrate how you can change the value of options through the interface. IPOPT options can also be changed by creating a ipopt.opt file (see Section 5). We next allocate space for the initial point and set the values as given in the problem definition.

The call to IpoptSolve can provide us with information about the solution, but most of this is optional. Here, we want values for the bound multipliers at the solution and we allocate space for these.

We can now make the call to IpoptSolve and find the solution of the problem. We pass in the IpoptProblem, the starting point x (IPOPT will use this array to return the solution or final point as well). The next 5 arguments are pointers so IPOPT can fill in values at the solution. If these pointers are set to NULL, IPOPT will ignore that entry. For example, here, we do not want the constraint function values at the solution or the constraint multipliers, so we set those entries to NULL. We do want the value of the objective, and the multipliers for the variable bounds. The last argument is a void* for user data. Any pointer you give here will also be passed to you in the callback functions.

The return code is an ApplicationReturnStatus enumeration, see the header file ReturnCodes_inc.h which is installed along IpStdCInterface.h in the IPOPT include directory.

After the optimizer terminates, we check the status and print the solution if successful. Finally, we free the IpoptProblem and the remaining memory and return from main.



Footnotes

...IpoptProblem17
IpoptProblem is a pointer to a C structure; you should not access this structure directly, only through the functions provided in the C interface.