javashortest-pathconstraint-programmingchoco

ChocoSolver - Find the minimal number of path to visit all cities


I am using Choco Solver 4.10.8 in Java for a constraint programming problem. The goal is to find the minimal number of path we need to use to visit all the cities in a matrix M which is defined here.
We have 4 paths, and 6 cities.

static int[][] M = new int[][]{     
        {2, 3, 4, 5, 6},
        {1, 2, 5},
        {2, 3, 4},      
        {3, 4, 6}                     
};

Here the solver should return 0 and 1, which are the indexes of the path we need to take to see all the 6 cities. Indeed, with 2 paths only, we have reached all the cities in the matrix.

I'm a beginner with Choco Solver, and I'm having trouble defining the constraints of the problem. I think that I have well defined the variables of the problem, here are there:

Model model = new Model("minimalPaths");

// VARIABLES

int nbPath = M.length;
int nbCity = 6;

// Table of nbPath length which shows if a path is used (1) or no (0)
IntVar[] usedPath = model.intVarArray("path", nbPath, 0, 1);

// Which paths are used by each city
IntVar[][] city_by_path = model.intVarMatrix("city", nbCity, nbPath,  0, 1);

// Total number of paths used for the solution - we want to minimize that
IntVar totalPath = model.intVar("totalPath", 0, nbPath);        

I chose to use IntVar and not BoolVar because I think that it's easier to deal with the different constraints. Concerning the constraints, I have tried this:

// CONTRAINTES
        
// We loop over the paths of M
for(int i=0; i<nbPath; i++) {
    // We loop over all the cities for a Path
    int[] path = M[i];
    for(int city: path) {
        // If the Path is used or not, we update the value in the table
        model.element(usedPath[i], city_by_path[city-1], model.intVar(i), 0).post();
    }
}

// This constraint is used to have at least one path used for one city
// This means we want to see all the cities
for(int i = 0; i<nbCity; i++) {
    model.sum(city_by_path[i], ">=", model.intVar(1)).post();
}
        
// The sum of all the Path used is the variable we will minimize
// usedPath[i] = 0 or 1 so if we sum all our value, we will have the total number of paths used
model.sum(usedPath, "=", totalPath).post();
    
// We want to minimize the total number of Paths we will use
model.setObjective(Model.MINIMIZE, totalPath);

I'm sure that some of my constraints are badly defined but I really don't know where.
When I try to solve this, I print the object city_by_path associated to the solution. It shows me that the solution is not good and the constraints are not respected...

    path i=2 is used
City 1: 1 0 0 0 
City 2: 0 0 1 0 
City 3: 0 0 1 0 
City 4: 0 0 1 0 
City 5: 0 0 0 1 
City 6: 0 1 0 0 
    Total path used : 1

I was wondering if you can have a look to my code and give me some advices about the definition of my variables and constraints for this problem.


Solution

  • Actually, you don't need to declare the city_by_path variables as new ones. Indeed, either the path is used and its cities are visited, either the path is not used and all cities will be valued to zero in the matrix (which is always true for cities not in the path).

    To make it work, you can declare your matrix the following:

    IntVar[][] city_by_path = new IntVar[nbCity][nbPath];
    

    And, instead of your element constraints, you fill the matrix with the usedPath variables or with variables valued to zero whether the city is in the path or not:

    for (int p = 0; p < nbPath; p++) {
        for (int c = 0; c < nbCity; c++) {
            final int cityId = c + 1;
            if (Arrays.stream(M[p]).anyMatch(i -> i == cityId)) {
                city_by_path[c][p] = usedPath[p];
            } else {
                city_by_path[c][p] = model.intVar(0);
            }
        }
    }
    

    The remainder of your code is correct. Also, when solving a CSP with a constraint solver, it is always a good idea to help the solver by specifying on which variables to branch. Branching scheme designed specifically for the problem are generally better to find solutions quickly. Here, you can add the following branching scheme (which an optimal solution in 3 nodes and without any backtrack - especially because the instance of the problem is very easy):

    model.getSolver().setSearch(Search.inputOrderLBSearch(usedPath));