The Java Interface

based on documentation by Rafael de Pelegrini Soares18


The Java interface offers an abstract base class Ipopt with basic methods to specify an NLP, set a number of IPOPT options, to request IPOPT to solve the NLP, and to retrieve a found solution, if any. A HTML documentation of all available interface methods of the Ipopt class can be generated via javadoc by executing make doc in the JIPOPT build directory.

In the following, we discuss necessary steps to implement the HS071 example with JIPOPT.

First, we create a new directory and therein sub directories org/coinor/. Into org/coinor/ we copy the file Ipopt.java, which contains the Java code of the interface, from the corresponding JIPOPT source directory ($IPOPTDIR/Ipopt/contrib/JavaInterface/org/coinor). Further, we create a directory lib next to the org directory and place the previously build JIPOPT library into it (libjipopt.so on Linux/UNIX, libjipopt.dylib on Mac OS X, jipopt.dll on Windows), see also Section 2.6.

Next, we create a new Java source file HS071.java and define a class HS071 that extends the class Ipopt of JIPOPT. In the class constructor, we call the create() method of JIPOPT, which works analogously to get_nlp_info() of the C++ interface. It initializes an IpoptApplication object and informs JIPOPT about the problem size (number of variables, constraints, nonzeros in Jacobian and Hessian).

/** Initialize the bounds and create the native Ipopt problem. */
public HS071() {
  /* Number of nonzeros in the Jacobian of the constraints */
  int nele_jac = 8;
  
  /* Number of nonzeros in the Hessian of the Lagrangian (lower or
   * upper triangual part only) */
  int nele_hess = 10;

  /* Number of variables */
  int n = 4;
        
  /* Number of constraints */
  int m = 2;
        
  /* Index style for the irow/jcol elements */
  int index_style = Ipopt.C_STYLE;
        
  /* create the IpoptProblem */
  create(n, m,  nele_jac, nele_hess, index_style);
}

Next, we add callback functions that are called by JIPOPT to obtain variable bounds, constraint sides, and a starting point:

protected boolean get_bounds_info(int n, double[] x_L, double[] x_U,
                                  int m, double[] g_L, double[] g_U) {
  /* set the values of the variable bounds */
  for( int i = 0; i < x_L.length; i++ ) {
    x_L[i] = 1.0;
    x_U[i] = 5.0;
  }
  
  /* set the values of the constraint bounds */
  g_L[0] = 25.0;
  g_U[0] = 2e19;
  g_L[1] = 40.0;
  g_U[1] = 40.0;
        
  return true;
}
    
protected boolean get_starting_point(int n, boolean init_x, double[] x,
                                     boolean init_z, double[] z_L, double[] z_U,
                                     int m, boolean init_lambda,double[] lambda) {
  assert init_z == false;
  assert init_lambda = false;
  
  if( init_x ) {
    x[0] = 1.0;
    x[1] = 5.0;
    x[2] = 5.0;
    x[3] = 1.0;
  }
        
  return true;
}

In the following, we implement the evaluation methods in a way that is very similar to the C++ interface:

protected boolean eval_f(int n, double[] x, boolean new_x, double[] obj_value) {
  obj_value[0] = x[0] * x[3] * (x[0] + x[1] + x[2]) + x[2];

  return true;
}

protected boolean eval_grad_f(int n, double[] x, boolean new_x, double[] grad_f) {
  grad_f[0] = x[0] * x[3] + x[3] * (x[0] + x[1] + x[2]);
  grad_f[1] = x[0] * x[3];
  grad_f[2] = x[0] * x[3] + 1;
  grad_f[3] = x[0] * (x[0] + x[1] + x[2]);

  return true;
}

protected boolean eval_g(int n, double[] x, boolean new_x, int m, double[] g) {
  g[0] = x[0] * x[1] * x[2] * x[3];
  g[1] = x[0] * x[0] + x[1] * x[1] + x[2] * x[2] + x[3] * x[3];

  return true;
}

