nearestMessengers, VehicleRoutingTransportCosts routingCosts, RouteAndActivityStateGetter stateManager) {
this.bestMessengers = nearestMessengers;
this.routingCosts = routingCosts;
this.stateManager = stateManager;
this.latest_act_arrival_time_stateId = latest_act_arrival_time;
}
@Override
public ConstraintsStatus fulfilled(JobInsertionContext iFacts,TourActivity prevAct, TourActivity newAct, TourActivity nextAct, double prevActDepTime) {
//make sure vehicle can manage direct path
double arrTime_at_nextAct_onDirectRoute = prevActDepTime + routingCosts.getTransportTime(prevAct.getLocationId(), nextAct.getLocationId(), prevActDepTime, iFacts.getNewDriver(), iFacts.getNewVehicle());
Double latest_arrTime_at_nextAct = stateManager.getActivityState(nextAct, latest_act_arrival_time_stateId, Double.class);
if(latest_arrTime_at_nextAct == null) latest_arrTime_at_nextAct = nextAct.getTheoreticalLatestOperationStartTime();
if(arrTime_at_nextAct_onDirectRoute > latest_arrTime_at_nextAct){
//constraint can never be fulfilled anymore, thus .NOT_FULFILLED_BREAK
return ConstraintsStatus.NOT_FULFILLED_BREAK;
}
double arrTime_at_newAct = prevActDepTime + routingCosts.getTransportTime(prevAct.getLocationId(), newAct.getLocationId(), prevActDepTime, iFacts.getNewDriver(), iFacts.getNewVehicle());
//local impact
//no matter whether it is a pickupShipment or deliverShipment activities. both arrivalTimes must be < 3*best.
double directTimeOfNearestMessenger = bestMessengers.get(((JobActivity) newAct).getJob().getId());
if(arrTime_at_newAct > 3 * directTimeOfNearestMessenger){
//not fulfilled AND it can never be fulfilled anymore by going forward in route, thus NOT_FULFILLED_BREAK
return ConstraintsStatus.NOT_FULFILLED_BREAK;
}
//impact on whole route, since insertion of newAct shifts all subsequent activities forward in time
double departureTime_at_newAct = arrTime_at_newAct + newAct.getOperationTime();
double latest_arrTime_at_newAct = latest_arrTime_at_nextAct - routingCosts.getTransportTime(newAct.getLocationId(),nextAct.getLocationId(),departureTime_at_newAct,iFacts.getNewDriver(),iFacts.getNewVehicle());
if(arrTime_at_newAct > latest_arrTime_at_newAct){
return ConstraintsStatus.NOT_FULFILLED;
}
double arrTime_at_nextAct = departureTime_at_newAct + routingCosts.getTransportTime(newAct.getLocationId(), nextAct.getLocationId(), departureTime_at_newAct, iFacts.getNewDriver(), iFacts.getNewVehicle());
//here you need an activity state
if(arrTime_at_nextAct > latest_arrTime_at_nextAct){
return ConstraintsStatus.NOT_FULFILLED;
}
return ConstraintsStatus.FULFILLED;
}
}
/**
* When inserting the activities of an envelope which are pickup and deliver envelope, this constraint makes insertion procedure to ignore messengers that are too far away to meet the 3*directTime-Constraint.
*
* one does not need this constraint. but it is faster. the earlier the solution-space can be constraint the better/faster.
* @author schroeder
*
*/
static class IgnoreMessengerThatCanNeverMeetTimeRequirements implements HardRouteStateLevelConstraint {
private final Map bestMessengers;
private final VehicleRoutingTransportCosts routingCosts;
public IgnoreMessengerThatCanNeverMeetTimeRequirements(Map bestMessengers, VehicleRoutingTransportCosts routingCosts) {
super();
this.bestMessengers = bestMessengers;
this.routingCosts = routingCosts;
}
@Override
public boolean fulfilled(JobInsertionContext insertionContext) {
double timeOfDirectRoute = getTimeOfDirectRoute(insertionContext.getJob(), insertionContext.getNewVehicle(), routingCosts);
double timeOfNearestMessenger = bestMessengers.get(insertionContext.getJob().getId());
return !(timeOfDirectRoute > 3 * timeOfNearestMessenger);
}
}
/**
* updates the state "latest-activity-start-time" (required above) once route/activity states changed, i.e. when removing or inserting an envelope-activity
*
* thus once either the insertion-procedure starts or an envelope has been inserted, this visitor runs through the route in reverse order (i.e. starting with the end of the route) and
* calculates the latest-activity-start-time (or latest-activity-arrival-time) which is the time to just meet the constraints of subsequent activities.
*
* @author schroeder
*
*/
static class UpdateLatestActivityStartTimes implements StateUpdater, ReverseActivityVisitor {
private final StateManager stateManager;
private final VehicleRoutingTransportCosts routingCosts;
private final Map bestMessengers;
private VehicleRoute route;
private TourActivity prevAct;
private double latest_arrTime_at_prevAct;
private final StateFactory.StateId latest_act_arrival_time_stateId;
public UpdateLatestActivityStartTimes(StateFactory.StateId latest_act_arrival_time, StateManager stateManager, VehicleRoutingTransportCosts routingCosts, Map bestMessengers) {
super();
this.stateManager = stateManager;
this.routingCosts = routingCosts;
this.bestMessengers = bestMessengers;
this.latest_act_arrival_time_stateId = latest_act_arrival_time;
}
@Override
public void begin(VehicleRoute route) {
this.route = route;
latest_arrTime_at_prevAct = route.getEnd().getTheoreticalLatestOperationStartTime();
prevAct = route.getEnd();
}
@Override
public void visit(TourActivity currAct) {
double timeOfNearestMessenger = bestMessengers.get(((JobActivity)currAct).getJob().getId());
double potential_latest_arrTime_at_currAct =
latest_arrTime_at_prevAct - routingCosts.getBackwardTransportTime(currAct.getLocationId(), prevAct.getLocationId(), latest_arrTime_at_prevAct, route.getDriver(),route.getVehicle()) - currAct.getOperationTime();
double latest_arrTime_at_currAct = Math.min(3*timeOfNearestMessenger, potential_latest_arrTime_at_currAct);
stateManager.putActivityState(currAct, latest_act_arrival_time_stateId, Double.class, latest_arrTime_at_currAct);
assert currAct.getArrTime() <= latest_arrTime_at_currAct : "this must not be since it breaks condition; actArrTime: " + currAct.getArrTime() + " latestArrTime: " + latest_arrTime_at_currAct + " vehicle: " + route.getVehicle().getId();
latest_arrTime_at_prevAct = latest_arrTime_at_currAct;
prevAct = currAct;
}
@Override
public void finish() {}
}
/**
*
* @throws IOException
*/
public static void main(String[] args) throws IOException {
Examples.createOutputFolder();
/*
build the problem
*/
VehicleRoutingProblem.Builder problemBuilder = VehicleRoutingProblem.Builder.newInstance();
problemBuilder.setFleetSize(FleetSize.FINITE);
readEnvelopes(problemBuilder);
readMessengers(problemBuilder);
//add constraints to problem
VehicleRoutingTransportCosts routingCosts = new CrowFlyCosts(problemBuilder.getLocations()); //which is the default VehicleRoutingTransportCosts in builder above
problemBuilder.setRoutingCost(routingCosts);
//finally build the problem
problemBuilder.addPenaltyVehicles(20.0,50000);
VehicleRoutingProblem bicycleMessengerProblem = problemBuilder.build();
/*
define states and constraints
*/
//map mapping nearest messengers, i.e. for each envelope the direct-delivery-time with the fastest messenger is stored here
Map nearestMessengers = getNearestMessengers(routingCosts, problemBuilder.getAddedJobs(), problemBuilder.getAddedVehicles());
//define stateManager to update the required activity-state: "latest-activity-start-time"
StateManager stateManager = new StateManager(bicycleMessengerProblem);
//create state
StateFactory.StateId latest_act_arrival_time_stateId = stateManager.createStateId("latest-act-arrival-time");
//default states makes it more comfortable since state has a value and cannot be null (thus u dont need to check nulls)
stateManager.addDefaultActivityState(latest_act_arrival_time_stateId, Double.class, Double.MAX_VALUE);
//and make sure you update the activity-state "latest-activity-start-time" the way it is defined above
stateManager.addStateUpdater(new UpdateLatestActivityStartTimes(latest_act_arrival_time_stateId, stateManager, routingCosts, nearestMessengers));
stateManager.updateLoadStates();
ConstraintManager constraintManager = new ConstraintManager(bicycleMessengerProblem,stateManager);
constraintManager.addLoadConstraint();
constraintManager.addConstraint(new ThreeTimesLessThanBestDirectRouteConstraint(latest_act_arrival_time_stateId,nearestMessengers,routingCosts,stateManager), ConstraintManager.Priority.CRITICAL);
constraintManager.addConstraint(new IgnoreMessengerThatCanNeverMeetTimeRequirements(nearestMessengers, routingCosts));
//create your algorithm
VehicleRoutingAlgorithmBuilder vraBuilder = new VehicleRoutingAlgorithmBuilder(bicycleMessengerProblem,"input/algorithmConfig_open.xml");
vraBuilder.addDefaultCostCalculators();
vraBuilder.setStateAndConstraintManager(stateManager, constraintManager);
VehicleRoutingAlgorithm algorithm = vraBuilder.build();
algorithm.setNuOfIterations(2000);
VariationCoefficientTermination prematureAlgorithmTermination = new VariationCoefficientTermination(200, 0.001);
algorithm.setPrematureAlgorithmTermination(prematureAlgorithmTermination);
algorithm.addListener(prematureAlgorithmTermination);
algorithm.addListener(new AlgorithmSearchProgressChartListener("output/progress.png"));
//search
Collection solutions = algorithm.searchSolutions();
//this is just to ensure that solution meet the above constraints
validateSolution(Solutions.bestOf(solutions), bicycleMessengerProblem, nearestMessengers);
SolutionPrinter.print(bicycleMessengerProblem, Solutions.bestOf(solutions), Print.VERBOSE);
//you may want to plot the problem
Plotter plotter = new Plotter(bicycleMessengerProblem);
// plotter.setBoundingBox(10000, 47500, 20000, 67500);
plotter.plotShipments(true);
plotter.plot("output/bicycleMessengerProblem.png", "bicycleMessenger");
//and the problem as well as the solution
Plotter plotter1 = new Plotter(bicycleMessengerProblem, Solutions.bestOf(solutions));
plotter1.plotShipments(false);
// plotter1.setBoundingBox(5000, 45500, 25000, 66500);
plotter1.plot("output/bicycleMessengerSolution.png", "bicycleMessenger");
//and write out your solution in xml
// new VrpXMLWriter(bicycleMessengerProblem, solutions).write("output/bicycleMessenger.xml");
new GraphStreamViewer(bicycleMessengerProblem).labelWith(Label.ID).setRenderShipments(true).setRenderDelay(150).display();
//
new GraphStreamViewer(bicycleMessengerProblem, Solutions.bestOf(solutions)).setGraphStreamFrameScalingFactor(1.5).setCameraView(12500, 55000, 0.25).labelWith(Label.ACTIVITY).setRenderShipments(true).setRenderDelay(150).display();
}
//if you wanne run this enable assertion by putting an '-ea' in your vmargument list - Run As --> Run Configurations --> (x)=Arguments --> VM arguments: -ea
private static void validateSolution(VehicleRoutingProblemSolution bestOf, VehicleRoutingProblem bicycleMessengerProblem, Map nearestMessengers) {
for(VehicleRoute route : bestOf.getRoutes()){
for(TourActivity act : route.getActivities()){
if(act.getArrTime() > 3*nearestMessengers.get(((JobActivity)act).getJob().getId())){
SolutionPrinter.print(bicycleMessengerProblem, bestOf, Print.VERBOSE);
throw new IllegalStateException("three times less than ... constraint broken. this must not be. act.getArrTime(): " + act.getArrTime() + " allowed: " + 3*nearestMessengers.get(((JobActivity)act).getJob().getId()));
}
}
if(route.getVehicle().getType() instanceof PenaltyVehicleType){
SolutionPrinter.print(bicycleMessengerProblem, bestOf, Print.VERBOSE);
throw new IllegalStateException("penaltyVehicle in solution. if there is a valid solution, this should not be");
}
}
}
static Map getNearestMessengers(VehicleRoutingTransportCosts routingCosts, Collection envelopes, Collection messengers) {
Map nearestMessengers = new HashMap();
for(Job envelope : envelopes){
double minDirect = Double.MAX_VALUE;
for(Vehicle m : messengers){
double direct = getTimeOfDirectRoute(envelope, m, routingCosts);
if(direct < minDirect){
minDirect = direct;
}
}
nearestMessengers.put(envelope.getId(), minDirect);
}
return nearestMessengers;
}
static double getTimeOfDirectRoute(Job job, Vehicle v, VehicleRoutingTransportCosts routingCosts) {
Shipment envelope = (Shipment) job;
return routingCosts.getTransportTime(v.getStartLocationId(), envelope.getPickupLocation(), 0.0, DriverImpl.noDriver(), v) +
routingCosts.getTransportTime(envelope.getPickupLocation(), envelope.getDeliveryLocation(), 0.0, DriverImpl.noDriver(), v);
}
private static void readEnvelopes(Builder problemBuilder) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(new File("input/bicycle_messenger_demand.txt")));
String line;
boolean firstLine = true;
while((line = reader.readLine()) != null){
if(firstLine) { firstLine = false; continue; }
String[] tokens = line.split("\\s+");
//define your envelope which is basically a shipment from A to B
Shipment envelope = Shipment.Builder.newInstance(tokens[1]).addSizeDimension(0, 1).setPickupCoord(Coordinate.newInstance(Double.parseDouble(tokens[2]), Double.parseDouble(tokens[3])))
.setDeliveryCoord(Coordinate.newInstance(Double.parseDouble(tokens[4]), Double.parseDouble(tokens[5]))).build();
problemBuilder.addJob(envelope);
}
reader.close();
}
private static void readMessengers(Builder problemBuilder) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(new File("input/bicycle_messenger_supply.txt")));
String line;
boolean firstLine = true;
VehicleType messengerType = VehicleTypeImpl.Builder.newInstance("messengerType").addCapacityDimension(0, 15).setCostPerDistance(1).build();
/*
* the algo requires some time and space to search for a valid solution. if you ommit a penalty-type, it probably throws an Exception once it cannot insert an envelope anymore
* thus, give it space by defining a penalty/shadow vehicle with higher variable and fixed costs to up the pressure to find solutions without penalty type
*
* it is important to give it the same typeId as the type you want to shadow
*/
while((line = reader.readLine()) != null){
if(firstLine) { firstLine = false; continue; }
String[] tokens = line.split("\\s+");
//build your vehicle
VehicleImpl vehicle = VehicleImpl.Builder.newInstance(tokens[1]).setStartLocationCoordinate(Coordinate.newInstance(Double.parseDouble(tokens[2]), Double.parseDouble(tokens[3])))
.setReturnToDepot(false).setType(messengerType).build();
problemBuilder.addVehicle(vehicle);
}
reader.close();
}
}