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); + } +}