You would just create a variable for each pair of indices; i.e. loop over i
and j
and create an ArrayList<ArrayList<MPVariable>>
; i.e. do something like the following, where ni
and nj
denote the number of values for the indices i
and j
respectively:
var x = new ArrayList<ArrayList<MPVariable>>();
for (int i = 0; i < ni; i++) {
var inner = new ArrayList<MPVariable>();
for (int j = 0; j < nj; j++) {
var xij = solver.makeIntVar(0.0, infinity, String.format("x%d%d", i, j));
inner.add(xij);
}
x.add(inner);
}
At this point you can access $x_{i,j}$ through x.get(i).get(j)
.
The official documentation has examples of this albeit for the CP solver; see e.g. the solution to the N-queens problem. Here, the examples use the Python API but you can translate that to Java; for reference, the above nested loops would look as follows in Python:
x = [[solver.IntVar(0.0, infinity, f'x{i}{j}') for j in range(nj)] for i in range(ni)]
Full working example: The assignment problem
With this in mind, let's try to create a complete example. A simple problem modelled by a two-dimensional matrix of integer variables is the linear assignment problem. In its simplest form, we are given a real square matrix of weights $(w_{ij})_{ij}$ and are trying to minimize $\sum_{ij} w_{ij} x_{ij}$ where each $x_{ij}$ is either 0 or 1, and where for each $i$, exactly one $x_{ij}$ is 1, and similarly, for each $j$, exactly $x_{ij}$ is 1.
Here, let us create a 5x5 instance in which $w_{ij} = (i+1)(j+1)$. One readily verifies that in this case, the optimal solution is to let $x_{04} = x_{13} = x_{22} = x_{31} = x_{40} = 1$, and let all other values of $x_{ij}$ be 0. Then, the value of the objective is 5 + 8 + 9 + 8 + 5 = 35.
The following is a full program for solving this case and printing the result:
import com.google.ortools.linearsolver.MPConstraint;
import com.google.ortools.linearsolver.MPObjective;
import com.google.ortools.linearsolver.MPSolver;
import com.google.ortools.linearsolver.MPVariable;
import java.util.ArrayList;
public class LinearAssignment {
public static void main(String[] args) {
System.loadLibrary("jniortools");
var solver = new MPSolver(
"LinearAssignmentProblem", MPSolver.OptimizationProblemType.valueOf("CBC_MIXED_INTEGER_PROGRAMMING"));
// Define the variables and the objective function
var x = new ArrayList<ArrayList<MPVariable>>();
var objective = solver.objective();
int n = 5;
for (int i = 0; i < n; i++) {
var inner = new ArrayList<MPVariable>();
for (int j = 0; j < n; j++) {
var xij = solver.makeBoolVar(String.format("x%d%d", i, j));
objective.setCoefficient(xij, (i+1)*(j+1));
inner.add(xij);
}
x.add(inner);
}
// Add the constraint that sum_j x_{ij} = 1 for every i.
for (int i = 0; i < n; i++) {
var ci = solver.makeConstraint(1, 1);
for (int j = 0; j < n; j++) ci.setCoefficient(x.get(i).get(j), 1);
}
// Add the constraint that sum_i x_{ij} = 1 for every j.
for (int i = 0; j < n; j++) {
var cj = solver.makeConstraint(1, 1);
for (int i = 0; i < n; i++) cj.setCoefficient(x.get(i).get(j), 1);
}
// Run the solver
solver.solve();
// Print the results
System.out.println("Objective at minimum = " + solver.objective().value());
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++)
System.out.print(String.format("x%d%d = %d, ", i, j, (int) x.get(i).get(j).solutionValue()));
System.out.println();
}
}
}
Output:
Objective at minimum = 35.0
x00 = 0, x01 = 0, x02 = 0, x03 = 0, x04 = 1,
x10 = 0, x11 = 0, x12 = 0, x13 = 1, x14 = 0,
x20 = 0, x21 = 0, x22 = 1, x23 = 0, x24 = 0,
x30 = 0, x31 = 1, x32 = 0, x33 = 0, x34 = 0,
x40 = 1, x41 = 0, x42 = 0, x43 = 0, x44 = 0,
It's should be noted that the solution here is mostly illustrative, and that problem could actually be simplified a bit: Since the $x_{ij}$ are either 0 or 1, we could have used makeBoolVar
instead of makeIntVar
. But in fact, since the constraint matrix is totally unimodular, we didn't actually have to use integer variables at all and could just have used real-valued $0 \leq x_{ij} \leq 1$.
Moreover, efficient algorithms exist for solving the linear assignment problem; indeed OR-Tools itself bundles an implementation of the CSA-Q algorithm for integer-valued weights, which works well in practice. Nevertheless, the solution is fine for smaller instances of the problem and hopefully serves as an illustrative example of how to use MPSolver
for non-trivial problems.