examples: cleanup
This commit is contained in:
@@ -21,6 +21,7 @@ import com.google.ortools.constraintsolver.RoutingDimension;
|
||||
import com.google.ortools.constraintsolver.RoutingIndexManager;
|
||||
import com.google.ortools.constraintsolver.RoutingModel;
|
||||
import com.google.ortools.constraintsolver.RoutingSearchParameters;
|
||||
import com.google.ortools.constraintsolver.RoutingSearchStatus;
|
||||
import com.google.ortools.constraintsolver.main;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -29,70 +30,139 @@ import java.util.function.LongBinaryOperator;
|
||||
import java.util.function.LongUnaryOperator;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
// A pair class
|
||||
|
||||
class Pair<K, V> {
|
||||
final K first;
|
||||
final V second;
|
||||
|
||||
public static <K, V> Pair<K, V> of(K element0, V element1) {
|
||||
return new Pair<K, V>(element0, element1);
|
||||
}
|
||||
|
||||
public Pair(K element0, V element1) {
|
||||
this.first = element0;
|
||||
this.second = element1;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sample showing how to model and solve a capacitated vehicle routing problem with time windows
|
||||
* using the swig-wrapped version of the vehicle routing library in src/constraint_solver.
|
||||
* using the swig-wrapped version of the vehicle routing library in
|
||||
* //ortools/constraint_solver.
|
||||
*/
|
||||
public class CapacitatedVehicleRoutingProblemWithTimeWindows {
|
||||
private static Logger logger =
|
||||
private static final Logger logger =
|
||||
Logger.getLogger(CapacitatedVehicleRoutingProblemWithTimeWindows.class.getName());
|
||||
|
||||
// Locations representing either an order location or a vehicle route
|
||||
// start/end.
|
||||
private List<Pair<Integer, Integer>> locations = new ArrayList();
|
||||
// A pair class
|
||||
static class Pair<K, V> {
|
||||
final K first;
|
||||
final V second;
|
||||
|
||||
// Quantity to be picked up for each order.
|
||||
private List<Integer> orderDemands = new ArrayList();
|
||||
// Time window in which each order must be performed.
|
||||
private List<Pair<Integer, Integer>> orderTimeWindows = new ArrayList();
|
||||
// Penalty cost "paid" for dropping an order.
|
||||
private List<Integer> orderPenalties = new ArrayList();
|
||||
public static <K, V> Pair<K, V> of(K element0, V element1) {
|
||||
return new Pair<K, V>(element0, element1);
|
||||
}
|
||||
|
||||
// Capacity of the vehicles.
|
||||
private int vehicleCapacity = 0;
|
||||
// Latest time at which each vehicle must end its tour.
|
||||
private List<Integer> vehicleEndTime = new ArrayList();
|
||||
// Cost per unit of distance of each vehicle.
|
||||
private List<Integer> vehicleCostCoefficients = new ArrayList();
|
||||
// Vehicle start and end indices. They have to be implemented as int[] due
|
||||
// to the available SWIG-ed interface.
|
||||
private int vehicleStarts[];
|
||||
private int vehicleEnds[];
|
||||
public Pair(K element0, V element1) {
|
||||
this.first = element0;
|
||||
this.second = element1;
|
||||
}
|
||||
}
|
||||
|
||||
// Random number generator to produce data.
|
||||
private final Random randomGenerator = new Random(0xBEEF);
|
||||
static class DataModel {
|
||||
// Locations representing either an order location or a vehicle route
|
||||
// start/end.
|
||||
public final List<Pair<Integer, Integer>> locations = new ArrayList<>();
|
||||
|
||||
// Quantity to be picked up for each order.
|
||||
public final List<Integer> orderDemands = new ArrayList<>();
|
||||
// Time window in which each order must be performed.
|
||||
public final List<Pair<Integer, Integer>> orderTimeWindows = new ArrayList<>();
|
||||
// Penalty cost "paid" for dropping an order.
|
||||
public final List<Integer> orderPenalties = new ArrayList<>();
|
||||
|
||||
public final int numberOfVehicles = 20;
|
||||
|
||||
// Capacity of the vehicles.
|
||||
public final int vehicleCapacity = 50;
|
||||
public final int numberOfOrders = 100;
|
||||
// Latest time at which each vehicle must end its tour.
|
||||
public final List<Integer> vehicleEndTime = new ArrayList<>();
|
||||
// Cost per unit of distance of each vehicle.
|
||||
public final List<Integer> vehicleCostCoefficients = new ArrayList<>();
|
||||
// Vehicle start and end indices. They have to be implemented as int[] due
|
||||
// to the available SWIG-ed interface.
|
||||
public int[] vehicleStarts;
|
||||
public int[] vehicleEnds;
|
||||
|
||||
// Random number generator to produce data.
|
||||
private final Random randomGenerator = new Random(0xBEEF);
|
||||
|
||||
/**
|
||||
* Creates order data. Location of the order is random, as well as its demand (quantity), time
|
||||
* window and penalty.
|
||||
*
|
||||
* @param xMax maximum x coordinate in which orders are located.
|
||||
* @param yMax maximum y coordinate in which orders are located.
|
||||
* @param demandMax maximum quantity of a demand.
|
||||
* @param timeWindowMax maximum starting time of the order time window.
|
||||
* @param timeWindowWidth duration of the order time window.
|
||||
* @param penaltyMin minimum pernalty cost if order is dropped.
|
||||
* @param penaltyMax maximum pernalty cost if order is dropped.
|
||||
*/
|
||||
private void buildOrders(int xMax, int yMax, int demandMax, int timeWindowMax,
|
||||
int timeWindowWidth, int penaltyMin, int penaltyMax) {
|
||||
for (int order = 0; order < numberOfOrders; ++order) {
|
||||
locations.add(
|
||||
Pair.of(randomGenerator.nextInt(xMax + 1), randomGenerator.nextInt(yMax + 1)));
|
||||
orderDemands.add(randomGenerator.nextInt(demandMax + 1));
|
||||
int timeWindowStart = randomGenerator.nextInt(timeWindowMax + 1);
|
||||
orderTimeWindows.add(Pair.of(timeWindowStart, timeWindowStart + timeWindowWidth));
|
||||
orderPenalties.add(randomGenerator.nextInt(penaltyMax - penaltyMin + 1) + penaltyMin);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates fleet data. Vehicle starting and ending locations are random, as well as vehicle
|
||||
* costs per distance unit.
|
||||
*
|
||||
* @param xMax maximum x coordinate in which orders are located.
|
||||
* @param yMax maximum y coordinate in which orders are located.
|
||||
* @param endTime latest end time of a tour of a vehicle.
|
||||
* @param costCoefficientMax maximum cost per distance unit of a vehicle (minimum is 1),
|
||||
*/
|
||||
private void buildFleet(int xMax, int yMax, int endTime, int costCoefficientMax) {
|
||||
vehicleStarts = new int[numberOfVehicles];
|
||||
vehicleEnds = new int[numberOfVehicles];
|
||||
for (int vehicle = 0; vehicle < numberOfVehicles; ++vehicle) {
|
||||
vehicleStarts[vehicle] = locations.size();
|
||||
locations.add(
|
||||
Pair.of(randomGenerator.nextInt(xMax + 1), randomGenerator.nextInt(yMax + 1)));
|
||||
vehicleEnds[vehicle] = locations.size();
|
||||
locations.add(
|
||||
Pair.of(randomGenerator.nextInt(xMax + 1), randomGenerator.nextInt(yMax + 1)));
|
||||
vehicleEndTime.add(endTime);
|
||||
vehicleCostCoefficients.add(randomGenerator.nextInt(costCoefficientMax) + 1);
|
||||
}
|
||||
}
|
||||
|
||||
public DataModel() {
|
||||
final int xMax = 20;
|
||||
final int yMax = 20;
|
||||
final int demandMax = 3;
|
||||
final int timeWindowMax = 24 * 60;
|
||||
final int timeWindowWidth = 4 * 60;
|
||||
final int penaltyMin = 50;
|
||||
final int penaltyMax = 100;
|
||||
final int endTime = 24 * 60;
|
||||
final int costCoefficientMax = 3;
|
||||
buildOrders(xMax, yMax, demandMax, timeWindowMax, timeWindowWidth, penaltyMin, penaltyMax);
|
||||
buildFleet(xMax, yMax, endTime, costCoefficientMax);
|
||||
}
|
||||
} // DataModel
|
||||
|
||||
/**
|
||||
* Creates a Manhattan Distance evaluator with 'costCoefficient'.
|
||||
*
|
||||
* @param data Data Model.
|
||||
* @param manager Node Index Manager.
|
||||
* @param costCoefficient The coefficient to apply to the evaluator.
|
||||
*/
|
||||
private LongBinaryOperator buildManhattanCallback(
|
||||
RoutingIndexManager manager, int costCoefficient) {
|
||||
private static LongBinaryOperator buildManhattanCallback(
|
||||
DataModel data, RoutingIndexManager manager, int costCoefficient) {
|
||||
return new LongBinaryOperator() {
|
||||
public long applyAsLong(long firstIndex, long secondIndex) {
|
||||
@Override
|
||||
public long applyAsLong(long fromIndex, long toIndex) {
|
||||
try {
|
||||
int firstNode = manager.indexToNode(firstIndex);
|
||||
int secondNode = manager.indexToNode(secondIndex);
|
||||
Pair<Integer, Integer> firstLocation = locations.get(firstNode);
|
||||
Pair<Integer, Integer> secondLocation = locations.get(secondNode);
|
||||
int fromNode = manager.indexToNode(fromIndex);
|
||||
int toNode = manager.indexToNode(toIndex);
|
||||
Pair<Integer, Integer> firstLocation = data.locations.get(fromNode);
|
||||
Pair<Integer, Integer> secondLocation = data.locations.get(toNode);
|
||||
return (long) costCoefficient
|
||||
* (Math.abs(firstLocation.first - secondLocation.first)
|
||||
+ Math.abs(firstLocation.second - secondLocation.second));
|
||||
@@ -104,83 +174,82 @@ public class CapacitatedVehicleRoutingProblemWithTimeWindows {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates order data. Location of the order is random, as well as its demand (quantity), time
|
||||
* window and penalty.
|
||||
*
|
||||
* @param numberOfOrders number of orders to build.
|
||||
* @param xMax maximum x coordinate in which orders are located.
|
||||
* @param yMax maximum y coordinate in which orders are located.
|
||||
* @param demandMax maximum quantity of a demand.
|
||||
* @param timeWindowMax maximum starting time of the order time window.
|
||||
* @param timeWindowWidth duration of the order time window.
|
||||
* @param penaltyMin minimum pernalty cost if order is dropped.
|
||||
* @param penaltyMax maximum pernalty cost if order is dropped.
|
||||
*/
|
||||
private void buildOrders(int numberOfOrders, int xMax, int yMax, int demandMax, int timeWindowMax,
|
||||
int timeWindowWidth, int penaltyMin, int penaltyMax) {
|
||||
logger.info("Building orders.");
|
||||
for (int order = 0; order < numberOfOrders; ++order) {
|
||||
locations.add(Pair.of(randomGenerator.nextInt(xMax + 1), randomGenerator.nextInt(yMax + 1)));
|
||||
orderDemands.add(randomGenerator.nextInt(demandMax + 1));
|
||||
int timeWindowStart = randomGenerator.nextInt(timeWindowMax + 1);
|
||||
orderTimeWindows.add(Pair.of(timeWindowStart, timeWindowStart + timeWindowWidth));
|
||||
orderPenalties.add(randomGenerator.nextInt(penaltyMax - penaltyMin + 1) + penaltyMin);
|
||||
// Print the solution.
|
||||
static void printSolution(
|
||||
DataModel data, RoutingModel model, RoutingIndexManager manager, Assignment solution) {
|
||||
RoutingSearchStatus.Value status = model.status();
|
||||
logger.info("Status: " + status);
|
||||
if (status != RoutingSearchStatus.Value.ROUTING_OPTIMAL
|
||||
&& status != RoutingSearchStatus.Value.ROUTING_SUCCESS) {
|
||||
logger.warning("No solution found!");
|
||||
return;
|
||||
}
|
||||
|
||||
// Solution cost.
|
||||
logger.info("Objective : " + solution.objectiveValue());
|
||||
// Dropped orders
|
||||
String dropped = "";
|
||||
for (int order = 0; order < data.numberOfOrders; ++order) {
|
||||
if (solution.value(model.nextVar(order)) == order) {
|
||||
dropped += " " + order;
|
||||
}
|
||||
}
|
||||
if (dropped.length() > 0) {
|
||||
logger.info("Dropped orders:" + dropped);
|
||||
}
|
||||
|
||||
// Routes
|
||||
for (int vehicle = 0; vehicle < data.numberOfVehicles; ++vehicle) {
|
||||
if (!model.isVehicleUsed(solution, vehicle)) {
|
||||
continue;
|
||||
}
|
||||
long index = model.start(vehicle);
|
||||
logger.info("Route for Vehicle " + vehicle + ":");
|
||||
String route = "";
|
||||
RoutingDimension capacityDimension = model.getMutableDimension("capacity");
|
||||
RoutingDimension timeDimension = model.getMutableDimension("time");
|
||||
while (!model.isEnd(index)) {
|
||||
IntVar load = capacityDimension.cumulVar(index);
|
||||
IntVar time = timeDimension.cumulVar(index);
|
||||
long nodeIndex = manager.indexToNode(index);
|
||||
route += nodeIndex + " Load(" + solution.value(load) + ")";
|
||||
route += " Time(" + solution.min(time) + ", " + solution.max(time) + ") -> ";
|
||||
index = solution.value(model.nextVar(index));
|
||||
}
|
||||
IntVar load = capacityDimension.cumulVar(index);
|
||||
IntVar time = timeDimension.cumulVar(index);
|
||||
long nodeIndex = manager.indexToNode(index);
|
||||
route += nodeIndex + " Load(" + solution.value(load) + ")";
|
||||
route += " Time(" + solution.min(time) + ", " + solution.max(time) + ")";
|
||||
logger.info(route);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates fleet data. Vehicle starting and ending locations are random, as well as vehicle costs
|
||||
* per distance unit.
|
||||
*
|
||||
* @param numberOfVehicles
|
||||
* @param xMax maximum x coordinate in which orders are located.
|
||||
* @param yMax maximum y coordinate in which orders are located.
|
||||
* @param endTime latest end time of a tour of a vehicle.
|
||||
* @param capacity capacity of a vehicle.
|
||||
* @param costCoefficientMax maximum cost per distance unit of a vehicle (mimimum is 1),
|
||||
*/
|
||||
private void buildFleet(
|
||||
int numberOfVehicles, int xMax, int yMax, int endTime, int capacity, int costCoefficientMax) {
|
||||
logger.info("Building fleet.");
|
||||
vehicleCapacity = capacity;
|
||||
vehicleStarts = new int[numberOfVehicles];
|
||||
vehicleEnds = new int[numberOfVehicles];
|
||||
for (int vehicle = 0; vehicle < numberOfVehicles; ++vehicle) {
|
||||
vehicleStarts[vehicle] = locations.size();
|
||||
locations.add(Pair.of(randomGenerator.nextInt(xMax + 1), randomGenerator.nextInt(yMax + 1)));
|
||||
vehicleEnds[vehicle] = locations.size();
|
||||
locations.add(Pair.of(randomGenerator.nextInt(xMax + 1), randomGenerator.nextInt(yMax + 1)));
|
||||
vehicleEndTime.add(endTime);
|
||||
vehicleCostCoefficients.add(randomGenerator.nextInt(costCoefficientMax) + 1);
|
||||
}
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
Loader.loadNativeLibraries();
|
||||
|
||||
/** Solves the current routing problem. */
|
||||
private void solve(final int numberOfOrders, final int numberOfVehicles) {
|
||||
logger.info(
|
||||
"Creating model with " + numberOfOrders + " orders and " + numberOfVehicles + " vehicles.");
|
||||
// Finalizing model
|
||||
final int numberOfLocations = locations.size();
|
||||
// Instantiate the data problem.
|
||||
final DataModel data = new DataModel();
|
||||
|
||||
RoutingIndexManager manager =
|
||||
new RoutingIndexManager(numberOfLocations, numberOfVehicles, vehicleStarts, vehicleEnds);
|
||||
// Create Routing Index Manager
|
||||
RoutingIndexManager manager = new RoutingIndexManager(
|
||||
data.locations.size(), data.numberOfVehicles, data.vehicleStarts, data.vehicleEnds);
|
||||
RoutingModel model = new RoutingModel(manager);
|
||||
|
||||
// Setting up dimensions
|
||||
final int bigNumber = 100000;
|
||||
final LongBinaryOperator callback = buildManhattanCallback(manager, 1);
|
||||
final String timeStr = "time";
|
||||
model.addDimension(
|
||||
model.registerTransitCallback(callback), bigNumber, bigNumber, false, timeStr);
|
||||
RoutingDimension timeDimension = model.getMutableDimension(timeStr);
|
||||
final LongBinaryOperator callback = buildManhattanCallback(data, manager, 1);
|
||||
boolean unused = model.addDimension(
|
||||
model.registerTransitCallback(callback), bigNumber, bigNumber, false, "time");
|
||||
RoutingDimension timeDimension = model.getMutableDimension("time");
|
||||
|
||||
LongUnaryOperator demandCallback = new LongUnaryOperator() {
|
||||
public long applyAsLong(long index) {
|
||||
@Override
|
||||
public long applyAsLong(long fromIndex) {
|
||||
try {
|
||||
int node = manager.indexToNode(index);
|
||||
if (node < numberOfOrders) {
|
||||
return orderDemands.get(node);
|
||||
int fromNode = manager.indexToNode(fromIndex);
|
||||
if (fromNode < data.numberOfOrders) {
|
||||
return data.orderDemands.get(fromNode);
|
||||
}
|
||||
return 0;
|
||||
} catch (Throwable throwed) {
|
||||
@@ -189,27 +258,25 @@ public class CapacitatedVehicleRoutingProblemWithTimeWindows {
|
||||
}
|
||||
}
|
||||
};
|
||||
final String capacityStr = "capacity";
|
||||
model.addDimension(
|
||||
model.registerUnaryTransitCallback(demandCallback), 0, vehicleCapacity, true, capacityStr);
|
||||
RoutingDimension capacityDimension = model.getMutableDimension(capacityStr);
|
||||
unused = model.addDimension(model.registerUnaryTransitCallback(demandCallback), 0,
|
||||
data.vehicleCapacity, true, "capacity");
|
||||
|
||||
// Setting up vehicles
|
||||
LongBinaryOperator[] callbacks = new LongBinaryOperator[numberOfVehicles];
|
||||
for (int vehicle = 0; vehicle < numberOfVehicles; ++vehicle) {
|
||||
final int costCoefficient = vehicleCostCoefficients.get(vehicle);
|
||||
callbacks[vehicle] = buildManhattanCallback(manager, costCoefficient);
|
||||
LongBinaryOperator[] callbacks = new LongBinaryOperator[data.numberOfVehicles];
|
||||
for (int vehicle = 0; vehicle < data.numberOfVehicles; ++vehicle) {
|
||||
final int costCoefficient = data.vehicleCostCoefficients.get(vehicle);
|
||||
callbacks[vehicle] = buildManhattanCallback(data, manager, costCoefficient);
|
||||
final int vehicleCost = model.registerTransitCallback(callbacks[vehicle]);
|
||||
model.setArcCostEvaluatorOfVehicle(vehicleCost, vehicle);
|
||||
timeDimension.cumulVar(model.end(vehicle)).setMax(vehicleEndTime.get(vehicle));
|
||||
timeDimension.cumulVar(model.end(vehicle)).setMax(data.vehicleEndTime.get(vehicle));
|
||||
}
|
||||
|
||||
// Setting up orders
|
||||
for (int order = 0; order < numberOfOrders; ++order) {
|
||||
for (int order = 0; order < data.numberOfOrders; ++order) {
|
||||
timeDimension.cumulVar(order).setRange(
|
||||
orderTimeWindows.get(order).first, orderTimeWindows.get(order).second);
|
||||
data.orderTimeWindows.get(order).first, data.orderTimeWindows.get(order).second);
|
||||
long[] orderIndices = {manager.nodeToIndex(order)};
|
||||
model.addDisjunction(orderIndices, orderPenalties.get(order));
|
||||
int unusedNested = model.addDisjunction(orderIndices, data.orderPenalties.get(order));
|
||||
}
|
||||
|
||||
// Solving
|
||||
@@ -219,67 +286,11 @@ public class CapacitatedVehicleRoutingProblemWithTimeWindows {
|
||||
.setFirstSolutionStrategy(FirstSolutionStrategy.Value.ALL_UNPERFORMED)
|
||||
.build();
|
||||
|
||||
logger.info("Search");
|
||||
Assignment solution = model.solveWithParameters(parameters);
|
||||
|
||||
if (solution != null) {
|
||||
String output = "Total cost: " + solution.objectiveValue() + "\n";
|
||||
// Dropped orders
|
||||
String dropped = "";
|
||||
for (int order = 0; order < numberOfOrders; ++order) {
|
||||
if (solution.value(model.nextVar(order)) == order) {
|
||||
dropped += " " + order;
|
||||
}
|
||||
}
|
||||
if (dropped.length() > 0) {
|
||||
output += "Dropped orders:" + dropped + "\n";
|
||||
}
|
||||
// Routes
|
||||
for (int vehicle = 0; vehicle < numberOfVehicles; ++vehicle) {
|
||||
String route = "Vehicle " + vehicle + ": ";
|
||||
long order = model.start(vehicle);
|
||||
// Empty route has a minimum of two nodes: Start => End
|
||||
if (model.isEnd(solution.value(model.nextVar(order)))) {
|
||||
route += "Empty";
|
||||
} else {
|
||||
for (; !model.isEnd(order); order = solution.value(model.nextVar(order))) {
|
||||
IntVar load = capacityDimension.cumulVar(order);
|
||||
IntVar time = timeDimension.cumulVar(order);
|
||||
route += order + " Load(" + solution.value(load) + ") "
|
||||
+ "Time(" + solution.min(time) + ", " + solution.max(time) + ") -> ";
|
||||
}
|
||||
IntVar load = capacityDimension.cumulVar(order);
|
||||
IntVar time = timeDimension.cumulVar(order);
|
||||
route += order + " Load(" + solution.value(load) + ") "
|
||||
+ "Time(" + solution.min(time) + ", " + solution.max(time) + ")";
|
||||
}
|
||||
output += route + "\n";
|
||||
}
|
||||
logger.info(output);
|
||||
}
|
||||
// Print solution on console.
|
||||
printSolution(data, model, manager, solution);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
Loader.loadNativeLibraries();
|
||||
CapacitatedVehicleRoutingProblemWithTimeWindows problem =
|
||||
new CapacitatedVehicleRoutingProblemWithTimeWindows();
|
||||
final int xMax = 20;
|
||||
final int yMax = 20;
|
||||
final int demandMax = 3;
|
||||
final int timeWindowMax = 24 * 60;
|
||||
final int timeWindowWidth = 4 * 60;
|
||||
final int penaltyMin = 50;
|
||||
final int penaltyMax = 100;
|
||||
final int endTime = 24 * 60;
|
||||
final int costCoefficientMax = 3;
|
||||
|
||||
final int orders = 100;
|
||||
final int vehicles = 20;
|
||||
final int capacity = 50;
|
||||
|
||||
problem.buildOrders(
|
||||
orders, xMax, yMax, demandMax, timeWindowMax, timeWindowWidth, penaltyMin, penaltyMax);
|
||||
problem.buildFleet(vehicles, xMax, yMax, endTime, capacity, costCoefficientMax);
|
||||
problem.solve(orders, vehicles);
|
||||
}
|
||||
private CapacitatedVehicleRoutingProblemWithTimeWindows() {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user