JOM (Java Optimization Modeler)

Getting started

Getting started

My suggestion for getting started with JOM, is:

  • First download and install JOM.
  • Then read the web page you are seeing now: you will go through the steps to solve an optimization problem with JOM.
  • Then read the JOM Expression syntax page (click here).

 

After doing the steps before, my suggestion for making a program that uses JOM is:

  • Use one of the examples (or other program) as a template to write your program, then
  • Use the JAVADOC information for knowing the details of the (reduced) amount of calls needed to solve optimization problems with JOM. Javadocs of the releases contain the most up-to-date information and documentation.

  • Use the JOM Expression syntax to solve the doubts about the syntax to write expressions in JOM.

 

As a tutorial, below we use the example in the home page to illustrate the main steps in solving an optimization problem with JOM.

1. Importing the libraries

The JOM.jar file should be stored in your file system and included in the CLASSPATH. The JAR file contains the following Java packages:

  • com.jom: This is the main package, and the only one you have to import in your Java program.
  • com.jom.javaluator: JOM uses a slightly modified version of the Javaluator package to be able to parse the JOM expressions using a MATLAB-like syntax. You do NOT have to import this package, since your code does not access to it. In summary, you do not have to know anything about it, it is for JOM internals.
  • by.bsu.JVmipcl: This package is includes the JNI interface for MIPCL solver. Copy-paste from the MIPCL package (LGPL license).

Then, the only import you need in your Java program to use JOM functionalities is:
import com.jom.*;

2. Creating the OptimizationProblem Object

To be able to define and finally solve an optimization problem, you first have to create an OptimizationProblem object:

OptimizationProblem op = new OptimizationProblem();

3. Adding decision variables to the problem

The decision variables in the problem can be added by calling the addDecisionVariable in the OptimizationProblem object. We have to define (i) the name of the decision vairable as it will appear in the expressions, (ii) if it is constrained to be integer or not, (iii) its size, given as array of integer, with one integer per dimension, (iv) the lower and upper bounds to the values of the variables if any. There are several variations for calling the addDecisionVariable method, offering different forms of, for instance, defining the lower and upper bounds to the variables. See the Javadoc for details.

In our example, we create a decision variable with name "x", constrained to be integer, of size N x N x N (and thus with three dimensions), with lower and upper bound for all the coordinates of 0 and 1 (then, x is an array of binary variables). The name of the decision variable is case-sensitive

op.addDecisionVariable("x", true, new int[] { N , N , N }, 0, 1); 

Important: JOM uses 0-indexing for the arrays of decision variables and input parameters. Then, the first coordinate in any dimension has index 0, not index 1. In our example, first element in "x" is x(0,0,0) and not x(1,1,1). Last element in each dimension is N-1 and not N. For instance, trying to access x(N,0,0) is an error, since the first coordinate is out of bounds.

4. Setting the values to the input parameters

It is possible to assign arrayed constant values of any dimension to input parameters, to be used later in expressions, calling the setInputParameter methods. When the input parameter appears in an expression, JOM replaces it by its current value. The values of input parameter can be reset at any moment. This permits for instance creating for-loops for adding constraints which are different, since the values of some input parameters are updated inside the loop. In our example, the c input parameter is initialized only once, taking the values from an array of size NxNxN where each coordinate has a random value uniformly distibuted between 0 and 1 (see the com.jom.DoubleMatrixND javadoc for details on how this works).

op.setInputParameter("c", new DoubleMatrixND(new int [] { N , N , N} , "random"));

Input parameters can also be useful for setting the values of notable constants like i.e. number \( \pi \), as shown in next line of code:

op.setInputParameter("pi", 3.141592653589793);

5. Adding the objective function

The method setObjectiveFunction is used to define the objective function of the problem, and the search direction (minimize or maximize). If this method is called more than once, only the last call is valid. The objective function to the problem is a JOM expression combining input parameters, numerical constants and decision variables using operators and functions. It should be an scalar expression: that is, a function that evaluates in a real number, not in an array of numbers. See the section about JOM expression syntax for details.

