diff --git a/examples/java/Vrp.java b/examples/java/Vrp.java new file mode 100644 index 0000000000..cbad5d6883 --- /dev/null +++ b/examples/java/Vrp.java @@ -0,0 +1,168 @@ +// Copyright 2018 Google LLC +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +import java.io.*; +import static java.lang.Math.abs; +//import java.util.*; + +import com.google.ortools.constraintsolver.RoutingModel; +import com.google.ortools.constraintsolver.NodeEvaluator2; +import com.google.ortools.constraintsolver.RoutingDimension; +import com.google.ortools.constraintsolver.RoutingSearchParameters; +import com.google.ortools.constraintsolver.FirstSolutionStrategy; +import com.google.ortools.constraintsolver.Assignment; + +class DataProblem { + private int[][] locations_; + + public DataProblem() { + locations_ = new int[][] { + {4, 4}, + {2, 0}, {8, 0}, + {0, 1}, {1, 1}, + {5, 2}, {7, 2}, + {3, 3}, {6, 3}, + {5, 5}, {8, 5}, + {1, 6}, {2, 6}, + {3, 7}, {6, 7}, + {0, 8}, {7, 8} + }; + + // Compute locations in meters using the block dimension defined as follow + // Manhattan average block: 750ft x 264ft -> 228m x 80m + // here we use: 114m x 80m city block + // src: https://nyti.ms/2GDoRIe "NY Times: Know Your distance" + int[] cityBlock = {228/2, 80}; + for (int i=0; i < locations_.length; i++) { + locations_[i][0] = locations_[i][0] * cityBlock[0]; + locations_[i][1] = locations_[i][1] * cityBlock[1]; + } + } + + /// @brief Gets the number of vehicles. + public int getVehicleNumber() { return 4;} + /// @brief Gets the locations. + public int[][] getLocations() { return locations_;} + /// @brief Gets the number of locations. + public int getLocationNumber() { return locations_.length;} + /// @brief Gets the depot NodeIndex. + public int getDepot() { return 0;} +} + +/// @brief Manhattan distance implemented as a callback. +/// @details It uses an array of positions and computes +/// the Manhattan distance between the two positions of +/// two different indices. +class ManhattanDistance extends NodeEvaluator2 { + private int[][] distances_; + + public ManhattanDistance(DataProblem data) { + // precompute distance between location to have distance callback in O(1) + distances_ = new int[data.getLocationNumber()][data.getLocationNumber()]; + for (int fromNode = 0; fromNode < data.getLocationNumber(); ++fromNode) { + for (int toNode = 0; toNode < data.getLocationNumber(); ++toNode) { + if (fromNode == toNode) + distances_[fromNode][toNode] = 0; + else + distances_[fromNode][toNode] = + abs(data.getLocations()[toNode][0] - data.getLocations()[fromNode][0]) + + abs(data.getLocations()[toNode][1] - data.getLocations()[fromNode][1]); + } + } + } + + @Override + /// @brief Returns the manhattan distance between the two nodes. + public long run(int fromNode, int toNode) { + return distances_[fromNode][toNode]; + } +} + +class Vrp { + static { + System.loadLibrary("jniortools"); + } + + /// @brief Add Global Span constraint. + static void addDistanceDimension(RoutingModel routing, DataProblem data) { + String distance = "Distance"; + routing.addDimension( + new ManhattanDistance(data), + 0, // null slack + 3000, // maximum distance per vehicle + true, // start cumul to zero + distance); + RoutingDimension distanceDimension = routing.getDimensionOrDie(distance); + // Try to minimize the max distance among vehicles. + // /!\ It doesn't mean the standard deviation is minimized + distanceDimension.setGlobalSpanCostCoefficient(100); + } + + /// @brief Print the solution + static void printSolution( + DataProblem data, + RoutingModel routing, + Assignment solution) + { + // Solution cost. + System.out.println("Objective : " + solution.objectiveValue()); + // Inspect solution. + for (int i=0; i < data.getVehicleNumber(); ++i) { + System.out.println("Route for Vehicle " + i + ":"); + long distance = 0; + for (long index = routing.start(i); + !routing.isEnd(index);) + { + System.out.print(routing.indexToNode(index) + " -> "); + + long previousIndex = index; + index = solution.value(routing.nextVar(index)); + distance += routing.getArcCostForVehicle(previousIndex, index, i); + } + System.out.println(routing.indexToNode(routing.end(i))); + System.out.println("Distance of the route: " + distance + "m"); + } + } + + /// @brief Solves the current routing problem. + static void solve() { + // Instantiate the data problem. + DataProblem data = new DataProblem(); + + // Create Routing Model + RoutingModel routing = new RoutingModel( + data.getLocationNumber(), + data.getVehicleNumber(), + data.getDepot()); + + // Setting the cost function. + // [todo]: protect callback from the GC + NodeEvaluator2 distanceEvaluator = new ManhattanDistance(data); + routing.setArcCostEvaluatorOfAllVehicles(distanceEvaluator); + addDistanceDimension(routing, data); + + // Setting first solution heuristic (cheapest addition). + RoutingSearchParameters search_parameters = + RoutingSearchParameters.newBuilder() + .mergeFrom(RoutingModel.defaultSearchParameters()) + .setFirstSolutionStrategy(FirstSolutionStrategy.Value.PATH_CHEAPEST_ARC) + .build(); + + Assignment solution = routing.solveWithParameters(search_parameters); + printSolution(data, routing, solution); + } + + /// @brief Entry point of the program. + public static void main(String[] args) throws Exception { + solve(); + } +} diff --git a/makefiles/Makefile.java.mk b/makefiles/Makefile.java.mk index 132fa42601..a578aebc75 100755 --- a/makefiles/Makefile.java.mk +++ b/makefiles/Makefile.java.mk @@ -64,6 +64,7 @@ $(LIB_DIR)/Sudoku$J \ $(LIB_DIR)/SurvoPuzzle$J \ $(LIB_DIR)/ToNum$J \ $(LIB_DIR)/Tsp$J \ +$(LIB_DIR)/Vrp$J \ $(LIB_DIR)/WhoKilledAgatha$J \ $(LIB_DIR)/Xkcd$J \ $(LIB_DIR)/YoungTableaux$J