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 488fea1b..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 @@ -31,10 +31,7 @@ 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.BreakActivity; -import com.graphhopper.jsprit.core.problem.solution.route.activity.DeliveryActivity; -import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupService; -import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; +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; @@ -353,13 +350,13 @@ public class VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest { * |--- nextAct ---| */ + Random random = new Random(); @Test - public void testSquash () { - Random random = new Random(); + public void testSquashNotEnd () { final double fixedCostAtSameLocation = random.nextDouble(), travelTime = random.nextDouble(), - firstActDuration = 5, nextActivitiesDuration = 4.0, - vehicleLatestArrival = firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 3; + 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()); @@ -372,24 +369,9 @@ public class VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest { RouteAndActivityStateGetter routeAndActivityStateGetter = mock(RouteAndActivityStateGetter.class); - TourActivity prevAct = mock(DeliveryActivity.class), - newAct = mock(DeliveryActivity.class), - nextAct = mock(DeliveryActivity.class); - - when(prevAct.getTheoreticalEarliestOperationStartTime()).thenReturn(.0); - when(prevAct.getTheoreticalLatestOperationStartTime()).thenReturn(firstActDuration); - when(prevAct.getOperationTime()).thenReturn(firstActDuration); - when(prevAct.getLocation()).thenReturn(locationFirst); - - when(newAct.getTheoreticalEarliestOperationStartTime()).thenReturn(firstActDuration); - when(newAct.getTheoreticalLatestOperationStartTime()).thenReturn(firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 2); - when(newAct.getOperationTime()).thenReturn(nextActivitiesDuration); - when(newAct.getLocation()).thenReturn(locationSecond); - - when(nextAct.getTheoreticalEarliestOperationStartTime()).thenReturn(firstActDuration); - when(nextAct.getTheoreticalLatestOperationStartTime()).thenReturn(firstActDuration + nextActivitiesDuration + fixedCostAtSameLocation + travelTime * 2); - when(nextAct.getOperationTime()).thenReturn(nextActivitiesDuration); - when(nextAct.getLocation()).thenReturn(locationSecond); + 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); @@ -398,6 +380,95 @@ public class VehicleDependentTimeWindowWithStartTimeAndMaxOperationTimeTest { 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