Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dynamic service time // group activities together #508

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public Collection<Vehicle> get(VehicleRoute vehicleRoute) {
stateManager.addStateUpdater(twUpdater);
stateManager.updateSkillStates();

stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), ActivityTimeTracker.ActivityPolicy.AS_SOON_AS_TIME_WINDOW_OPENS, vrp.getActivityCosts()));
stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), ActivityTimeTracker.ActivityPolicy.AS_SOON_AS_TIME_WINDOW_OPENS_WITHIN_GROUP, vrp.getActivityCosts()));
stateManager.addStateUpdater(new UpdateVariableCosts(vrp.getActivityCosts(), vrp.getTransportCosts(), stateManager));
stateManager.addStateUpdater(new UpdateFutureWaitingTimes(stateManager, vrp.getTransportCosts()));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public class UpdateActivityTimes implements ActivityVisitor, StateUpdater {
*/
public UpdateActivityTimes(ForwardTransportTime transportTime, VehicleRoutingActivityCosts activityCosts) {
super();
timeTracker = new ActivityTimeTracker(transportTime,activityCosts );
timeTracker = new ActivityTimeTracker(transportTime, activityCosts);
}

public UpdateActivityTimes(ForwardTransportTime transportTime, ActivityTimeTracker.ActivityPolicy activityPolicy, VehicleRoutingActivityCosts activityCosts) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public class ActivityTimeTracker implements ActivityVisitor {

public static enum ActivityPolicy {

AS_SOON_AS_TIME_WINDOW_OPENS, AS_SOON_AS_ARRIVED
AS_SOON_AS_TIME_WINDOW_OPENS, AS_SOON_AS_ARRIVED, AS_SOON_AS_TIME_WINDOW_OPENS_WITHIN_GROUP

}

Expand Down Expand Up @@ -85,22 +85,39 @@ public void visit(TourActivity activity) {
double transportTime = this.transportTime.getTransportTime(prevAct.getLocation(), activity.getLocation(), startAtPrevAct, route.getDriver(), route.getVehicle());
double arrivalTimeAtCurrAct = startAtPrevAct + transportTime;

actArrTime = arrivalTimeAtCurrAct;
// modify the activity arrival time if this activity can be grouped with the previous one
// they will both have the same arrival and end times afterwards
if (canGroupActivities(activity, arrivalTimeAtCurrAct)) {
actArrTime = arrivalTimeAtCurrAct - prevAct.getOperationTime();
} else {
actArrTime = arrivalTimeAtCurrAct;
}
double operationStartTime;

if (activityPolicy.equals(ActivityPolicy.AS_SOON_AS_TIME_WINDOW_OPENS)) {
operationStartTime = Math.max(activity.getTheoreticalEarliestOperationStartTime(), arrivalTimeAtCurrAct);
} else if (activityPolicy.equals(ActivityPolicy.AS_SOON_AS_ARRIVED)) {
operationStartTime = actArrTime;
} else if (activityPolicy.equals(ActivityPolicy.AS_SOON_AS_TIME_WINDOW_OPENS_WITHIN_GROUP)) {
operationStartTime = Math.max(activity.getTheoreticalEarliestOperationStartTime(), actArrTime);
} else operationStartTime = actArrTime;

double operationEndTime = operationStartTime + activityCosts.getActivityDuration(activity,actArrTime,route.getDriver(),route.getVehicle());
double operationEndTime;
// if the current activity can be grouped with the previous one adjust the operation end time
// select the operation time which is bigger
// as we iterate over each activity, we need to change the operation end time of the previous activity so that they have the same end time
// (we didn't know when inserting the previous activity, that we should use the operating time of the current activity)
if (canGroupActivities(activity, arrivalTimeAtCurrAct)) {
operationEndTime = operationStartTime + Math.max(prevAct.getOperationTime(), activity.getOperationTime());
prevAct.setEndTime(operationEndTime);
} else {
operationEndTime = operationStartTime + activity.getOperationTime();
}

actEndTime = operationEndTime;

prevAct = activity;
startAtPrevAct = operationEndTime;

}

@Override
Expand All @@ -115,4 +132,19 @@ public void finish() {
}


private boolean canGroupActivities(TourActivity activity, double arrivalTimeAtCurrAct) {
if (!activityPolicy.equals(ActivityPolicy.AS_SOON_AS_TIME_WINDOW_OPENS_WITHIN_GROUP)) {
return false;
}

// group activities if the end time of the previous activity is matching the arrival time of the current activity
if (Double.compare(arrivalTimeAtCurrAct, startAtPrevAct) == 0) {
// check if the current activity could start at the same time as the previous activity by subtracting the operation time / service time
// and compare this time to the lower bound of the time window.
double theoreticalArrivalTimeAtCurrActWithoutPrevOperatingTime = arrivalTimeAtCurrAct - prevAct.getOperationTime();
return theoreticalArrivalTimeAtCurrActWithoutPrevOperatingTime >= activity.getTheoreticalEarliestOperationStartTime();
}
return false;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package com.graphhopper.jsprit.examples;

import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm;
import com.graphhopper.jsprit.core.algorithm.box.Jsprit;
import com.graphhopper.jsprit.core.algorithm.state.StateManager;
import com.graphhopper.jsprit.core.algorithm.state.UpdateActivityTimes;
import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
import com.graphhopper.jsprit.core.problem.constraint.ConstraintManager;
import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts;
import com.graphhopper.jsprit.core.problem.job.Shipment;
import com.graphhopper.jsprit.core.problem.solution.SolutionCostCalculator;
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleType;
import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl;
import com.graphhopper.jsprit.core.reporting.SolutionPrinter;
import com.graphhopper.jsprit.core.util.ActivityTimeTracker;
import com.graphhopper.jsprit.core.util.Coordinate;
import com.graphhopper.jsprit.core.util.Solutions;
import com.graphhopper.jsprit.core.util.VehicleRoutingTransportCostsMatrix;
import com.graphhopper.jsprit.util.Examples;

import java.time.Instant;
import java.util.Collection;

public class DynamicServiceTimeExample {

public static void main(String[] args) {
/*
* some preparation - create output folder
*/
Examples.createOutputFolder();

//define a symmetric travel time matrix
VehicleRoutingTransportCostsMatrix.Builder costMatrixBuilder = VehicleRoutingTransportCostsMatrix.Builder.newInstance(true);
costMatrixBuilder.addTransportTime("vehicle:location", "shipment:pickup", 60D * 1);
costMatrixBuilder.addTransportTime("vehicle:location", "shipment:dropoff", 60D * 2);
costMatrixBuilder.addTransportTime("shipment:pickup", "shipment:dropoff", 60D * 1);
costMatrixBuilder.addTransportTime("vehicle:location", "vehicle:location", 0D);
costMatrixBuilder.addTransportTime("shipment:pickup", "shipment:pickup", 0D);
costMatrixBuilder.addTransportTime("shipment:dropoff", "shipment:dropoff", 0D);

costMatrixBuilder.addTransportTime("vehicle:location", "new:pickup", 60D * 1);
costMatrixBuilder.addTransportTime("vehicle:location", "new:dropoff", 60D * 2);
costMatrixBuilder.addTransportTime("new:pickup", "new:dropoff", 60D * 1);
costMatrixBuilder.addTransportTime("new:pickup", "new:pickup", 0D);
costMatrixBuilder.addTransportTime("new:dropoff", "new:dropoff", 0D);

costMatrixBuilder.addTransportTime("new:pickup", "shipment:dropoff", 60D * 1);
costMatrixBuilder.addTransportTime("new:dropoff", "shipment:pickup", 60D * 1);
costMatrixBuilder.addTransportTime("new:pickup", "shipment:pickup", 0D);
costMatrixBuilder.addTransportTime("new:dropoff", "shipment:dropoff", 0D);

VehicleRoutingTransportCosts costMatrix = costMatrixBuilder.build();

Instant vehicleStartTime = Instant.parse("2020-10-05T12:00:00Z");
Instant vehicleEndTime = Instant.parse("2020-10-05T14:00:00Z");
VehicleType type = VehicleTypeImpl.Builder.newInstance("type")
.addCapacityDimension(0, 7)
.setCostPerTransportTime(1)
.setCostPerDistance(0.0)
.setCostPerWaitingTime(0.0)
.setCostPerServiceTime(0.0)
.build();
VehicleImpl vehicle = VehicleImpl.Builder.newInstance("vehicle")
.setStartLocation(Location.Builder.newInstance().setId("vehicle:location").setCoordinate(Coordinate.newInstance(0, 0)).build())
.setEarliestStart(vehicleStartTime.getEpochSecond())
.setLatestArrival(vehicleEndTime.getEpochSecond())
.setReturnToDepot(false)
.setType(type)
.build();

Instant earliestPickupTime = Instant.parse("2020-10-05T12:00:00Z");
Instant latestPickupTime = Instant.parse("2020-10-05T12:05:00Z");
Instant earliestDeliveryTime = Instant.parse("2020-10-05T12:01:00Z");
Instant latestDeliveryTime = Instant.parse("2020-10-05T12:10:00Z");
Shipment shipment = Shipment.Builder.newInstance("shipment")
.addSizeDimension(0, 1)
.setPickupLocation(Location.Builder.newInstance().setId("shipment:pickup").setCoordinate(Coordinate.newInstance(2, 2)).build())
.setDeliveryLocation(Location.Builder.newInstance().setId("shipment:dropoff").setCoordinate(Coordinate.newInstance(4, 4)).build())
.addPickupTimeWindow(earliestPickupTime.getEpochSecond(), latestPickupTime.getEpochSecond())
.addDeliveryTimeWindow(earliestDeliveryTime.getEpochSecond(), latestDeliveryTime.getEpochSecond())
.setDeliveryServiceTime(120D)
.setPickupServiceTime(120D)
.build();

Instant newEarliestPickupTime = Instant.parse("2020-10-05T12:00:00Z");
Instant newLatestPickupTime = Instant.parse("2020-10-05T12:05:00Z");
Instant newEarliestDeliveryTime = Instant.parse("2020-10-05T12:01:00Z");
Instant newLatestDeliveryTime = Instant.parse("2020-10-05T12:10:00Z");
Shipment newShipment = Shipment.Builder.newInstance("new")
.addSizeDimension(0, 1)
.setPickupLocation(Location.Builder.newInstance().setId("new:pickup").setCoordinate(Coordinate.newInstance(2, 2)).build())
.setDeliveryLocation(Location.Builder.newInstance().setId("new:dropoff").setCoordinate(Coordinate.newInstance(4, 4)).build())
.addPickupTimeWindow(newEarliestPickupTime.getEpochSecond(), newLatestPickupTime.getEpochSecond())
.addDeliveryTimeWindow(newEarliestDeliveryTime.getEpochSecond(), newLatestDeliveryTime.getEpochSecond())
.setDeliveryServiceTime(60D)
.setPickupServiceTime(60D)
.build();

VehicleRoute vehicleRoute = VehicleRoute.Builder.newInstance(vehicle)
.addPickup(shipment)
.addDelivery(shipment)
.build();

VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance()
.setFleetSize(VehicleRoutingProblem.FleetSize.FINITE)
.setRoutingCost(costMatrix)
.addInitialVehicleRoute(vehicleRoute)
.addJob(newShipment)
.build();

SolutionCostCalculator objectiveFunction = new SolutionCostCalculator() {
@Override
public double getCosts(VehicleRoutingProblemSolution solution) {
double costs = 0;
for (VehicleRoute route : solution.getRoutes()) {
for (TourActivity activity : route.getActivities()) {
costs += vrp.getActivityCosts().getActivityCost(activity, activity.getArrTime(), route.getDriver(), route.getVehicle());
}
}
return costs;
}
};

StateManager stateManager = new StateManager(vrp);
stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(),ActivityTimeTracker.ActivityPolicy.AS_SOON_AS_TIME_WINDOW_OPENS_WITHIN_GROUP, vrp.getActivityCosts()));

ConstraintManager constraintManager = new ConstraintManager(vrp, stateManager);
constraintManager.addTimeWindowConstraint();
constraintManager.addLoadConstraint();
constraintManager.addSkillsConstraint();

VehicleRoutingAlgorithm vra = Jsprit.Builder.newInstance(vrp)
.setStateAndConstraintManager(stateManager, constraintManager)
.setObjectiveFunction(objectiveFunction)
.setProperty(Jsprit.Parameter.FAST_REGRET, "true")
.buildAlgorithm();

Collection<VehicleRoutingProblemSolution> solutions = vra.searchSolutions();

System.out.println("---------------------- Route Activity Times ----------------------");
Solutions.bestOf(solutions).getRoutes().stream().forEach(route -> {
route.getActivities().stream().forEach(activity -> {
System.out.println("Arrival time " + activity.getName() + " " + Instant.ofEpochSecond((long) activity.getArrTime()));
System.out.println("Departure time " + activity.getName() + " " + Instant.ofEpochSecond((long) activity.getEndTime()));
});
});
System.out.println("---------------------- Route Activity Times ----------------------");

SolutionPrinter.print(vrp, Solutions.bestOf(solutions), SolutionPrinter.Print.VERBOSE);
}
}