org.optaplanner.examples.vehiclerouting.persistence.VehicleRoutingImporter Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of optaplanner-examples Show documentation
Show all versions of optaplanner-examples Show documentation
OptaPlanner solves planning problems.
This lightweight, embeddable planning engine implements powerful and scalable algorithms
to optimize business resource scheduling and planning.
This module contains the examples which demonstrate how to use it in a normal Java application.
package org.optaplanner.examples.vehiclerouting.persistence;
import java.io.IOException;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.optaplanner.examples.common.business.SolutionBusiness;
import org.optaplanner.examples.common.persistence.AbstractTxtSolutionImporter;
import org.optaplanner.examples.common.persistence.SolutionConverter;
import org.optaplanner.examples.vehiclerouting.app.VehicleRoutingApp;
import org.optaplanner.examples.vehiclerouting.domain.Customer;
import org.optaplanner.examples.vehiclerouting.domain.Depot;
import org.optaplanner.examples.vehiclerouting.domain.Vehicle;
import org.optaplanner.examples.vehiclerouting.domain.VehicleRoutingSolution;
import org.optaplanner.examples.vehiclerouting.domain.location.AirLocation;
import org.optaplanner.examples.vehiclerouting.domain.location.DistanceType;
import org.optaplanner.examples.vehiclerouting.domain.location.Location;
import org.optaplanner.examples.vehiclerouting.domain.location.RoadLocation;
import org.optaplanner.examples.vehiclerouting.domain.location.segmented.HubSegmentLocation;
import org.optaplanner.examples.vehiclerouting.domain.location.segmented.RoadSegmentLocation;
import org.optaplanner.examples.vehiclerouting.domain.timewindowed.TimeWindowedCustomer;
import org.optaplanner.examples.vehiclerouting.domain.timewindowed.TimeWindowedDepot;
import org.optaplanner.examples.vehiclerouting.domain.timewindowed.TimeWindowedVehicleRoutingSolution;
public class VehicleRoutingImporter extends AbstractTxtSolutionImporter {
public static void main(String[] args) {
SolutionConverter converter = SolutionConverter.createImportConverter(
VehicleRoutingApp.DATA_DIR_NAME, new VehicleRoutingImporter(), new VehicleRoutingSolutionFileIO());
converter.convert("vrpweb/basic/air/A-n33-k6.vrp", "cvrp-32customers.json");
converter.convert("vrpweb/basic/air/A-n55-k9.vrp", "cvrp-54customers.json");
converter.convert("vrpweb/basic/air/F-n72-k4.vrp", "cvrp-72customers.json");
converter.convert("vrpweb/timewindowed/air/Solomon_025_C101.vrp", "cvrptw-25customers.json");
converter.convert("vrpweb/timewindowed/air/Solomon_100_R101.vrp", "cvrptw-100customers-A.json");
converter.convert("vrpweb/timewindowed/air/Solomon_100_R201.vrp", "cvrptw-100customers-B.json");
converter.convert("vrpweb/timewindowed/air/Homberger_0400_R1_4_1.vrp", "cvrptw-400customers.json");
converter.convert("vrpweb/basic/road-unknown/bays-n29-k5.vrp", "road-cvrp-29customers.json");
}
@Override
public String getInputFileSuffix() {
return "vrp";
}
@Override
public TxtInputBuilder createTxtInputBuilder() {
return new VehicleRoutingInputBuilder();
}
public static class VehicleRoutingInputBuilder extends TxtInputBuilder {
private VehicleRoutingSolution solution;
private boolean timewindowed;
private int customerListSize;
private int vehicleListSize;
private int capacity;
private Map locationMap;
private List depotList;
@Override
public VehicleRoutingSolution readSolution() throws IOException {
String firstLine = readStringValue();
if (firstLine.matches("\\s*NAME\\s*:.*")) {
// Might be replaced by TimeWindowedVehicleRoutingSolution later on
solution = new VehicleRoutingSolution();
solution.setName(removePrefixSuffixFromLine(firstLine, "\\s*NAME\\s*:", ""));
readVrpWebFormat();
} else if (splitBySpacesOrTabs(firstLine).length == 3) {
timewindowed = false;
solution = new VehicleRoutingSolution();
solution.setName(SolutionBusiness.getBaseFileName(inputFile));
String[] tokens = splitBySpacesOrTabs(firstLine, 3);
customerListSize = Integer.parseInt(tokens[0]);
vehicleListSize = Integer.parseInt(tokens[1]);
capacity = Integer.parseInt(tokens[2]);
readCourseraFormat();
} else {
timewindowed = true;
solution = new TimeWindowedVehicleRoutingSolution();
solution.setName(firstLine);
readTimeWindowedFormat();
}
BigInteger a = factorial(customerListSize + vehicleListSize - 1);
BigInteger b = factorial(vehicleListSize - 1);
BigInteger possibleSolutionSize = (a == null || b == null) ? null : a.divide(b);
logger.info("VehicleRoutingSolution {} has {} depots, {} vehicles and {} customers with a search space of {}.",
getInputId(),
solution.getDepotList().size(),
solution.getVehicleList().size(),
solution.getCustomerList().size(),
getFlooredPossibleSolutionSize(possibleSolutionSize));
return solution;
}
// ************************************************************************
// CVRP normal format. See https://neo.lcc.uma.es/vrp/
// ************************************************************************
public void readVrpWebFormat() throws IOException {
readVrpWebHeaders();
readVrpWebLocationList();
readVrpWebCustomerList();
readVrpWebDepotList();
createVrpWebVehicleList();
readConstantLine("EOF");
}
private void readVrpWebHeaders() throws IOException {
skipOptionalConstantLines("COMMENT *:.*");
String vrpType = readStringValue("TYPE *:");
switch (vrpType) {
case "CVRP":
timewindowed = false;
break;
case "CVRPTW":
timewindowed = true;
Long solutionId = solution.getId();
String solutionName = solution.getName();
solution = new TimeWindowedVehicleRoutingSolution(solutionId);
solution.setName(solutionName);
break;
default:
throw new IllegalArgumentException("The vrpType (" + vrpType + ") is not supported.");
}
customerListSize = readIntegerValue("DIMENSION *:");
String edgeWeightType = readStringValue("EDGE_WEIGHT_TYPE *:");
if (edgeWeightType.equalsIgnoreCase("EUC_2D")) {
solution.setDistanceType(DistanceType.AIR_DISTANCE);
} else if (edgeWeightType.equalsIgnoreCase("EXPLICIT")) {
solution.setDistanceType(DistanceType.ROAD_DISTANCE);
String edgeWeightFormat = readStringValue("EDGE_WEIGHT_FORMAT *:");
if (!edgeWeightFormat.equalsIgnoreCase("FULL_MATRIX")) {
throw new IllegalArgumentException("The edgeWeightFormat (" + edgeWeightFormat + ") is not supported.");
}
} else if (edgeWeightType.equalsIgnoreCase("SEGMENTED_EXPLICIT")) {
solution.setDistanceType(DistanceType.SEGMENTED_ROAD_DISTANCE);
String edgeWeightFormat = readStringValue("EDGE_WEIGHT_FORMAT *:");
if (!edgeWeightFormat.equalsIgnoreCase("HUB_AND_NEARBY_MATRIX")) {
throw new IllegalArgumentException("The edgeWeightFormat (" + edgeWeightFormat + ") is not supported.");
}
} else {
throw new IllegalArgumentException("The edgeWeightType (" + edgeWeightType + ") is not supported.");
}
solution.setDistanceUnitOfMeasurement(readOptionalStringValue("EDGE_WEIGHT_UNIT_OF_MEASUREMENT *:", "distance"));
capacity = readIntegerValue("CAPACITY *:");
}
private void readVrpWebLocationList() throws IOException {
DistanceType distanceType = solution.getDistanceType();
List hubLocationList = null;
locationMap = new LinkedHashMap<>(customerListSize);
if (distanceType == DistanceType.SEGMENTED_ROAD_DISTANCE) {
int hubListSize = readIntegerValue("HUBS *:");
hubLocationList = new ArrayList<>(hubListSize);
readConstantLine("HUB_COORD_SECTION");
for (int i = 0; i < hubListSize; i++) {
String line = bufferedReader.readLine();
String[] lineTokens = splitBySpacesOrTabs(line.trim(), 3, 4);
HubSegmentLocation location = new HubSegmentLocation(Long.parseLong(lineTokens[0]),
Double.parseDouble(lineTokens[1]), Double.parseDouble(lineTokens[2]));
if (lineTokens.length >= 4) {
location.setName(lineTokens[3]);
}
hubLocationList.add(location);
locationMap.put(location.getId(), location);
}
}
List customerLocationList = new ArrayList<>(customerListSize);
readConstantLine("NODE_COORD_SECTION");
for (int i = 0; i < customerListSize; i++) {
String line = bufferedReader.readLine();
String[] lineTokens = splitBySpacesOrTabs(line.trim(), 3, 4);
long id = Long.parseLong(lineTokens[0]);
double latitude = Double.parseDouble(lineTokens[1]);
double longitude = Double.parseDouble(lineTokens[2]);
Location location;
switch (distanceType) {
case AIR_DISTANCE:
location = new AirLocation(id, latitude, longitude);
break;
case ROAD_DISTANCE:
location = new RoadLocation(id, latitude, longitude);
break;
case SEGMENTED_ROAD_DISTANCE:
location = new RoadSegmentLocation(id, latitude, longitude);
break;
default:
throw new IllegalStateException("The distanceType (" + distanceType
+ ") is not implemented.");
}
if (lineTokens.length >= 4) {
location.setName(lineTokens[3]);
}
customerLocationList.add(location);
locationMap.put(location.getId(), location);
}
if (distanceType == DistanceType.ROAD_DISTANCE) {
readConstantLine("EDGE_WEIGHT_SECTION");
for (int i = 0; i < customerListSize; i++) {
RoadLocation location = (RoadLocation) customerLocationList.get(i);
Map travelDistanceMap = new LinkedHashMap<>(customerListSize);
String line = bufferedReader.readLine();
String[] lineTokens = splitBySpacesOrTabs(line.trim(), customerListSize);
for (int j = 0; j < customerListSize; j++) {
double travelDistance = Double.parseDouble(lineTokens[j]);
if (i == j) {
if (travelDistance != 0.0) {
throw new IllegalStateException("The travelDistance (" + travelDistance
+ ") should be zero.");
}
} else {
RoadLocation otherLocation = (RoadLocation) customerLocationList.get(j);
travelDistanceMap.put(otherLocation, travelDistance);
}
}
location.setTravelDistanceMap(travelDistanceMap);
}
}
if (distanceType == DistanceType.SEGMENTED_ROAD_DISTANCE) {
readConstantLine("SEGMENTED_EDGE_WEIGHT_SECTION");
int locationListSize = hubLocationList.size() + customerListSize;
for (int i = 0; i < locationListSize; i++) {
String line = bufferedReader.readLine();
String[] lineTokens = splitBySpacesOrTabs(line.trim(), 3, null);
if (lineTokens.length % 2 != 1) {
throw new IllegalArgumentException("Invalid SEGMENTED_EDGE_WEIGHT_SECTION line (" + line + ").");
}
long id = Long.parseLong(lineTokens[0]);
Location location = locationMap.get(id);
if (location == null) {
throw new IllegalArgumentException(
"The location with id (" + id + ") of line (" + line + ") does not exist.");
}
Map hubTravelDistanceMap = new LinkedHashMap<>(lineTokens.length / 2);
Map nearbyTravelDistanceMap = new LinkedHashMap<>(lineTokens.length / 2);
for (int j = 1; j < lineTokens.length; j += 2) {
Location otherLocation = locationMap.get(Long.parseLong(lineTokens[j]));
double travelDistance = Double.parseDouble(lineTokens[j + 1]);
if (otherLocation instanceof HubSegmentLocation) {
hubTravelDistanceMap.put((HubSegmentLocation) otherLocation, travelDistance);
} else {
nearbyTravelDistanceMap.put((RoadSegmentLocation) otherLocation, travelDistance);
}
}
if (location instanceof HubSegmentLocation) {
HubSegmentLocation hubSegmentLocation = (HubSegmentLocation) location;
hubSegmentLocation.setHubTravelDistanceMap(hubTravelDistanceMap);
hubSegmentLocation.setNearbyTravelDistanceMap(nearbyTravelDistanceMap);
} else {
RoadSegmentLocation roadSegmentLocation = (RoadSegmentLocation) location;
roadSegmentLocation.setHubTravelDistanceMap(hubTravelDistanceMap);
roadSegmentLocation.setNearbyTravelDistanceMap(nearbyTravelDistanceMap);
}
}
}
List locationList;
if (distanceType == DistanceType.SEGMENTED_ROAD_DISTANCE) {
locationList = new ArrayList<>(hubLocationList.size() + customerListSize);
locationList.addAll(hubLocationList);
locationList.addAll(customerLocationList);
} else {
locationList = customerLocationList;
}
solution.setLocationList(locationList);
}
private void readVrpWebCustomerList() throws IOException {
readConstantLine("DEMAND_SECTION");
depotList = new ArrayList<>(customerListSize);
List customerList = new ArrayList<>(customerListSize);
for (int i = 0; i < customerListSize; i++) {
String line = bufferedReader.readLine();
String[] lineTokens = splitBySpacesOrTabs(line.trim(), timewindowed ? 5 : 2);
long id = Long.parseLong(lineTokens[0]);
int demand = Integer.parseInt(lineTokens[1]);
// Depots have no demand
if (demand == 0) {
Location location = locationMap.get(id);
if (location == null) {
throw new IllegalArgumentException("The depot with id (" + id
+ ") has no location (" + location + ").");
}
if (timewindowed) {
long serviceDuration = Long.parseLong(lineTokens[4]);
if (serviceDuration != 0L) {
throw new IllegalArgumentException("The depot with id (" + id
+ ") has a serviceDuration (" + serviceDuration + ") that is not 0.");
}
depotList.add(new TimeWindowedDepot(id, location, Long.parseLong(lineTokens[2]),
Long.parseLong(lineTokens[3])));
} else {
depotList.add(new Depot(id, location));
}
} else {
Location location = locationMap.get(id);
if (location == null) {
throw new IllegalArgumentException("The customer with id (" + id
+ ") has no location (" + location + ").");
}
// Notice that we leave the PlanningVariable properties on null
if (timewindowed) {
customerList.add(new TimeWindowedCustomer(id, location, demand, Long.parseLong(lineTokens[2]),
Long.parseLong(lineTokens[3]), Long.parseLong(lineTokens[4])));
} else {
customerList.add(new Customer(id, location, demand));
}
}
}
solution.setCustomerList(customerList);
solution.setDepotList(depotList);
}
private void readVrpWebDepotList() throws IOException {
readConstantLine("DEPOT_SECTION");
int depotCount = 0;
long id = readLongValue();
while (id != -1) {
depotCount++;
id = readLongValue();
}
if (depotCount != depotList.size()) {
throw new IllegalStateException("The number of demands with 0 demand (" + depotList.size()
+ ") differs from the number of depots (" + depotCount + ").");
}
}
private void createVrpWebVehicleList() throws IOException {
String inputFileName = inputFile.getName();
if (inputFileName.toLowerCase().startsWith("tutorial")) {
vehicleListSize = readIntegerValue("VEHICLES *:");
} else {
String inputFileNameRegex = "^.+\\-k(\\d+)\\.vrp$";
if (!inputFileName.matches(inputFileNameRegex)) {
throw new IllegalArgumentException("The inputFileName (" + inputFileName
+ ") does not match the inputFileNameRegex (" + inputFileNameRegex + ").");
}
String vehicleListSizeString = inputFileName.replaceAll(inputFileNameRegex, "$1");
try {
vehicleListSize = Integer.parseInt(vehicleListSizeString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The inputFileName (" + inputFileName
+ ") has a vehicleListSizeString (" + vehicleListSizeString + ") that is not a number.", e);
}
}
createVehicleList();
}
private void createVehicleList() {
List vehicleList = new ArrayList<>(vehicleListSize);
long id = 0;
for (int i = 0; i < vehicleListSize; i++) {
// Round robin the vehicles to a depot if there are multiple depots
Vehicle vehicle = new Vehicle(i, capacity, depotList.get(i % depotList.size()));
vehicleList.add(vehicle);
}
solution.setVehicleList(vehicleList);
}
// ************************************************************************
// CVRP coursera format. See https://class.coursera.org/optimization-001/
// ************************************************************************
public void readCourseraFormat() throws IOException {
solution.setDistanceType(DistanceType.AIR_DISTANCE);
solution.setDistanceUnitOfMeasurement("distance");
List locationList = new ArrayList<>(customerListSize);
depotList = new ArrayList<>(1);
List customerList = new ArrayList<>(customerListSize);
locationMap = new LinkedHashMap<>(customerListSize);
for (int i = 0; i < customerListSize; i++) {
String line = bufferedReader.readLine();
String[] lineTokens = splitBySpacesOrTabs(line.trim(), 3, 4);
AirLocation location = new AirLocation(i, Double.parseDouble(lineTokens[1]), Double.parseDouble(lineTokens[2]));
if (lineTokens.length >= 4) {
location.setName(lineTokens[3]);
}
locationList.add(location);
if (i == 0) {
Depot depot = new Depot(i, location);
depotList.add(depot);
} else {
int demand = Integer.parseInt(lineTokens[0]);
// Do not add a customer that has no demand
if (demand != 0) {
// Notice that we leave the PlanningVariable properties on null
Customer customer = new Customer(i, location, demand);
customerList.add(customer);
}
}
}
solution.setLocationList(locationList);
solution.setDepotList(depotList);
solution.setCustomerList(customerList);
createVehicleList();
}
// ************************************************************************
// CVRPTW normal format. See https://neo.lcc.uma.es/vrp/
// ************************************************************************
public void readTimeWindowedFormat() throws IOException {
readTimeWindowedHeaders();
readTimeWindowedDepotAndCustomers();
createVehicleList();
}
private void readTimeWindowedHeaders() throws IOException {
solution.setDistanceType(DistanceType.AIR_DISTANCE);
solution.setDistanceUnitOfMeasurement("distance");
readEmptyLine();
readConstantLine("VEHICLE");
readConstantLine("NUMBER +CAPACITY");
String[] lineTokens = splitBySpacesOrTabs(readStringValue(), 2);
vehicleListSize = Integer.parseInt(lineTokens[0]);
capacity = Integer.parseInt(lineTokens[1]);
readEmptyLine();
readConstantLine("CUSTOMER");
readConstantLine(
"CUST\\s+NO\\.\\s+XCOORD\\.\\s+YCOORD\\.\\s+DEMAND\\s+READY\\s+TIME\\s+DUE\\s+DATE\\s+SERVICE\\s+TIME");
readEmptyLine();
}
private void readTimeWindowedDepotAndCustomers() throws IOException {
String line = bufferedReader.readLine();
int locationListSizeEstimation = 25;
List locationList = new ArrayList<>(locationListSizeEstimation);
depotList = new ArrayList<>(1);
TimeWindowedDepot depot = null;
List customerList = new ArrayList<>(locationListSizeEstimation);
boolean first = true;
while (line != null && !line.trim().isEmpty()) {
String[] lineTokens = splitBySpacesOrTabs(line.trim(), 7);
long id = Long.parseLong(lineTokens[0]);
AirLocation location =
new AirLocation(id, Double.parseDouble(lineTokens[1]), Double.parseDouble(lineTokens[2]));
locationList.add(location);
int demand = Integer.parseInt(lineTokens[3]);
long readyTime = Long.parseLong(lineTokens[4]) * 1000L;
long dueTime = Long.parseLong(lineTokens[5]) * 1000L;
long serviceDuration = Long.parseLong(lineTokens[6]) * 1000L;
if (first) {
if (demand != 0) {
throw new IllegalArgumentException("The depot with id (" + id
+ ") has a demand (" + demand + ").");
}
if (serviceDuration != 0) {
throw new IllegalArgumentException("The depot with id (" + id
+ ") has a serviceDuration (" + serviceDuration + ").");
}
depot = new TimeWindowedDepot(id, location, readyTime, dueTime);
depotList.add(depot);
first = false;
} else {
// Score constraint arrivalAfterDueTimeAtDepot is a built-in hard constraint in VehicleRoutingImporter
long maximumDueTime = depot.getDueTime()
- serviceDuration - location.getDistanceTo(depot.getLocation());
if (dueTime > maximumDueTime) {
logger.warn("The customer ({})'s dueTime ({}) was automatically reduced" +
" to maximumDueTime ({}) because of the depot's dueTime ({}).",
id, dueTime, maximumDueTime, depot.getDueTime());
dueTime = maximumDueTime;
}
// Do not add a customer that has no demand
if (demand != 0) {
// Notice that we leave the PlanningVariable properties on null
TimeWindowedCustomer customer =
new TimeWindowedCustomer(id, location, demand, readyTime, dueTime, serviceDuration);
customerList.add(customer);
}
}
line = bufferedReader.readLine();
}
solution.setLocationList(locationList);
solution.setDepotList(depotList);
solution.setCustomerList(customerList);
customerListSize = locationList.size();
}
}
}