0

I am trying to form a variable that has a number of indices, e.g. $x_{i,j}$

Until now I have found in the documentation simple setting of variables like the following:

MPVariable x = solver.makeIntVar(0.0, infinity, "x");

Is there any documentation showing such an example?

Furthermore, is it possible to use AMPL for the formulation of problems within OR tools?

fuglede
  • 17,388
  • 2
  • 54
  • 99
Georgios
  • 861
  • 2
  • 12
  • 30

1 Answers1

1

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.

fuglede
  • 17,388
  • 2
  • 54
  • 99
  • It is still not clear enough for me. If I write the following: `ArrayList> x = new ArrayList>();` and want to use the set Method: `x.set(int index, ArrayList element)` where do I use the index *i* and *j*? – Georgios Jul 14 '19 at 23:11
  • I've updated the answer to expand on what I had in mind. – fuglede Jul 15 '19 at 08:10
  • If I want to use the same constraint \forall elements of a set, do I have to rename the constraint variable? In your example `var ci = solver.makeConstraint(1, 1);` c_i is the variable that I mean. It would seem logical for me to use c_{i,1}; ... ;c_{i,n} with n the cardinality of elements in the set. Such a naming in Java is not possible with my experience. Or do I just have to use `var ci = solver.makeConstraint(1, 1,"ci"+index);` With your code example the later from the two makes more sense. – Georgios Jul 18 '19 at 22:22
  • 1
    You don't have to provide a name for the constraint; in my example, a new `ci` is created in each iteration of the loop, and even though the name of the variables doesn't change, they do end up behaving like different constraints to the solver (which never sees the name of the variable, so it would have no way of distinguishing based on that). If you do want to provide a name to the solver, then yes, you would do what you do in your latter approach. Providing a name can simplify debugging but isn't otherwise necessary (as suggested by the linear assignment example). – fuglede Jul 19 '19 at 06:38
  • 1
    Maybe the use of `k` to combine the two loops is what is confusing, so let me just edit the example to split that up. – fuglede Jul 19 '19 at 06:40