In our example, the objective is to maximimize the sum of all the values in the "x" decision variable element-by-element multiplied by their associated benefit given by the "c" array.

op.setObjectiveFunction("maximize", "sum(x .* c)");

6. Adding the constraints

The method addConstraint is used to add equality or inequality constraints to the problem. Constraints are defined with strings with the syntax: lhs-expression + symbol + rhs-expression. lhs-expression and rhs-expression are the left-hand-side and right-hand-side expressions of the constraint (using the JOM syntax), and the symbol connector determines the type of constraint:

  • == : for equality constraints
  • >= or => : for greater-or-equal constraints
  • <= or =< : for less-or-equal constraints

Note that strict inequalities are not allowed. In general, solvers consider strict inequalities as non-strict inequalities, so we preferred to avoid any confussion prohibiting the < and >. symbols

In general, the size of the lhs and rhs expressions in a constraint must be the same. Then, an array of constraints is defined with one line of code, element by element. If one expression (lhs or rhs) is an array, and the other is an scalar expression, again multiple constraints are added, one for each element in the array taking the same scalar expression in the other side of the constraint. For instance, in the following constraint:

op.addConstraint(" sum(sum(x,2),1) <= 1");
  • The lhs expression is sum(sum(x,2),1). The inner expression sum(x,2), means that array x is summed in its dimension 2 (the second dimension, recall that first dimension is dimension 1), producing an array of size NxN. Then, this NxN array is summed in its dimension 1 (the first dimension for this 2D array, the rows), producing a row vector of size 1xN. The k-th element of this vector contains the sum \( \sum_{ij} x_{ijk} \).
  • The <= symbol means that we have a less-or-equal inequality.
  • The rhs expression is 1, an scalar. In this case, JOM adds as many constraints as the number of cells in the lhs array, repeating for all of them the same rhs expression.

7. Solving the problem

The method void solve(String solverName, Object... paramValuePairs) is used to call the solver and solve the problem. The solverName String can be "glpk", "ipopt" or "cplex". Note that GPLK and CPLEX can only be used with JOM for linear problems, and IPOPT for non-integer differentiable problems. If this is not fulfiled, JOM detects it and raises an exception.

It is possible to pass several pairs of parameter-value parameters to the function. See the section "About solvers' parameters" for more details. An important parameter is "solverLibraryName", which is followed by a String indicating the relative of full path to the .DLL or .SO file in the file system. If this parameter is not included, some default names are used.

In our example, the GLPK solver is used, calling to the binary file glpk_4_47.dll (in a Windows system)

op.solve("glpk" , "solverLibraryName" , "glpk_4_47");

8. Retrieving the solution optimality properties

After the solver has been called, several methods in the OptimizationProblem object are accessible to check the optimality properties of the solution:

  • solutionIsOptimal () indicates if the solution found is optimal, or optimal within the tolerances set for the solver
  • solutionIsFeasible () indicates if the solution found is feasible (optimal or not)
  • feasibleSolutionDoesNotExist () to check if the solver proved that the problem has no feasible solutions
  • foundUnboundedSolution () to check if the solver proved that the problem is unbounded, finding a solution with \( - \infty\) cost of \( + \infty \) benefit.
See the javadoc for more details. In our example we just check for optimality of the solution:
if (!op.solutionIsOptimal ()) throw new RuntimeException ("An optimal solution was not found");

9. Retrieveing the problem solution and Lagrange multipliers

Several methods exist to access the solution to the problem:

  • getPrimalSolution to retrieve the problem solution.
  • getMultipliersOfConstraint to access the Lagrange multipliers of the constraints. This is not available for integer problems
  • getSlackOfConstraint to access the slack of the constraints.
  • getMultiplierOfLowerBoundConstraintToPrimalVariables and getMultiplierOfUpperBoundConstraintToPrimalVariables to access the Lagrange multipliers of the implicit constraints related to the upper and lower bounds to the decision variables set.

See the javadoc for further details. In the example, we just retrieve the primal solution for the decision variable x, returned as a DoubleMatrixND object, representing an array of the same dimensions as x

DoubleMatrixND sol = op.getPrimalSolution("x");