/*******************************************************************************
* Copyright (C) 2013 Stefan Schroeder
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3.0 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
A routing problem is defined as jobs, vehicles and costs. * *
To construct the problem, use VehicleRoutingProblem.Builder (VehicleRoutingProblem.Builder.newInstance()). * *
By default, fleetSize is INFINITE and fleetComposition is HOMOGENEOUS, transport-costs are calculated as euclidean-distance (CrowFlyCosts), * and activity-costs are set to DefaultVehicleRoutingActivityCosts which represent hard time-windows (missed time-windows are penalyzed with Double.MAX_VALUE). * * * * @author stefan schroeder * */ public class VehicleRoutingProblem { /** * Overall problem constraints. * *
DELIIVERIES_FIRST corresponds to the vehicle routing problem with back hauls, i.e. before a vehicle is not entirely unloaded, no pickup can be made.
*
* @author stefan
*
*/
public enum Constraint {
DELIVERIES_FIRST
}
/**
* Builder to build the routing-problem.
*
* @author stefan schroeder
*
*/
public static class Builder {
/**
* Returns a new instance of this builder.
*
* @return builder
*/
public static Builder newInstance(){ return new Builder(); }
private VehicleRoutingTransportCosts transportCosts;
private VehicleRoutingActivityCosts activityCosts = new DefaultVehicleRoutingActivityCosts();
private Map Locations are cached when adding a shipment, service, depot, vehicle.
*
* @return locations
*
*/
public Locations getLocations(){
return new Locations() {
@Override
public Coordinate getCoord(String id) {
return coordinates.get(id);
}
};
}
public void addProblemConstraint(Constraint constraint){
if(!problemConstraints.contains(constraint)) problemConstraints.add(constraint);
}
/**
* Sets routing costs.
*
* @param costs
* @return builder
* @see VehicleRoutingTransportCosts
*/
public Builder setRoutingCost(VehicleRoutingTransportCosts costs){
this.transportCosts = costs;
return this;
}
/**
* Sets the type of fleetSize.
*
* FleetSize is either FleetSize.INFINITE or FleetSize.FINITE
*
* @param fleetSize
* @return
*/
public Builder setFleetSize(FleetSize fleetSize){
this.fleetSize = fleetSize;
return this;
}
/**
* Sets the fleetComposition.
*
* FleetComposition is either FleetComposition.HETEROGENEOUS or FleetComposition.HOMOGENEOUS
*
* @param fleetComposition
* @return
*/
public Builder setFleetComposition(FleetComposition fleetComposition){
this.fleetComposition = fleetComposition;
return this;
}
/**
* Adds a service to jobList.
*
* If jobList already contains service, a warning message is printed, and the existing job will be overwritten.
*
* @param service
* @return
*/
public Builder addService(Service service){
coordinates.put(service.getLocationId(), service.getCoord());
if(jobs.containsKey(service.getId())){ logger.warn("service " + service + " already in job list. overrides existing job."); }
jobs.put(service.getId(),service);
services.add(service);
return this;
}
/**
* Adds a job which is either a service or a shipment.
*
* @param job
* @return
* @throws IllegalStateException if job is neither a shipment or a service.
*/
public Builder addJob(Job job) {
if(job instanceof Service) {
addService((Service) job);
}
else throw new IllegalStateException("job can only be a shipment or a service, but is instance of " + job.getClass());
return this;
}
/**
* Adds a vehicle.
*
*
* @param vehicle
* @return
*/
public Builder addVehicle(Vehicle vehicle) {
vehicles.add(vehicle);
if(!vehicleTypes.contains(vehicle.getType())){
vehicleTypes.add(vehicle.getType());
}
coordinates.put(vehicle.getLocationId(), vehicle.getCoord());
return this;
}
/**
* Adds a vehicleType.
*
* @param type
* @return builder
*/
public Builder addVehicleType(VehicleType type){
vehicleTypes.add(type);
return this;
}
/**
* Sets the neighborhood.
*
* @param neighborhood
* @return
*/
public Builder setNeighborhood(Neighborhood neighborhood){
this.neighborhood = neighborhood;
return this;
}
/**
* Sets the activityCostFunction that considers also activities on a vehicle-route.
*
* Here you can consider missed time-windows for example. By default, this is set to a DefaultVehicleActivityCostFunction.
*
* @param activityCosts
* @return
* @see VehicleRoutingTransportCosts, DefaultVehicleRouteCostFunction
*/
public Builder setActivityCosts(VehicleRoutingActivityCosts activityCosts){
this.activityCosts = activityCosts;
return this;
}
/**
* Builds the {@link VehicleRoutingProblem}.
*
* If {@link VehicleRoutingTransportCosts} are not set, {@link CrowFlyCosts} is used.
*
* @return {@link VehicleRoutingProblem}
*/
public VehicleRoutingProblem build() {
log.info("build problem ...");
if(transportCosts == null){
logger.warn("set routing costs crowFlyDistance.");
transportCosts = new CrowFlyCosts(getLocations());
}
return new VehicleRoutingProblem(this);
}
public Builder addLocation(String id, Coordinate coord) {
coordinates.put(id, coord);
return this;
}
/**
* Adds a collection of jobs.
*
* @param jobs
* @return
*/
public Builder addAllJobs(Collection By default, it is INFINITE.
*
* @return either FleetSize.INFINITE or FleetSize.FINITE
*/
public FleetSize getFleetSize() {
return fleetSize;
}
/**
* Returns the unmodifiable job map.
*
* @return unmodifiable jobMap
*/
public Map