diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicle.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicle.java index 3496f3a1..9ae37d36 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicle.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicle.java @@ -57,6 +57,8 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ private final VehicleRoutingActivityCosts activityCosts; + private TourActivity prevTourActivity = null; + private UpdateVehicleDependentPracticalTimeWindows.VehiclesToUpdate vehiclesToUpdate = new UpdateVehicleDependentPracticalTimeWindows.VehiclesToUpdate() { @Override @@ -98,6 +100,7 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ prevActEndTimes[vehicleIndex] = v.getEarliestDeparture(); prevActLocations[vehicleIndex] = v.getStartLocation(); } + prevTourActivity = route.getStart(); } @Override @@ -111,7 +114,7 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ double activityArrival = prevActEndTimes[v.getVehicleTypeIdentifier().getIndex()] + transportTime.getTransportTime(prevActLocation,activity.getLocation(),prevActEndTime,route.getDriver(),v); double activityStart = Math.max(activityArrival,activity.getTheoreticalEarliestOperationStartTime()); memorizeActStart(activity,v,activityStart); - double activityEnd = activityStart + activityCosts.getActivityDuration(null, activity, activityArrival, route.getDriver(), v); + double activityEnd = activityStart + activityCosts.getActivityDuration(prevTourActivity, activity, activityArrival, route.getDriver(), v); Map openPickups = openPickupEndTimesPerVehicle.get(vehicleIndex); if (activity instanceof ServiceActivity || activity instanceof PickupActivity) { openPickups.put(((TourActivity.JobActivity) activity).getJob(), activityEnd); @@ -129,6 +132,8 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ prevActEndTimes[vehicleIndex] = activityEnd; } + prevTourActivity = activity; + } private double getMaxTimeInVehicle(TourActivity activity) { diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentPracticalTimeWindows.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentPracticalTimeWindows.java index e361467c..661d8282 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentPracticalTimeWindows.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentPracticalTimeWindows.java @@ -26,19 +26,23 @@ 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.Vehicle; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Iterator; +import java.util.List; public class UpdateVehicleDependentPracticalTimeWindows implements RouteVisitor, StateUpdater { @Override public void visit(VehicleRoute route) { begin(route); - Iterator revIterator = route.getTourActivities().reverseActivityIterator(); - while (revIterator.hasNext()) { - visit(revIterator.next()); - } + List activities = new ArrayList<>(); + activities.add(route.getStart()); + activities.addAll(route.getTourActivities().getActivities()); + + for (int i = activities.size() - 1; i > 0; --i) + visit(activities.get(i), activities.get(i - 1)); + finish(); } @@ -99,12 +103,12 @@ public class UpdateVehicleDependentPracticalTimeWindows implements RouteVisitor, } - public void visit(TourActivity activity) { + public void visit(TourActivity activity, TourActivity prev) { for (Vehicle vehicle : vehicles) { double latestArrTimeAtPrevAct = latest_arrTimes_at_prevAct[vehicle.getVehicleTypeIdentifier().getIndex()]; Location prevLocation = location_of_prevAct[vehicle.getVehicleTypeIdentifier().getIndex()]; double potentialLatestArrivalTimeAtCurrAct = latestArrTimeAtPrevAct - transportCosts.getBackwardTransportTime(activity.getLocation(), prevLocation, - latestArrTimeAtPrevAct, route.getDriver(), vehicle) - activityCosts.getActivityDuration(null, activity, latestArrTimeAtPrevAct, route.getDriver(), route.getVehicle()); + latestArrTimeAtPrevAct, route.getDriver(), vehicle) - activityCosts.getActivityDuration(prev, activity, latestArrTimeAtPrevAct, route.getDriver(), route.getVehicle()); double latestArrivalTime = Math.min(activity.getTheoreticalLatestOperationStartTime(), potentialLatestArrivalTimeAtCurrAct); if (latestArrivalTime < activity.getTheoreticalEarliestOperationStartTime()) { stateManager.putTypedInternalRouteState(route, vehicle, InternalStates.SWITCH_NOT_FEASIBLE, true); diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowConstraints.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowConstraints.java index 5d0d1ffb..ba1cebde 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowConstraints.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowConstraints.java @@ -108,12 +108,17 @@ public class VehicleDependentTimeWindowConstraints implements HardActivityConstr return ConstraintsStatus.NOT_FULFILLED; } // log.info("check insertion of " + newAct + " between " + prevAct + " and " + nextAct + ". prevActDepTime=" + prevActDepTime); + double routingFromNewToNext = routingCosts.getBackwardTransportTime(newAct.getLocation(), nextActLocation, latestArrTimeAtNextAct, iFacts.getNewDriver(), iFacts.getNewVehicle()); double arrTimeAtNewAct = prevActDepTime + routingCosts.getTransportTime(prevAct.getLocation(), newAct.getLocation(), prevActDepTime, iFacts.getNewDriver(), iFacts.getNewVehicle()); double endTimeAtNewAct = Math.max(arrTimeAtNewAct, newAct.getTheoreticalEarliestOperationStartTime()) + activityCosts.getActivityDuration(prevAct, newAct, arrTimeAtNewAct,iFacts.getNewDriver(),iFacts.getNewVehicle()); + double savingsInNextActivityDuration = + activityCosts.getActivityDuration(prevAct, nextAct, arrTimeAtNextOnDirectRouteWithNewVehicle, iFacts.getNewDriver(), iFacts.getNewVehicle()) - + activityCosts.getActivityDuration(newAct, nextAct, endTimeAtNewAct + routingFromNewToNext, iFacts.getNewDriver(), iFacts.getNewVehicle()); + + latestArrTimeAtNextAct = Math.min(nextAct.getTheoreticalLatestOperationStartTime(), latestArrTimeAtNextAct + savingsInNextActivityDuration); double latestArrTimeAtNewAct = Math.min(newAct.getTheoreticalLatestOperationStartTime(), - latestArrTimeAtNextAct - - routingCosts.getBackwardTransportTime(newAct.getLocation(), nextActLocation, latestArrTimeAtNextAct, iFacts.getNewDriver(), iFacts.getNewVehicle()) + latestArrTimeAtNextAct - routingFromNewToNext - activityCosts.getActivityDuration(prevAct, newAct, arrTimeAtNewAct, iFacts.getNewDriver(), iFacts.getNewVehicle()) ); diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentTimeWindowTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentTimeWindowTest.java index a07d61e5..096ada4f 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentTimeWindowTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentTimeWindowTest.java @@ -25,14 +25,15 @@ import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; import com.graphhopper.jsprit.core.problem.cost.WaitingTimeCosts; +import com.graphhopper.jsprit.core.problem.driver.Driver; +import com.graphhopper.jsprit.core.problem.job.Delivery; import com.graphhopper.jsprit.core.problem.job.Job; +import com.graphhopper.jsprit.core.problem.job.Pickup; 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.TimeWindow; -import com.graphhopper.jsprit.core.problem.vehicle.FiniteFleetManagerFactory; -import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; -import com.graphhopper.jsprit.core.problem.vehicle.VehicleFleetManager; -import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +import com.graphhopper.jsprit.core.problem.vehicle.*; import com.graphhopper.jsprit.core.util.Coordinate; import com.graphhopper.jsprit.core.util.CostFactory; import org.junit.Assert; @@ -42,6 +43,7 @@ import org.junit.Test; import java.util.*; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; /** * unit tests to test vehicle dependent time window updater @@ -268,5 +270,86 @@ public class UpdateVehicleDependentTimeWindowTest { } + Random random = new Random(); + + @Test + public void testSquash () { + Location pickupLocation = Location.newInstance(random.nextDouble(), random.nextDouble()); + final double fixedDurationInSameLocation = random.nextDouble(), activityDuration = 10 + random.nextDouble(), travelTime = random.nextDouble(); + + Pickup pickup = Pickup.Builder.newInstance("pick1").setLocation(pickupLocation).setTimeWindow(TimeWindow.newInstance(0, 40)).setServiceTime(activityDuration).build(); + Pickup pickup2 = Pickup.Builder.newInstance("pick2").setLocation(pickupLocation).setTimeWindow(TimeWindow.newInstance(0, 40)).setServiceTime(activityDuration).build(); + + Delivery delivery = Delivery.Builder.newInstance("del1").setLocation(Location.newInstance(random.nextDouble(), random.nextDouble())).setTimeWindow(TimeWindow.newInstance(0, 40)).setServiceTime(activityDuration).build(); + Delivery delivery2 = Delivery.Builder.newInstance("del2").setLocation(Location.newInstance(random.nextDouble(), random.nextDouble())).setTimeWindow(TimeWindow.newInstance(0, 40)).setServiceTime(activityDuration).build(); + + Vehicle vehicle = VehicleImpl.Builder.newInstance("v").setStartLocation(Location.newInstance(random.nextDouble(), random.nextDouble())).setType(VehicleTypeImpl.Builder.newInstance("type").build()).build(); + + VehicleRoutingProblem.Builder vrpBuilder = VehicleRoutingProblem.Builder.newInstance(); + final VehicleRoutingProblem vrp = vrpBuilder.addJob(pickup).addJob(pickup2).addJob(delivery).addJob(delivery2).addVehicle(vehicle).setFleetSize(VehicleRoutingProblem.FleetSize.FINITE).build(); + + route = VehicleRoute.Builder.newInstance(vehicle, mock(Driver.class)).setJobActivityFactory(new JobActivityFactory() { + @Override + public List createActivities(Job job) { + return vrp.copyAndGetActivities(job); + } + }).addService(pickup).addService(pickup2).addService(delivery).addService(delivery2).build(); + + + + VehicleRoutingTransportCosts transportCosts = getTransportCosts(travelTime); + + activityCosts = new WaitingTimeCosts() { + @Override + public double getActivityDuration(TourActivity prevAct, TourActivity tourAct, double arrivalTime, Driver driver, Vehicle vehicle) { + return prevAct != null && prevAct.getLocation().getCoordinate().equals(tourAct.getLocation().getCoordinate()) ? fixedDurationInSameLocation : tourAct.getOperationTime(); + } + }; + + stateManager = new StateManager(vrp); + + final UpdateVehicleDependentPracticalTimeWindows timeWindowsUpdater = new UpdateVehicleDependentPracticalTimeWindows(stateManager, transportCosts, activityCosts); + timeWindowsUpdater.visit(route); + + double latestArrivalAt4Act = 40, + latestArrivalAt3Act = latestArrivalAt4Act - activityDuration - travelTime, + latestArrivalAt2Act = latestArrivalAt3Act - fixedDurationInSameLocation - travelTime, + latestArrivalAt1Act = latestArrivalAt2Act - activityDuration; + + assertEquals(stateManager.getActivityState(route.getActivities().get(3), vehicle, InternalStates.LATEST_OPERATION_START_TIME, Double.class), latestArrivalAt4Act, 0.001); + assertEquals(stateManager.getActivityState(route.getActivities().get(2), vehicle, InternalStates.LATEST_OPERATION_START_TIME, Double.class), latestArrivalAt3Act, 0.001); + assertEquals(stateManager.getActivityState(route.getActivities().get(1), vehicle, InternalStates.LATEST_OPERATION_START_TIME, Double.class), latestArrivalAt2Act, 0.001); + assertEquals(stateManager.getActivityState(route.getActivities().get(0), vehicle, InternalStates.LATEST_OPERATION_START_TIME, Double.class), latestArrivalAt1Act, 0.001); + } + + public VehicleRoutingTransportCosts getTransportCosts(final double travelTime) { + return new VehicleRoutingTransportCosts() { + + @Override + public double getDistance(Location from, Location to, double departureTime, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getTransportTime(Location from, Location to, double departureTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getTransportCost(Location from, Location to, double departureTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getBackwardTransportTime(Location from, Location to, double arrivalTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getBackwardTransportCost(Location from, Location to, double arrivalTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + }; + } } diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest.java index 373b0edd..8e861715 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest.java @@ -26,11 +26,13 @@ import com.graphhopper.jsprit.core.problem.*; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; import com.graphhopper.jsprit.core.problem.cost.WaitingTimeCosts; +import com.graphhopper.jsprit.core.problem.driver.Driver; import com.graphhopper.jsprit.core.problem.job.Job; import com.graphhopper.jsprit.core.problem.job.Service; import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; -import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupService; +import com.graphhopper.jsprit.core.problem.solution.route.activity.*; +import com.graphhopper.jsprit.core.problem.solution.route.state.RouteAndActivityStateGetter; import com.graphhopper.jsprit.core.problem.vehicle.*; import com.graphhopper.jsprit.core.util.CostFactory; import org.junit.Before; @@ -39,6 +41,8 @@ import org.junit.Test; import java.util.*; import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; /** * unit tests to test vehicle dependent time-windows @@ -338,5 +342,178 @@ public class VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest { } + /* + * driver TW: 5 - 10 + * prev task 0 - 5 + * new and next acts at same location, tw 5-10, activity duration 4 + * |--- newAct ---| + * |--- nextAct ---| + */ + Random random = new Random(); + @Test + public void testSquashNotEnd () { + final double fixedCostAtSameLocation = random.nextDouble(), + travelTime = random.nextDouble(), + firstActDuration = 5, nextActivitiesDuration = 4.0, + vehicleLatestArrival = firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 3; + + Location locationFirst = Location.newInstance(random.nextDouble(), random.nextDouble()); + Location locationSecond = Location.newInstance(random.nextDouble(), random.nextDouble()); + + Vehicle vehicle = mock(Vehicle.class); + when(vehicle.getLatestArrival()).thenReturn(vehicleLatestArrival); + + JobInsertionContext iFacts = mock(JobInsertionContext.class); + when(iFacts.getNewVehicle()).thenReturn(vehicle); + + RouteAndActivityStateGetter routeAndActivityStateGetter = mock(RouteAndActivityStateGetter.class); + + TourActivity prevAct = getTourActivity(.0, firstActDuration, firstActDuration, locationFirst); + TourActivity newAct = getTourActivity(firstActDuration, firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 2, nextActivitiesDuration, locationSecond); + TourActivity nextAct = getTourActivity(firstActDuration, firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 2, nextActivitiesDuration, locationSecond); + + when(routeAndActivityStateGetter.getActivityState(nextAct, vehicle, InternalStates.LATEST_OPERATION_START_TIME, Double.class)).thenReturn(vehicleLatestArrival - travelTime - nextActivitiesDuration); + + final VehicleDependentTimeWindowConstraints timeWindowConstraints = new VehicleDependentTimeWindowConstraints(routeAndActivityStateGetter, getTransportCosts(travelTime), getActivityCost(fixedCostAtSameLocation)); + + assertEquals(timeWindowConstraints.fulfilled(iFacts, prevAct, newAct, nextAct, 5.0), HardActivityConstraint.ConstraintsStatus.FULFILLED); + } + + @Test + public void testNoSquashEnd () { + final double fixedCostAtSameLocation = random.nextDouble(), + travelTime = random.nextDouble(), + firstActDuration = 5, nextActivitiesDuration = 4.0, + vehicleLatestArrival = firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 3; + + Location locationFirst = Location.newInstance(random.nextDouble(), random.nextDouble()); + Location locationSecond = Location.newInstance(random.nextDouble(), random.nextDouble()); + + Vehicle vehicle = mock(Vehicle.class); + when(vehicle.getLatestArrival()).thenReturn(vehicleLatestArrival); + when(vehicle.isReturnToDepot()).thenReturn(false); + + JobInsertionContext iFacts = mock(JobInsertionContext.class); + when(iFacts.getNewVehicle()).thenReturn(vehicle); + + TourActivity prevAct = getTourActivity(.0, firstActDuration, firstActDuration, locationFirst); + TourActivity newAct = getTourActivity(firstActDuration, firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 2, nextActivitiesDuration, locationSecond); + End end = new End(locationSecond, firstActDuration, firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 2); + + final VehicleDependentTimeWindowConstraints timeWindowConstraints = new VehicleDependentTimeWindowConstraints(mock(RouteAndActivityStateGetter.class), getTransportCosts(travelTime), getActivityCost(fixedCostAtSameLocation)); + + assertEquals(timeWindowConstraints.fulfilled(iFacts, prevAct, newAct, end, 5.0), HardActivityConstraint.ConstraintsStatus.FULFILLED); + } + + @Test + public void testSquashEndReturnToDepot () { + final double fixedCostAtSameLocation = random.nextDouble(), + travelTime = random.nextDouble(), + firstActDuration = 5.0, newActivityDuration = 4.0, + vehicleLatestArrival = firstActDuration + fixedCostAtSameLocation + travelTime; + + Location location = Location.newInstance(random.nextDouble(), random.nextDouble()); + Location depot = Location.newInstance(random.nextDouble(), random.nextDouble()); + + Vehicle vehicle = mock(Vehicle.class); + when(vehicle.getLatestArrival()).thenReturn(vehicleLatestArrival); + when(vehicle.isReturnToDepot()).thenReturn(true); + when(vehicle.getEndLocation()).thenReturn(depot); + + JobInsertionContext iFacts = mock(JobInsertionContext.class); + when(iFacts.getNewVehicle()).thenReturn(vehicle); + + TourActivity prevAct = getTourActivity(.0, firstActDuration, firstActDuration, location); + TourActivity newAct = getTourActivity(firstActDuration, firstActDuration + fixedCostAtSameLocation, newActivityDuration, location); + End end = new End(depot, firstActDuration, firstActDuration + fixedCostAtSameLocation + travelTime); + + final VehicleDependentTimeWindowConstraints timeWindowConstraints = new VehicleDependentTimeWindowConstraints(mock(RouteAndActivityStateGetter.class), getTransportCosts(travelTime), getActivityCost(fixedCostAtSameLocation)); + + assertEquals(timeWindowConstraints.fulfilled(iFacts, prevAct, newAct, end, 5.0), HardActivityConstraint.ConstraintsStatus.FULFILLED); + } + + @Test + public void testSquashEndNoReturnToDepot () { + final double fixedCostAtSameLocation = random.nextDouble(), + travelTime = random.nextDouble(), + firstActDuration = 5.0, newActivityDuration = 4.0, + vehicleLatestArrival = firstActDuration + fixedCostAtSameLocation; + + Location location = Location.newInstance(random.nextDouble(), random.nextDouble()); + Location depot = Location.newInstance(random.nextDouble(), random.nextDouble()); + + Vehicle vehicle = mock(Vehicle.class); + when(vehicle.getLatestArrival()).thenReturn(vehicleLatestArrival); + when(vehicle.isReturnToDepot()).thenReturn(false); + when(vehicle.getEndLocation()).thenReturn(depot); + + JobInsertionContext iFacts = mock(JobInsertionContext.class); + when(iFacts.getNewVehicle()).thenReturn(vehicle); + + TourActivity prevAct = getTourActivity(.0, firstActDuration, firstActDuration, location); + TourActivity newAct = getTourActivity(firstActDuration, firstActDuration + fixedCostAtSameLocation, newActivityDuration, location); + End end = new End(depot, firstActDuration, firstActDuration + fixedCostAtSameLocation); + + final VehicleDependentTimeWindowConstraints timeWindowConstraints = new VehicleDependentTimeWindowConstraints(mock(RouteAndActivityStateGetter.class), getTransportCosts(travelTime), getActivityCost(fixedCostAtSameLocation)); + + assertEquals(timeWindowConstraints.fulfilled(iFacts, prevAct, newAct, end, 5.0), HardActivityConstraint.ConstraintsStatus.FULFILLED); + } + + private TourActivity getTourActivity(double start, double end, double activityDuration, Location location) { + TourActivity act = mock(DeliveryActivity.class); + when(act.getTheoreticalEarliestOperationStartTime()).thenReturn(start); + when(act.getTheoreticalLatestOperationStartTime()).thenReturn(end); + when(act.getOperationTime()).thenReturn(activityDuration); + when(act.getLocation()).thenReturn(location); + return act; + } + + private VehicleRoutingActivityCosts getActivityCost(final double fixedCostAtSameLocation) { + return new VehicleRoutingActivityCosts() { + @Override + public double getActivityCost(TourActivity prevAct, TourActivity tourAct, double arrivalTime, Driver driver, Vehicle vehicle) { + return getActivityDuration(prevAct, tourAct, arrivalTime, driver, vehicle); + } + + @Override + public double getActivityDuration(TourActivity from, TourActivity to, double startTime, Driver driver, Vehicle vehicle) { + if (from != null && !(to instanceof BreakActivity || from instanceof BreakActivity) && from.getLocation().getCoordinate().equals(to.getLocation().getCoordinate())) { + return fixedCostAtSameLocation; + } + + return to.getOperationTime(); + } + }; + } + + public VehicleRoutingTransportCosts getTransportCosts(final double travelTime) { + return new VehicleRoutingTransportCosts() { + + @Override + public double getDistance(Location from, Location to, double departureTime, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getTransportTime(Location from, Location to, double departureTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getTransportCost(Location from, Location to, double departureTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getBackwardTransportTime(Location from, Location to, double arrivalTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + + @Override + public double getBackwardTransportCost(Location from, Location to, double arrivalTime, Driver driver, Vehicle vehicle) { + return from.getCoordinate().equals(to.getCoordinate()) ? 0 : travelTime; + } + }; + } } diff --git a/jsprit-io/src/test/java/com/graphhopper/jsprit/io/algorithm/TestAlgorithmSquashActivityTimes.java b/jsprit-io/src/test/java/com/graphhopper/jsprit/io/algorithm/TestAlgorithmSquashActivityTimes.java new file mode 100644 index 00000000..faf295f9 --- /dev/null +++ b/jsprit-io/src/test/java/com/graphhopper/jsprit/io/algorithm/TestAlgorithmSquashActivityTimes.java @@ -0,0 +1,92 @@ +package com.graphhopper.jsprit.io.algorithm; + +import com.graphhopper.jsprit.core.algorithm.VehicleRoutingAlgorithm; +import com.graphhopper.jsprit.core.algorithm.state.StateManager; +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.constraint.HardActivityConstraint; +import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; +import com.graphhopper.jsprit.core.problem.driver.Driver; +import com.graphhopper.jsprit.core.problem.job.Delivery; +import com.graphhopper.jsprit.core.problem.job.Job; +import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; +import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution; +import com.graphhopper.jsprit.core.problem.solution.route.activity.BreakActivity; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; +import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; +import com.graphhopper.jsprit.core.problem.vehicle.VehicleImpl; +import com.graphhopper.jsprit.core.problem.vehicle.VehicleTypeImpl; +import com.graphhopper.jsprit.core.util.Solutions; +import org.junit.Test; + +import java.util.Random; +import java.util.UUID; + +import static org.junit.Assert.assertEquals; + +public class TestAlgorithmSquashActivityTimes { + + @Test + public void testSquash () { + VehicleRoutingActivityCosts activityCost = new VehicleRoutingActivityCosts() { + @Override + public double getActivityCost(TourActivity prevAct, TourActivity tourAct, double arrivalTime, Driver driver, Vehicle vehicle) { + return getActivityDuration(prevAct, tourAct, arrivalTime, driver, vehicle); + } + + @Override + public double getActivityDuration(TourActivity from, TourActivity to, double startTime, Driver driver, Vehicle vehicle) { + if (from != null && !(to instanceof BreakActivity || from instanceof BreakActivity) && from.getLocation().getCoordinate().equals(to.getLocation().getCoordinate())) { + return 1; + } + + return to.getOperationTime(); + } + }; + final VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance() + .addVehicle(VehicleImpl.Builder.newInstance("vehicle") + .setStartLocation(Location.newInstance(1.521801, 42.506285)) + .setEarliestStart(0) + .setLatestArrival(10).setType(VehicleTypeImpl.Builder.newInstance(UUID.randomUUID().toString()).addCapacityDimension(0, Integer.MAX_VALUE).build()) + .setReturnToDepot(false) + .build()) + .addJob(getJobs(0, 10, 1, 7)) + .addJob(getJobs(0, 10, 1, 7)) + .addJob(getJobs(0, 10, 1, 7)) + .addJob(getJobs(0, 10, 1, 6)) + .setActivityCosts(activityCost) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + StateManager stateManager = new StateManager(vrp); + + ConstraintManager constraintManager = new ConstraintManager (vrp, stateManager) { + @Override + public HardActivityConstraint.ConstraintsStatus fulfilled(JobInsertionContext iFacts, TourActivity prevAct, TourActivity newAct, TourActivity nextAct, double prevActDepTime){ + if (newAct instanceof BreakActivity && newAct.getLocation().equals(nextAct.getLocation())) + return ConstraintsStatus.NOT_FULFILLED; + + return super.fulfilled(iFacts, prevAct, newAct, nextAct, prevActDepTime); + } + }; + + final VehicleRoutingAlgorithm algorithm = VehicleRoutingAlgorithms.readAndCreateAlgorithm(vrp, Runtime.getRuntime().availableProcessors() + 1, getClass().getResource("algorithmConfig_withoutIterations.xml").getFile(), stateManager, constraintManager, null); + + final VehicleRoutingProblemSolution problemSolution = Solutions.bestOf(algorithm.searchSolutions()); + + assertEquals(problemSolution.getUnassignedJobs().size(), 0); + assertEquals(problemSolution.getRoutes().iterator().next().getTourActivities().getActivities().size(), 4); + } + + private Job getJobs(int start, int end, int capacity, int duration) { + return Delivery.Builder.newInstance("service_" + new Random().nextInt()) + .setLocation(Location.newInstance(1.52181, 42.5062)) + .setServiceTime(duration) + .addTimeWindow(new TimeWindow(start, end)) + .addSizeDimension(0, capacity) + .setName(UUID.randomUUID().toString()) + .build(); + } +}