diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleType.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleType.java index 753254561..61d22c15f 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleType.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleType.java @@ -33,6 +33,11 @@ public interface VehicleType { */ String getTypeId(); + /** + * Category of vehicle, allow to use different Cost Matrix according to vehicle's category + */ + int getCategoryId(); + /** * Returns capacity dimensions. * diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java index f4279a933..ea198709a 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java @@ -124,6 +124,7 @@ public static VehicleTypeImpl.Builder newInstance(String id) { } private String id; + private int categoryId = 0; private double maxVelo = Double.MAX_VALUE; /** * default cost values for default vehicle type @@ -148,7 +149,6 @@ private Builder(String id) { this.id = id; } - /** * Sets user specific domain data associated with the object. * @@ -167,6 +167,11 @@ public Builder setUserData(Object userData) { return this; } + public VehicleTypeImpl.Builder setCategoryId(int categoryId) { + this.categoryId = categoryId; + return this; + } + /** * Sets the maximum velocity this vehicle-type can go [in meter per * seconds]. @@ -333,6 +338,7 @@ public boolean equals(Object o) { if (Double.compare(that.maxVelocity, maxVelocity) != 0) return false; if (!typeId.equals(that.typeId)) return false; + if (categoryId != that.categoryId) return false; if (profile != null ? !profile.equals(that.profile) : that.profile != null) return false; if (!vehicleCostParams.equals(that.vehicleCostParams)) return false; return capacityDimensions.equals(that.capacityDimensions); @@ -351,6 +357,8 @@ public int hashCode() { return result; } + private final int categoryId; + private final String typeId; private final String profile; @@ -370,6 +378,7 @@ public int hashCode() { */ private VehicleTypeImpl(VehicleTypeImpl.Builder builder) { this.userData = builder.userData; + this.categoryId = builder.categoryId; typeId = builder.id; maxVelocity = builder.maxVelo; vehicleCostParams = new VehicleCostParams(builder.fixedCost, builder.perTime, builder.perDistance, builder.perWaitingTime, builder.perServiceTime); @@ -423,4 +432,9 @@ public String getProfile() { return profile; } + @Override + public int getCategoryId() { + return categoryId; + } + } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/util/FastVehicleCategoryRoutingTransportCostsMatrix.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/util/FastVehicleCategoryRoutingTransportCostsMatrix.java new file mode 100644 index 000000000..0170f93c9 --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/util/FastVehicleCategoryRoutingTransportCostsMatrix.java @@ -0,0 +1,179 @@ +package com.graphhopper.jsprit.core.util; + +import com.graphhopper.jsprit.core.problem.Location; +import com.graphhopper.jsprit.core.problem.cost.AbstractForwardVehicleRoutingTransportCosts; +import com.graphhopper.jsprit.core.problem.driver.Driver; +import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; +import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl; + + +public class FastVehicleCategoryRoutingTransportCostsMatrix extends AbstractForwardVehicleRoutingTransportCosts { + + /** + * Builder that builds the matrix. + * + * @author schroeder + */ + public static class Builder { + + private boolean isSymmetric; + + private int vehicleCategoryCount; + + private double[][][] matrix; + + private final int noLocations; + + /** + * Creates a new builder returning the matrix-builder. + *

If you want to consider symmetric matrices, set isSymmetric to true. + * + * @param isSymmetric true if matrix is symmetric, false otherwise + * @param vehicleCategoryCount the count of type of vehicle in the fleet + * @return builder + */ + public static Builder newInstance(int noLocations, boolean isSymmetric, int vehicleCategoryCount) { + return new Builder(noLocations, isSymmetric, vehicleCategoryCount); + } + + private Builder(int noLocations, boolean isSymmetric, int vehicleCategoryCount) { + this.isSymmetric = isSymmetric; + this.vehicleCategoryCount = vehicleCategoryCount; + matrix = new double[noLocations][noLocations][2 * vehicleCategoryCount]; + this.noLocations = noLocations; + } + + /** + * Adds a transport-distance for a particular relation. + * + * @param fromIndex from location index + * @param toIndex to location index + * @param distance the distance to be added + * @param vehicleCategory the category of the vehicle for this distance + * @return builder + */ + public Builder addTransportDistance(int fromIndex, int toIndex, double distance, int vehicleCategory) { + add(fromIndex, toIndex, vehicleCategory * 2, distance); + return this; + } + + private void add(int fromIndex, int toIndex, int indicatorIndex, double value) { + if (isSymmetric) { + if (fromIndex < toIndex) matrix[fromIndex][toIndex][indicatorIndex] = value; + else matrix[toIndex][fromIndex][indicatorIndex] = value; + } else matrix[fromIndex][toIndex][indicatorIndex] = value; + } + + /** + * Adds transport-time for a particular relation. + * + * @param fromIndex from location index + * @param toIndex to location index + * @param time the time to be added + * @param vehicleCategory the category of the vehicle for this time + * @return builder + */ + public Builder addTransportTime(int fromIndex, int toIndex, double time, int vehicleCategory) { + add(fromIndex, toIndex, vehicleCategory * 2 + 1, time); + return this; + } + + public Builder addTransportTimeAndDistance(int fromIndex, int toIndex, double time, double distance, int vehicleCategory) { + addTransportTime(fromIndex, toIndex, time, vehicleCategory); + addTransportDistance(fromIndex, toIndex, distance, vehicleCategory); + return this; + } + /** + * Builds the matrix. + * + * @return matrix + */ + public FastVehicleCategoryRoutingTransportCostsMatrix build() { + return new FastVehicleCategoryRoutingTransportCostsMatrix(this); + } + + + } + + private final boolean isSymmetric; + private final int vehicleCategoryCount; + + private final double[][][] matrix; + + private int noLocations; + + private FastVehicleCategoryRoutingTransportCostsMatrix(Builder builder) { + this.isSymmetric = builder.isSymmetric; + this.vehicleCategoryCount = builder.vehicleCategoryCount; + matrix = builder.matrix; + noLocations = builder.noLocations; + } + + /** + * First dim is from, second to and third indicates whether it is a distance value (index=0) or time value (index=1). + * + * @return + */ + public double[][][] getMatrix() { + return matrix; + } + + @Override + public double getTransportTime(Location from, Location to, double departureTime, Driver driver, Vehicle vehicle) { + if (from.getIndex() < 0 || to.getIndex() < 0) + throw new IllegalArgumentException("index of from " + from + " to " + to + " < 0 "); + int timeIndex = 1; + if (vehicle != null && vehicle.getType() != null && vehicle.getType().getCategoryId() < vehicleCategoryCount) { + timeIndex = vehicle.getType().getCategoryId() * 2 + 1; + } + return get(from.getIndex(), to.getIndex(), timeIndex); + } + + private double get(int from, int to, int indicatorIndex) { + double value; + if (isSymmetric) { + if (from < to) value = matrix[from][to][indicatorIndex]; + else value = matrix[to][from][indicatorIndex]; + } else { + value = matrix[from][to][indicatorIndex]; + } + return value; + } + + /** + * Returns the distance from to to. + * + * @param fromIndex from location index + * @param toIndex to location index + * @param vehicle the vehicle + * @return the distance + */ + private double getDistance(int fromIndex, int toIndex, Vehicle vehicle) { + int distanceIndex = 0; + if (vehicle != null && vehicle.getType().getCategoryId() < vehicleCategoryCount) { + distanceIndex = vehicle.getType().getCategoryId() * 2; + } + return get(fromIndex, toIndex, distanceIndex); + } + + @Override + public double getDistance(Location from, Location to, double departureTime, Vehicle vehicle) { + return getDistance(from.getIndex(), to.getIndex(), vehicle); + } + + @Override + public double getTransportCost(Location from, Location to, double departureTime, Driver driver, Vehicle vehicle) { + if (from.getIndex() < 0 || to.getIndex() < 0) + throw new IllegalArgumentException("index of from " + from + " to " + to + " < 0 "); + if (vehicle == null) + return getDistance(from.getIndex(), to.getIndex(), null); + VehicleTypeImpl.VehicleCostParams costParams = vehicle.getType().getVehicleCostParams(); + return costParams.perDistanceUnit * getDistance(from.getIndex(), to.getIndex(), vehicle) + costParams.perTransportTimeUnit * getTransportTime(from, to, departureTime, driver, vehicle); + } + + public int getNoLocations() { + return noLocations; + } + + +}