diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateActivityTimes.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateActivityTimes.java
index edf4c8dec..bfd01dfc1 100644
--- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateActivityTimes.java
+++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateActivityTimes.java
@@ -17,6 +17,7 @@
*/
package com.graphhopper.jsprit.core.algorithm.state;
+import com.graphhopper.jsprit.core.problem.Location;
import com.graphhopper.jsprit.core.problem.cost.ForwardTransportTime;
import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts;
import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
@@ -24,6 +25,9 @@
import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity;
import com.graphhopper.jsprit.core.util.ActivityTimeTracker;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* Updates arrival and end times of activities.
@@ -34,7 +38,7 @@
*/
public class UpdateActivityTimes implements ActivityVisitor, StateUpdater {
- private ActivityTimeTracker timeTracker;
+ private final ActivityTimeTracker timeTracker;
private VehicleRoute route;
@@ -48,8 +52,7 @@ public class UpdateActivityTimes implements ActivityVisitor, StateUpdater {
* activity.getEndTime()
*/
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) {
@@ -73,7 +76,69 @@ public void visit(TourActivity activity) {
@Override
public void finish() {
timeTracker.finish();
- route.getEnd().setArrTime(timeTracker.getActArrTime());
+ List activities = route.getActivities();
+ double totalSavedTime = 0;
+ for (int i = 0; i < activities.size(); i++) {
+ TourActivity current = activities.get(i);
+ double endTime = current.getEndTime();
+ double accumulatedOperatingTime = 0;
+ double savedTimeByGrouping = 0;
+ double maximumOperatingTime = current.getOperationTime();
+ double minimumOperatingTime = current.getOperationTime();
+ List groupedActivities = new ArrayList<>();
+ for (int j = i + 1; j < activities.size(); j++) {
+ TourActivity next = activities.get(j);
+ if (isSameLocation(current.getLocation(), next.getLocation()) && shouldOperateAtSameTime(next, endTime, accumulatedOperatingTime + next.getOperationTime())) {
+ accumulatedOperatingTime += next.getOperationTime();
+ maximumOperatingTime = Math.max(maximumOperatingTime, next.getOperationTime());
+ minimumOperatingTime = Math.min(minimumOperatingTime, next.getOperationTime());
+ savedTimeByGrouping += minimumOperatingTime;
+ groupedActivities.add(next);
+ i++;
+ } else {
+ break;
+ }
+ }
+ // if activities have been grouped before, adjust the arrival time for the vehicle by the previously accumulated saved time
+ current.setArrTime(current.getArrTime() - totalSavedTime);
+ current.setEndTime(endTime - current.getOperationTime() + maximumOperatingTime - totalSavedTime);
+ groupedActivities.forEach(activity -> {
+ activity.setArrTime(current.getArrTime());
+ activity.setEndTime(current.getEndTime());
+ });
+ // Adjust the saved time by comparing the savedTimePerGroup with the difference to the accumulated time.
+ // Three activities with the service time of 1 each
+ // - accumulatedOperatingTime = 2
+ // - savedTimeByGrouping = 2
+ // - total saved time = max(0, 2) = 2
+ // Three activities with the service time of 1, 2, 1 respectively:
+ // - accumulatedOperatingTime = 3
+ // - savedTimeByGrouping = 2
+ // - total saved time = max(1, 2) = 2
+ // Three activities with the service time of 1, 3, 2 respectively:
+ // - accumulatedOperatingTime = 5
+ // - savedTimeByGrouping = 2
+ // - total saved time = max(3, 2) = 3
+ totalSavedTime += Math.max(accumulatedOperatingTime - savedTimeByGrouping, savedTimeByGrouping);
+ }
+
+ route.getEnd().setArrTime(timeTracker.getActArrTime() - totalSavedTime);
+ }
+
+ private boolean isSameLocation(Location location, Location other) {
+ if (location.getCoordinate() != null && other.getCoordinate() != null) {
+ double maxDelta = 0.000001;
+ double diffLng = Math.abs(location.getCoordinate().getX() - other.getCoordinate().getX());
+ double diffLat = Math.abs(location.getCoordinate().getY() - other.getCoordinate().getY());
+ return diffLat < maxDelta && diffLng < maxDelta;
+ }
+ return location.equals(other);
+ }
+
+ private boolean shouldOperateAtSameTime(TourActivity next, double endTime, double accumulatedOperatingTime) {
+ boolean similarOperatingTime = Math.abs(next.getEndTime() - accumulatedOperatingTime - endTime) <= 0.001;
+ boolean laterThanTheoreticalStart = next.getEndTime() - accumulatedOperatingTime >= next.getTheoreticalEarliestOperationStartTime();
+ return similarOperatingTime && laterThanTheoreticalStart;
}
}
diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateActivityTimesTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateActivityTimesTest.java
new file mode 100644
index 000000000..b465ffb01
--- /dev/null
+++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateActivityTimesTest.java
@@ -0,0 +1,313 @@
+package com.graphhopper.jsprit.core.algorithm.state;
+
+import com.graphhopper.jsprit.core.problem.Location;
+import com.graphhopper.jsprit.core.problem.cost.ForwardTransportTime;
+import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts;
+import com.graphhopper.jsprit.core.problem.cost.WaitingTimeCosts;
+import com.graphhopper.jsprit.core.problem.job.Service;
+import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute;
+import com.graphhopper.jsprit.core.problem.solution.route.activity.*;
+import com.graphhopper.jsprit.core.util.Coordinate;
+import com.graphhopper.jsprit.core.util.CrowFlyCosts;
+import com.graphhopper.jsprit.core.util.Locations;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class UpdateActivityTimesTest{
+ ForwardTransportTime transportTime;
+ VehicleRoutingActivityCosts activityCosts;
+ VehicleRoute route;
+ List tourActivities;
+ Location startLocation;
+ Location endLocation;
+ Start start;
+ End end;
+ Map coordinates;
+ Locations locations;
+ TourActivityFactory tourActivityFactory;
+ UpdateActivityTimes stateUpdater;
+
+ @Before
+ public void setUp() throws Exception {
+ coordinates = new HashMap<>();
+ locations = new Locations() {
+ @Override
+ public Coordinate getCoord(String id) {
+ return coordinates.get(id);
+ }
+ };
+ Coordinate startCoordinate = Coordinate.newInstance(0, 0);
+ Coordinate endCoordinate = Coordinate.newInstance(10, 0);
+ coordinates.put("start", startCoordinate);
+ coordinates.put("end", endCoordinate);
+ startLocation = Location.Builder.newInstance().setId("start").setCoordinate(startCoordinate).build();
+ endLocation = Location.Builder.newInstance().setId("end").setCoordinate(endCoordinate).build();
+ start = Start.newInstance("start", 0, 0);
+ end = End.newInstance("end", 0, 30);
+ start.setLocation(startLocation);
+ end.setLocation(endLocation);
+ transportTime = new CrowFlyCosts(locations);
+ activityCosts = new WaitingTimeCosts();
+ stateUpdater = new UpdateActivityTimes(transportTime, activityCosts);
+ tourActivityFactory = new DefaultTourActivityFactory();
+ tourActivities = new ArrayList<>();
+ route = mock(VehicleRoute.class);
+ when(route.getStart()).thenReturn(start);
+ when(route.getEnd()).thenReturn(end);
+ when(route.getActivities()).thenReturn(tourActivities);
+ }
+
+ @Test
+ public void shouldNotAdjustActivityTimes_WhenActivitiesHappenAtDifferentLocations() {
+ Coordinate coordinateOne = Coordinate.newInstance(2, 0);
+ Coordinate coordinateTwo = Coordinate.newInstance(4, 0);
+ Coordinate coordinateThree = Coordinate.newInstance(6, 0);
+ Coordinate coordinateFour = Coordinate.newInstance(8, 0);
+ Location locationOne = Location.Builder.newInstance().setId("one").setCoordinate(coordinateOne).build();
+ Location locationTwo = Location.Builder.newInstance().setId("two").setCoordinate(coordinateTwo).build();
+ Location locationThree = Location.Builder.newInstance().setId("three").setCoordinate(coordinateThree).build();
+ Location locationFour = Location.Builder.newInstance().setId("four").setCoordinate(coordinateFour).build();
+ coordinates.put("one", coordinateOne);
+ coordinates.put("two", coordinateTwo);
+ coordinates.put("three", coordinateThree);
+ coordinates.put("four", coordinateFour);
+ TourActivity activityOne = tourActivityFactory.createActivity(Service.Builder.newInstance("one").setLocation(locationOne).setServiceTime(1D).build());
+ TourActivity activityTwo = tourActivityFactory.createActivity(Service.Builder.newInstance("two").setLocation(locationTwo).setServiceTime(1D).build());
+ TourActivity activityThree = tourActivityFactory.createActivity(Service.Builder.newInstance("three").setLocation(locationThree).setServiceTime(1D).build());
+ TourActivity activityFour = tourActivityFactory.createActivity(Service.Builder.newInstance("four").setLocation(locationFour).setServiceTime(1D).build());
+ tourActivities.addAll(Arrays.asList(activityOne, activityTwo, activityThree, activityFour));
+ stateUpdater.begin(route);
+ stateUpdater.visit(activityOne);
+ stateUpdater.visit(activityTwo);
+ stateUpdater.visit(activityThree);
+ stateUpdater.visit(activityFour);
+ stateUpdater.finish();
+
+ assertEquals(activityOne.getArrTime(), 2D, 0.001);
+ assertEquals(activityOne.getEndTime(), 3D, 0.001);
+ assertEquals(activityTwo.getArrTime(), 5D, 0.001);
+ assertEquals(activityTwo.getEndTime(), 6D, 0.001);
+ assertEquals(activityThree.getArrTime(), 8D, 0.001);
+ assertEquals(activityThree.getEndTime(), 9D, 0.001);
+ assertEquals(activityFour.getArrTime(), 11D, 0.001);
+ assertEquals(activityFour.getEndTime(), 12D, 0.001);
+ assertEquals(end.getArrTime(), 14D, 0.001);
+ assertEquals(end.getEndTime(), 30D, 0.001);
+ }
+
+ @Test
+ public void shouldNotAdjustActivityTimes_WhenActivitiesHappenAtDifferentTimes() {
+ Coordinate coordinateOne = Coordinate.newInstance(2, 0);
+ Coordinate coordinateTwo = Coordinate.newInstance(2, 0);
+ Coordinate coordinateThree = Coordinate.newInstance(2, 0);
+ Coordinate coordinateFour = Coordinate.newInstance(2, 0);
+ Location locationOne = Location.Builder.newInstance().setId("one").setCoordinate(coordinateOne).build();
+ Location locationTwo = Location.Builder.newInstance().setId("two").setCoordinate(coordinateTwo).build();
+ Location locationThree = Location.Builder.newInstance().setId("three").setCoordinate(coordinateThree).build();
+ Location locationFour = Location.Builder.newInstance().setId("four").setCoordinate(coordinateFour).build();
+ coordinates.put("one", coordinateOne);
+ coordinates.put("two", coordinateTwo);
+ coordinates.put("three", coordinateThree);
+ coordinates.put("four", coordinateFour);
+ TourActivity activityOne = tourActivityFactory.createActivity(Service.Builder.newInstance("one").setLocation(locationOne).setServiceTime(1D).build());
+ TourActivity activityTwo = tourActivityFactory.createActivity(Service.Builder.newInstance("two").setLocation(locationTwo).setServiceTime(1D).build());
+ TourActivity activityThree = tourActivityFactory.createActivity(Service.Builder.newInstance("three").setLocation(locationThree).setServiceTime(1D).build());
+ TourActivity activityFour = tourActivityFactory.createActivity(Service.Builder.newInstance("four").setLocation(locationFour).setServiceTime(1D).build());
+ activityOne.setTheoreticalEarliestOperationStartTime(0);
+ activityTwo.setTheoreticalEarliestOperationStartTime(6);
+ activityThree.setTheoreticalEarliestOperationStartTime(10);
+ activityFour.setTheoreticalEarliestOperationStartTime(15);
+ tourActivities.addAll(Arrays.asList(activityOne, activityTwo, activityThree, activityFour));
+ stateUpdater.begin(route);
+ stateUpdater.visit(activityOne);
+ stateUpdater.visit(activityTwo);
+ stateUpdater.visit(activityThree);
+ stateUpdater.visit(activityFour);
+ stateUpdater.finish();
+
+ assertEquals(activityOne.getArrTime(), 2D, 0.001);
+ assertEquals(activityOne.getEndTime(), 3D, 0.001);
+ assertEquals(activityTwo.getArrTime(), 3D, 0.001);
+ assertEquals(activityTwo.getEndTime(), 7D, 0.001);
+ assertEquals(activityThree.getArrTime(), 7D, 0.001);
+ assertEquals(activityThree.getEndTime(), 11D, 0.001);
+ assertEquals(activityFour.getArrTime(), 11D, 0.001);
+ assertEquals(activityFour.getEndTime(), 16D, 0.001);
+ assertEquals(end.getArrTime(), 24D, 0.001);
+ assertEquals(end.getEndTime(), 30D, 0.001);
+ }
+
+ @Test
+ public void shouldAdjustActivityTimes_WhenAllActivitiesHappenAtSameLocationAndTime() {
+ Coordinate coordinateOne = Coordinate.newInstance(2, 0);
+ Coordinate coordinateTwo = Coordinate.newInstance(2, 0);
+ Coordinate coordinateThree = Coordinate.newInstance(2, 0);
+ Coordinate coordinateFour = Coordinate.newInstance(2, 0);
+ Location locationOne = Location.Builder.newInstance().setId("one").setCoordinate(coordinateOne).build();
+ Location locationTwo = Location.Builder.newInstance().setId("two").setCoordinate(coordinateTwo).build();
+ Location locationThree = Location.Builder.newInstance().setId("three").setCoordinate(coordinateThree).build();
+ Location locationFour = Location.Builder.newInstance().setId("four").setCoordinate(coordinateFour).build();
+ coordinates.put("one", coordinateOne);
+ coordinates.put("two", coordinateTwo);
+ coordinates.put("three", coordinateThree);
+ coordinates.put("four", coordinateFour);
+ TourActivity activityOne = tourActivityFactory.createActivity(Service.Builder.newInstance("one").setLocation(locationOne).setServiceTime(1D).build());
+ TourActivity activityTwo = tourActivityFactory.createActivity(Service.Builder.newInstance("two").setLocation(locationTwo).setServiceTime(1D).build());
+ TourActivity activityThree = tourActivityFactory.createActivity(Service.Builder.newInstance("three").setLocation(locationThree).setServiceTime(1D).build());
+ TourActivity activityFour = tourActivityFactory.createActivity(Service.Builder.newInstance("four").setLocation(locationFour).setServiceTime(1D).build());
+ activityOne.setTheoreticalEarliestOperationStartTime(0);
+ activityTwo.setTheoreticalEarliestOperationStartTime(0);
+ activityThree.setTheoreticalEarliestOperationStartTime(0);
+ activityFour.setTheoreticalEarliestOperationStartTime(0);
+ tourActivities.addAll(Arrays.asList(activityOne, activityTwo, activityThree, activityFour));
+ stateUpdater.begin(route);
+ stateUpdater.visit(activityOne);
+ stateUpdater.visit(activityTwo);
+ stateUpdater.visit(activityThree);
+ stateUpdater.visit(activityFour);
+ stateUpdater.finish();
+
+ assertEquals(activityOne.getArrTime(), 2D, 0.001);
+ assertEquals(activityOne.getEndTime(), 3D, 0.001);
+ assertEquals(activityTwo.getArrTime(), 2D, 0.001);
+ assertEquals(activityTwo.getEndTime(), 3D, 0.001);
+ assertEquals(activityThree.getArrTime(), 2D, 0.001);
+ assertEquals(activityThree.getEndTime(), 3D, 0.001);
+ assertEquals(activityFour.getArrTime(), 2D, 0.001);
+ assertEquals(activityFour.getEndTime(), 3D, 0.001);
+ assertEquals(end.getArrTime(), 11D, 0.001);
+ assertEquals(end.getEndTime(), 30D, 0.001);
+ }
+
+ @Test
+ public void shouldAdjustActivityTimes_WhenSomeActivitiesHappenAtSameLocationAndTime() {
+ Coordinate coordinateOne = Coordinate.newInstance(2, 0);
+ Coordinate coordinateTwo = Coordinate.newInstance(2, 0);
+ Coordinate coordinateThree = Coordinate.newInstance(4, 0);
+ Coordinate coordinateFour = Coordinate.newInstance(4, 0);
+ Location locationOne = Location.Builder.newInstance().setId("one").setCoordinate(coordinateOne).build();
+ Location locationTwo = Location.Builder.newInstance().setId("two").setCoordinate(coordinateTwo).build();
+ Location locationThree = Location.Builder.newInstance().setId("three").setCoordinate(coordinateThree).build();
+ Location locationFour = Location.Builder.newInstance().setId("four").setCoordinate(coordinateFour).build();
+ coordinates.put("one", coordinateOne);
+ coordinates.put("two", coordinateTwo);
+ coordinates.put("three", coordinateThree);
+ coordinates.put("four", coordinateFour);
+ TourActivity activityOne = tourActivityFactory.createActivity(Service.Builder.newInstance("one").setLocation(locationOne).setServiceTime(1D).build());
+ TourActivity activityTwo = tourActivityFactory.createActivity(Service.Builder.newInstance("two").setLocation(locationTwo).setServiceTime(1D).build());
+ TourActivity activityThree = tourActivityFactory.createActivity(Service.Builder.newInstance("three").setLocation(locationThree).setServiceTime(1D).build());
+ TourActivity activityFour = tourActivityFactory.createActivity(Service.Builder.newInstance("four").setLocation(locationFour).setServiceTime(1D).build());
+ activityOne.setTheoreticalEarliestOperationStartTime(0);
+ activityTwo.setTheoreticalEarliestOperationStartTime(0);
+ activityThree.setTheoreticalEarliestOperationStartTime(0);
+ activityFour.setTheoreticalEarliestOperationStartTime(0);
+ tourActivities.addAll(Arrays.asList(activityOne, activityTwo, activityThree, activityFour));
+ stateUpdater.begin(route);
+ stateUpdater.visit(activityOne);
+ stateUpdater.visit(activityTwo);
+ stateUpdater.visit(activityThree);
+ stateUpdater.visit(activityFour);
+ stateUpdater.finish();
+
+ assertEquals(activityOne.getArrTime(), 2D, 0.001);
+ assertEquals(activityOne.getEndTime(), 3D, 0.001);
+ assertEquals(activityTwo.getArrTime(), 2D, 0.001);
+ assertEquals(activityTwo.getEndTime(), 3D, 0.001);
+ assertEquals(activityThree.getArrTime(), 5D, 0.001);
+ assertEquals(activityThree.getEndTime(), 6D, 0.001);
+ assertEquals(activityFour.getArrTime(), 5D, 0.001);
+ assertEquals(activityFour.getEndTime(), 6D, 0.001);
+ assertEquals(end.getArrTime(), 12D, 0.001);
+ assertEquals(end.getEndTime(), 30D, 0.001);
+ }
+
+ @Test
+ public void shouldAdjustActivityTimes_WhenAllActivitiesHappenAtSameLocationAndTimeAndDifferentServiceTimes() {
+ Coordinate coordinateOne = Coordinate.newInstance(2, 0);
+ Coordinate coordinateTwo = Coordinate.newInstance(2, 0);
+ Coordinate coordinateThree = Coordinate.newInstance(2, 0);
+ Coordinate coordinateFour = Coordinate.newInstance(2, 0);
+ Location locationOne = Location.Builder.newInstance().setId("one").setCoordinate(coordinateOne).build();
+ Location locationTwo = Location.Builder.newInstance().setId("two").setCoordinate(coordinateTwo).build();
+ Location locationThree = Location.Builder.newInstance().setId("three").setCoordinate(coordinateThree).build();
+ Location locationFour = Location.Builder.newInstance().setId("four").setCoordinate(coordinateFour).build();
+ coordinates.put("one", coordinateOne);
+ coordinates.put("two", coordinateTwo);
+ coordinates.put("three", coordinateThree);
+ coordinates.put("four", coordinateFour);
+ TourActivity activityOne = tourActivityFactory.createActivity(Service.Builder.newInstance("one").setLocation(locationOne).setServiceTime(1D).build());
+ TourActivity activityTwo = tourActivityFactory.createActivity(Service.Builder.newInstance("two").setLocation(locationTwo).setServiceTime(4D).build());
+ TourActivity activityThree = tourActivityFactory.createActivity(Service.Builder.newInstance("three").setLocation(locationThree).setServiceTime(2D).build());
+ TourActivity activityFour = tourActivityFactory.createActivity(Service.Builder.newInstance("four").setLocation(locationFour).setServiceTime(3D).build());
+ activityOne.setTheoreticalEarliestOperationStartTime(0);
+ activityTwo.setTheoreticalEarliestOperationStartTime(0);
+ activityThree.setTheoreticalEarliestOperationStartTime(0);
+ activityFour.setTheoreticalEarliestOperationStartTime(0);
+ tourActivities.addAll(Arrays.asList(activityOne, activityTwo, activityThree, activityFour));
+ stateUpdater.begin(route);
+ stateUpdater.visit(activityOne);
+ stateUpdater.visit(activityTwo);
+ stateUpdater.visit(activityThree);
+ stateUpdater.visit(activityFour);
+ stateUpdater.finish();
+
+ assertEquals(activityOne.getArrTime(), 2D, 0.001);
+ assertEquals(activityOne.getEndTime(), 6D, 0.001);
+ assertEquals(activityTwo.getArrTime(), 2D, 0.001);
+ assertEquals(activityTwo.getEndTime(), 6D, 0.001);
+ assertEquals(activityThree.getArrTime(), 2D, 0.001);
+ assertEquals(activityThree.getEndTime(), 6D, 0.001);
+ assertEquals(activityFour.getArrTime(), 2D, 0.001);
+ assertEquals(activityFour.getEndTime(), 6D, 0.001);
+ assertEquals(end.getArrTime(), 14D, 0.001);
+ assertEquals(end.getEndTime(), 30D, 0.001);
+ }
+
+ @Test
+ public void shouldAdjustActivityTimes_WhenAllActivitiesHappenAtSameLocationAndTimeAndOneDifferentServiceTime() {
+ Coordinate coordinateOne = Coordinate.newInstance(2, 0);
+ Coordinate coordinateTwo = Coordinate.newInstance(2, 0);
+ Coordinate coordinateThree = Coordinate.newInstance(2, 0);
+ Coordinate coordinateFour = Coordinate.newInstance(2, 0);
+ Location locationOne = Location.Builder.newInstance().setId("one").setCoordinate(coordinateOne).build();
+ Location locationTwo = Location.Builder.newInstance().setId("two").setCoordinate(coordinateTwo).build();
+ Location locationThree = Location.Builder.newInstance().setId("three").setCoordinate(coordinateThree).build();
+ Location locationFour = Location.Builder.newInstance().setId("four").setCoordinate(coordinateFour).build();
+ coordinates.put("one", coordinateOne);
+ coordinates.put("two", coordinateTwo);
+ coordinates.put("three", coordinateThree);
+ coordinates.put("four", coordinateFour);
+ TourActivity activityOne = tourActivityFactory.createActivity(Service.Builder.newInstance("one").setLocation(locationOne).setServiceTime(1D).build());
+ TourActivity activityTwo = tourActivityFactory.createActivity(Service.Builder.newInstance("two").setLocation(locationTwo).setServiceTime(4D).build());
+ TourActivity activityThree = tourActivityFactory.createActivity(Service.Builder.newInstance("three").setLocation(locationThree).setServiceTime(1D).build());
+ TourActivity activityFour = tourActivityFactory.createActivity(Service.Builder.newInstance("four").setLocation(locationFour).setServiceTime(1D).build());
+ activityOne.setTheoreticalEarliestOperationStartTime(0);
+ activityTwo.setTheoreticalEarliestOperationStartTime(0);
+ activityThree.setTheoreticalEarliestOperationStartTime(0);
+ activityFour.setTheoreticalEarliestOperationStartTime(0);
+ tourActivities.addAll(Arrays.asList(activityOne, activityTwo, activityThree, activityFour));
+ stateUpdater.begin(route);
+ stateUpdater.visit(activityOne);
+ stateUpdater.visit(activityTwo);
+ stateUpdater.visit(activityThree);
+ stateUpdater.visit(activityFour);
+ stateUpdater.finish();
+
+ assertEquals(activityOne.getArrTime(), 2D, 0.001);
+ assertEquals(activityOne.getEndTime(), 6D, 0.001);
+ assertEquals(activityTwo.getArrTime(), 2D, 0.001);
+ assertEquals(activityTwo.getEndTime(), 6D, 0.001);
+ assertEquals(activityThree.getArrTime(), 2D, 0.001);
+ assertEquals(activityThree.getEndTime(), 6D, 0.001);
+ assertEquals(activityFour.getArrTime(), 2D, 0.001);
+ assertEquals(activityFour.getEndTime(), 6D, 0.001);
+ assertEquals(end.getArrTime(), 14D, 0.001);
+ assertEquals(end.getEndTime(), 30D, 0.001);
+ }
+}
diff --git a/jsprit-examples/src/main/java/com/graphhopper/jsprit/examples/DynamicServiceTimeExample.java b/jsprit-examples/src/main/java/com/graphhopper/jsprit/examples/DynamicServiceTimeExample.java
new file mode 100644
index 000000000..849db5118
--- /dev/null
+++ b/jsprit-examples/src/main/java/com/graphhopper/jsprit/examples/DynamicServiceTimeExample.java
@@ -0,0 +1,144 @@
+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.problem.Location;
+import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem;
+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.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;
+ }
+ };
+
+ VehicleRoutingAlgorithm vra = Jsprit.Builder.newInstance(vrp)
+ .addCoreStateAndConstraintStuff(true)
+ .setObjectiveFunction(objectiveFunction)
+ .setProperty(Jsprit.Parameter.FAST_REGRET, "true")
+ .buildAlgorithm();
+
+ Collection 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);
+ }
+}