protected boolean eval_jac_g(int n, double[] x, boolean new_x, int m, int nele_jac,
                             int[] iRow, int[] jCol, double[] values) {
  if( values == null ) {
    /* return the structure of the jacobian */
    /* this particular jacobian is dense */
    iRow[0] = 0;  jCol[0] = 0;
    iRow[1] = 0;  jCol[1] = 1;
    iRow[2] = 0;  jCol[2] = 2;
    iRow[3] = 0;  jCol[3] = 3;
    iRow[4] = 1;  jCol[4] = 0;
    iRow[5] = 1;  jCol[5] = 1;
    iRow[6] = 1;  jCol[6] = 2;
    iRow[7] = 1;  jCol[7] = 3;
  }
  else {
    /* return the values of the jacobian of the constraints */
    values[0] = x[1]*x[2]*x[3]; /* 0,0 */
    values[1] = x[0]*x[2]*x[3]; /* 0,1 */
    values[2] = x[0]*x[1]*x[3]; /* 0,2 */
    values[3] = x[0]*x[1]*x[2]; /* 0,3 */

    values[4] = 2*x[0];         /* 1,0 */
    values[5] = 2*x[1];         /* 1,1 */
    values[6] = 2*x[2];         /* 1,2 */
    values[7] = 2*x[3];         /* 1,3 */
  }

  return true;
}

protected boolean eval_h(int n, double[] x, boolean new_x, double obj_factor,
                         int m, double[] lambda, boolean new_lambda,
                         int nele_hess, int[] iRow, int[] jCol, double[] values) {
  int idx = 0; /* nonzero element counter */
  int row = 0; /* row counter for loop */
  int col = 0; /* col counter for loop */
  
  if( values == null ) {
    /* return the structure. This is a symmetric matrix, fill the lower left
     * triangle only. */
    /* the hessian for this problem is actually dense */
    idx = 0;
    for( row = 0; row < 4; row++ ) {
      for( col = 0; col <= row; col++ ) {
        iRow[idx] = row;
        jCol[idx] = col;
        idx++;
      }
    }
  }
  else {
    /* return the values. This is a symmetric matrix, fill the lower left
     * triangle only */

    /* fill the objective portion */
    values[0] = obj_factor * (2*x[3]);               /* 0,0 */
    values[1] = obj_factor * (x[3]);                 /* 1,0 */
    values[2] = 0;                                   /* 1,1 */
    values[3] = obj_factor * (x[3]);                 /* 2,0 */
    values[4] = 0;                                   /* 2,1 */
    values[5] = 0;                                   /* 2,2 */
    values[6] = obj_factor * (2*x[0] + x[1] + x[2]); /* 3,0 */
    values[7] = obj_factor * (x[0]);                 /* 3,1 */
    values[8] = obj_factor * (x[0]);                 /* 3,2 */
    values[9] = 0;                                   /* 3,3 */

    /* add the portion for the first constraint */
    values[1] += lambda[0] * (x[2] * x[3]);          /* 1,0 */
    values[3] += lambda[0] * (x[1] * x[3]);          /* 2,0 */
    values[4] += lambda[0] * (x[0] * x[3]);          /* 2,1 */
    values[6] += lambda[0] * (x[1] * x[2]);          /* 3,0 */
    values[7] += lambda[0] * (x[0] * x[2]);          /* 3,1 */
    values[8] += lambda[0] * (x[0] * x[1]);          /* 3,2 */

    /* add the portion for the second constraint */
    values[0] += lambda[1] * 2;                      /* 0,0 */
    values[2] += lambda[1] * 2;                      /* 1,1 */
    values[5] += lambda[1] * 2;                      /* 2,2 */
    values[9] += lambda[1] * 2;                      /* 3,3 */
  }
  return true;
}

Finally, we add a main routine to run this example. The main routines creates an instance of our object and calls the solve method OptimizeNLP:

public static void main(String[] args) {
  // Create the problem
  HS071 hs071 = new HS071();

  // solve the problem
  int status = hs071.OptimizeNLP(x);
  
  // print the status and optimal value
  System.out.println("Status    = " + status);
  System.out.println("Obj Value = " + hs071.getObjectiveValue());
}
The OptimizeNLP method returns the IPOPT solve status as integer, which indicates whether the problem was solved successfully. Further, the methods getObjectiveValue(), getVariableValues(), and getConstraintMultipliers(), getLowerBoundMultipliers(), getUpperBoundMultipliers() can be used to obtain the objective value, the primal solution value of the variables, and dual solution values, respectively.



Footnotes

... Soares18
VRTech Industrial Technologies