diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/AlgorithmUtil.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/AlgorithmUtil.java index 0cdd76ce7..5e97e1c0f 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/AlgorithmUtil.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/AlgorithmUtil.java @@ -76,6 +76,9 @@ public Collection get(VehicleRoute vehicleRoute) { stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), ActivityTimeTracker.ActivityPolicy.AS_SOON_AS_TIME_WINDOW_OPENS, vrp.getActivityCosts())); stateManager.addStateUpdater(new UpdateVariableCosts(vrp.getActivityCosts(), vrp.getTransportCosts(), stateManager)); stateManager.addStateUpdater(new UpdateFutureWaitingTimes(stateManager, vrp.getTransportCosts())); + + stateManager.addStateUpdater(new UpdateVehicleDependentTimes(vrp.getTransportCosts(), vrp.getActivityCosts(), stateManager, vrp.getVehicles())); + stateManager.addStateUpdater(new UpdateVehicleDependentFutureWaitingTimes(stateManager, vrp.getVehicles())); } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java index 1135adb88..b78b57a99 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/JobInsertionCostsCalculatorBuilder.java @@ -19,6 +19,7 @@ import com.graphhopper.jsprit.core.algorithm.listener.VehicleRoutingAlgorithmListeners.PrioritizedVRAListener; import com.graphhopper.jsprit.core.algorithm.recreate.listener.InsertionListener; +import com.graphhopper.jsprit.core.algorithm.state.StateManager; import com.graphhopper.jsprit.core.problem.AbstractActivity; import com.graphhopper.jsprit.core.problem.JobActivityFactory; import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; @@ -289,12 +290,18 @@ public List createActivities(Job job) { }; ShipmentInsertionCalculator shipmentInsertion = new ShipmentInsertionCalculator(vrp.getTransportCosts(), vrp.getActivityCosts(),actInsertionCalc, constraintManager); shipmentInsertion.setJobActivityFactory(activityFactory); + shipmentInsertion.setStateManager((StateManager) statesManager); + ServiceInsertionCalculator serviceInsertion = new ServiceInsertionCalculator(vrp.getTransportCosts(), vrp.getActivityCosts(), actInsertionCalc, constraintManager); serviceInsertion.setJobActivityFactory(activityFactory); + serviceInsertion.setStateManager((StateManager) statesManager); BreakInsertionCalculator breakInsertionCalculator = new BreakInsertionCalculator(vrp.getTransportCosts(), vrp.getActivityCosts(), actInsertionCalc, constraintManager); breakInsertionCalculator.setJobActivityFactory(activityFactory); + shipmentInsertion.setBreakInsertionCalculator(breakInsertionCalculator); + serviceInsertion.setBreakInsertionCalculator(breakInsertionCalculator); + JobCalculatorSwitcher switcher = new JobCalculatorSwitcher(); switcher.put(Shipment.class, shipmentInsertion); switcher.put(Service.class, serviceInsertion); diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java index 9f91ff9bf..66b86aaca 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ServiceInsertionCalculator.java @@ -17,6 +17,8 @@ */ package com.graphhopper.jsprit.core.algorithm.recreate; +import com.graphhopper.jsprit.core.algorithm.state.InternalStates; +import com.graphhopper.jsprit.core.algorithm.state.StateManager; import com.graphhopper.jsprit.core.problem.JobActivityFactory; import com.graphhopper.jsprit.core.problem.constraint.ConstraintManager; import com.graphhopper.jsprit.core.problem.constraint.HardActivityConstraint.ConstraintsStatus; @@ -25,15 +27,11 @@ import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; 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.job.*; import com.graphhopper.jsprit.core.problem.misc.ActivityContext; 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.End; -import com.graphhopper.jsprit.core.problem.solution.route.activity.Start; -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.solution.route.activity.*; import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -71,6 +69,10 @@ final class ServiceInsertionCalculator extends AbstractInsertionCalculator { private ConstraintManager constraintManager; + private StateManager stateManager; + + private BreakInsertionCalculator breakInsertionCalculator; + public ServiceInsertionCalculator(VehicleRoutingTransportCosts routingCosts, VehicleRoutingActivityCosts activityCosts, ActivityInsertionCostsCalculator additionalTransportCostsCalculator, ConstraintManager constraintManager) { super(); this.transportCosts = routingCosts; @@ -87,6 +89,14 @@ public void setJobActivityFactory(JobActivityFactory jobActivityFactory) { this.activityFactory = jobActivityFactory; } + public void setStateManager(StateManager stateManager) { + this.stateManager = stateManager; + } + + public void setBreakInsertionCalculator(BreakInsertionCalculator breakInsertionCalculator) { + this.breakInsertionCalculator = breakInsertionCalculator; + } + @Override public String toString() { return "[name=calculatesServiceInsertion]"; @@ -149,6 +159,58 @@ public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job activityContext.setInsertionIndex(actIndex); insertionContext.setActivityContext(activityContext); ConstraintsStatus status = fulfilled(insertionContext, prevAct, deliveryAct2Insert, nextAct, prevActStartTime, failedActivityConstraints, constraintManager); + + // check if new vehicle has break + Break aBreak = newVehicle.getBreak(); + if (aBreak != null) { + // check if break has been inserted + if (!currentRoute.getTourActivities().servesJob(aBreak)) { + // get new route end time before insertion of newAct + Double routeEndTime = stateManager.getRouteState(currentRoute, newVehicle, InternalStates.END_TIME, Double.class); + if (routeEndTime == null) routeEndTime = newVehicle.getEarliestDeparture(); + // get future waiting of nextAct in the new route + Double futureWaiting = stateManager.getActivityState(nextAct, newVehicle, InternalStates.FUTURE_WAITING, Double.class); + if (futureWaiting == null) futureWaiting = 0.; + // get nextAct end time delay after insertion of newAct in the new route + double newActArrTime = prevActStartTime + transportCosts.getTransportTime(prevAct.getLocation(), deliveryAct2Insert.getLocation(), prevActStartTime, newDriver, newVehicle); + double newActEndTime = Math.max(deliveryAct2Insert.getTheoreticalEarliestOperationStartTime(), newActArrTime) + activityCosts.getActivityDuration(deliveryAct2Insert, newActArrTime, newDriver, newVehicle); + double nextActArrTime = newActEndTime + transportCosts.getTransportTime(deliveryAct2Insert.getLocation(), nextAct.getLocation(), newActEndTime, newDriver, newVehicle); + double nextActEndTime = Math.max(nextAct.getTheoreticalEarliestOperationStartTime(), nextActArrTime) + activityCosts.getActivityDuration(nextAct, nextActArrTime, newDriver, newVehicle); + Double nextActEndTimeOld = stateManager.getActivityState(nextAct, newVehicle, InternalStates.END_TIME, Double.class); + if (nextActEndTimeOld == null) nextActEndTimeOld = routeEndTime; + double nextActEndTimeDelay = Math.max(0., nextActEndTime - nextActEndTimeOld); + // get new route end time after insertion of newAct + double routeEndTimeNew = routeEndTime + Math.max(0., nextActEndTimeDelay - futureWaiting); + // check if new route end time later than break time window + if (routeEndTimeNew > aBreak.getTimeWindow().getEnd()) { + VehicleRoute.Builder routeBuilder = VehicleRoute.Builder.newInstance(newVehicle, newDriver); + routeBuilder.setJobActivityFactory(activityFactory); + for (int tourActIndex = 0; tourActIndex < currentRoute.getActivities().size(); tourActIndex++) { + if (tourActIndex == actIndex) { + addJobActToRouteBuilder(routeBuilder, jobToInsert, deliveryAct2Insert); + } + TourActivity tourActivity = currentRoute.getActivities().get(tourActIndex); + if (tourActivity instanceof TourActivity.JobActivity) { + addJobActToRouteBuilder(routeBuilder, ((TourActivity.JobActivity) tourActivity).getJob(), tourActivity); + } + } + if (actIndex == currentRoute.getActivities().size()) { + addJobActToRouteBuilder(routeBuilder, jobToInsert, deliveryAct2Insert); + } + routeBuilder.setDepartureTime(newVehicleDepartureTime); + VehicleRoute route = routeBuilder.build(); + stateManager.reCalculateStates(route); + // check if break can be inserted + InsertionData iData = breakInsertionCalculator.getInsertionData(route, aBreak, newVehicle, newVehicleDepartureTime, newDriver, Double.MAX_VALUE); + if (iData instanceof InsertionData.NoInsertionFound) { + status = ConstraintsStatus.NOT_FULFILLED; + } + if (!currentRoute.isEmpty()) + stateManager.reCalculateStates(currentRoute); + } + } + } + if (status.equals(ConstraintsStatus.FULFILLED)) { double additionalICostsAtActLevel = softActivityConstraint.getCosts(insertionContext, prevAct, deliveryAct2Insert, nextAct, prevActStartTime); double additionalTransportationCosts = additionalTransportCostsCalculator.getCosts(insertionContext, prevAct, nextAct, deliveryAct2Insert, prevActStartTime); @@ -182,5 +244,24 @@ public InsertionData getInsertionData(final VehicleRoute currentRoute, final Job return insertionData; } - + private void addJobActToRouteBuilder(VehicleRoute.Builder routeBuilder, Job job, TourActivity tourActivity) { + if (job instanceof Pickup) + routeBuilder.addPickup((Pickup) job); + else if (job instanceof Delivery) + routeBuilder.addDelivery((Delivery) job); + else if (job instanceof Service) + routeBuilder.addService((Service) job); + else if (job instanceof Break) + routeBuilder.addBreak((Break) job); + else if (job instanceof Shipment) { + if (tourActivity instanceof PickupShipment) + routeBuilder.addPickup((Shipment) job); + else if (tourActivity instanceof DeliverShipment) + routeBuilder.addDelivery((Shipment) job); + else + throw new IllegalStateException("tourActivity " + tourActivity.getName()); + } + else + throw new IllegalStateException("job " + job.getName()); + } } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java index e86984835..8bab62f25 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/ShipmentInsertionCalculator.java @@ -17,6 +17,8 @@ */ package com.graphhopper.jsprit.core.algorithm.recreate; +import com.graphhopper.jsprit.core.algorithm.state.InternalStates; +import com.graphhopper.jsprit.core.algorithm.state.StateManager; import com.graphhopper.jsprit.core.problem.JobActivityFactory; import com.graphhopper.jsprit.core.problem.constraint.ConstraintManager; import com.graphhopper.jsprit.core.problem.constraint.HardActivityConstraint.ConstraintsStatus; @@ -25,15 +27,11 @@ import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; import com.graphhopper.jsprit.core.problem.driver.Driver; -import com.graphhopper.jsprit.core.problem.job.Job; -import com.graphhopper.jsprit.core.problem.job.Shipment; +import com.graphhopper.jsprit.core.problem.job.*; import com.graphhopper.jsprit.core.problem.misc.ActivityContext; 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.End; -import com.graphhopper.jsprit.core.problem.solution.route.activity.Start; -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.solution.route.activity.*; import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,6 +64,10 @@ final class ShipmentInsertionCalculator extends AbstractInsertionCalculator { private AdditionalAccessEgressCalculator additionalAccessEgressCalculator; + private StateManager stateManager; + + private BreakInsertionCalculator breakInsertionCalculator; + public ShipmentInsertionCalculator(VehicleRoutingTransportCosts routingCosts, VehicleRoutingActivityCosts activityCosts, ActivityInsertionCostsCalculator activityInsertionCostsCalculator, ConstraintManager constraintManager) { super(); this.activityInsertionCostsCalculator = activityInsertionCostsCalculator; @@ -82,6 +84,14 @@ public void setJobActivityFactory(JobActivityFactory activityFactory) { this.activityFactory = activityFactory; } + public void setStateManager(StateManager stateManager) { + this.stateManager = stateManager; + } + + public void setBreakInsertionCalculator(BreakInsertionCalculator breakInsertionCalculator) { + this.breakInsertionCalculator = breakInsertionCalculator; + } + @Override public String toString() { return "[name=calculatesServiceInsertion]"; @@ -199,6 +209,64 @@ else if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) { activityContext_.setInsertionIndex(j); insertionContext.setActivityContext(activityContext_); ConstraintsStatus deliverShipmentConstraintStatus = fulfilled(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop, failedActivityConstraints, constraintManager); + + // check if new vehicle has break + Break aBreak = newVehicle.getBreak(); + if (aBreak != null) { + // check if break has been inserted + if (!currentRoute.getTourActivities().servesJob(aBreak)) { + // get new route end time before insertion of newAct + Double routeEndTime = stateManager.getRouteState(currentRoute, newVehicle, InternalStates.END_TIME, Double.class); + if (routeEndTime == null) routeEndTime = newVehicle.getEarliestDeparture(); + // get future waiting of nextAct in the new route + Double futureWaiting = stateManager.getActivityState(nextAct, newVehicle, InternalStates.FUTURE_WAITING, Double.class); + if (futureWaiting == null) futureWaiting = 0.; + // get nextAct end time delay after insertion of newAct in the new route + double newActArrTime = prevActEndTime_deliveryLoop + transportCosts.getTransportTime(prevAct.getLocation(), deliverShipment.getLocation(), prevActEndTime_deliveryLoop, newDriver, newVehicle); + double newActEndTime = Math.max(deliverShipment.getTheoreticalEarliestOperationStartTime(), newActArrTime) + activityCosts.getActivityDuration(deliverShipment, newActArrTime, newDriver, newVehicle); + double nextActArrTime = newActEndTime + transportCosts.getTransportTime(deliverShipment.getLocation(), nextAct.getLocation(), newActEndTime, newDriver, newVehicle); + double nextActEndTime = Math.max(nextAct.getTheoreticalEarliestOperationStartTime(), nextActArrTime) + activityCosts.getActivityDuration(nextAct, nextActArrTime, newDriver, newVehicle); + Double nextActEndTimeOld = stateManager.getActivityState(nextAct, newVehicle, InternalStates.END_TIME, Double.class); + if (nextActEndTimeOld == null) nextActEndTimeOld = routeEndTime; + double nextActEndTimeDelay = Math.max(0., nextActEndTime - nextActEndTimeOld); + // get new route end time after insertion of newAct + double routeEndTimeNew = routeEndTime + Math.max(0., nextActEndTimeDelay - futureWaiting); + // check if new route end time later than break time window + if (routeEndTimeNew > aBreak.getTimeWindow().getEnd()) { + VehicleRoute.Builder routeBuilder = VehicleRoute.Builder.newInstance(newVehicle, newDriver); + routeBuilder.setJobActivityFactory(activityFactory); + for (int tourActIndex = 0; tourActIndex < currentRoute.getActivities().size(); tourActIndex++) { + if (tourActIndex == i) { + addJobActToRouteBuilder(routeBuilder, jobToInsert, pickupShipment); + } + if (tourActIndex == j) { + addJobActToRouteBuilder(routeBuilder, jobToInsert, deliverShipment); + } + TourActivity tourActivity = currentRoute.getActivities().get(tourActIndex); + if (tourActivity instanceof TourActivity.JobActivity) { + addJobActToRouteBuilder(routeBuilder, ((TourActivity.JobActivity) tourActivity).getJob(), tourActivity); + } + } + if (i == currentRoute.getActivities().size()) { + addJobActToRouteBuilder(routeBuilder, jobToInsert, pickupShipment); + } + if (j == currentRoute.getActivities().size()) { + addJobActToRouteBuilder(routeBuilder, jobToInsert, deliverShipment); + } + routeBuilder.setDepartureTime(newVehicleDepartureTime); + VehicleRoute route = routeBuilder.build(); + stateManager.reCalculateStates(route); + // check if break can be inserted + InsertionData iData = breakInsertionCalculator.getInsertionData(route, aBreak, newVehicle, newVehicleDepartureTime, newDriver, Double.MAX_VALUE); + if (iData instanceof InsertionData.NoInsertionFound) { + deliverShipmentConstraintStatus = ConstraintsStatus.NOT_FULFILLED; + } + if (!currentRoute.isEmpty()) + stateManager.reCalculateStates(currentRoute); + } + } + } + if (deliverShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) { double additionalDeliveryICosts = softActivityConstraint.getCosts(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop); double deliveryAIC = calculate(insertionContext, prevAct_deliveryLoop, deliverShipment, nextAct_deliveryLoop, prevActEndTime_deliveryLoop); @@ -252,6 +320,26 @@ else if (pickupShipmentConstraintStatus.equals(ConstraintsStatus.FULFILLED)) { private double calculate(JobInsertionContext iFacts, TourActivity prevAct, TourActivity newAct, TourActivity nextAct, double departureTimeAtPrevAct) { return activityInsertionCostsCalculator.getCosts(iFacts, prevAct, nextAct, newAct, departureTimeAtPrevAct); + } + private void addJobActToRouteBuilder(VehicleRoute.Builder routeBuilder, Job job, TourActivity tourActivity) { + if (job instanceof Pickup) + routeBuilder.addPickup((Pickup) job); + else if (job instanceof Delivery) + routeBuilder.addDelivery((Delivery) job); + else if (job instanceof Service) + routeBuilder.addService((Service) job); + else if (job instanceof Break) + routeBuilder.addBreak((Break) job); + else if (job instanceof Shipment) { + if (tourActivity instanceof PickupShipment) + routeBuilder.addPickup((Shipment) job); + else if (tourActivity instanceof DeliverShipment) + routeBuilder.addDelivery((Shipment) job); + else + throw new IllegalStateException("tourActivity " + tourActivity.getName()); + } + else + throw new IllegalStateException("job " + job.getName()); } } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/InternalStates.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/InternalStates.java index 326ab9ec8..641cf08fd 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/InternalStates.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/InternalStates.java @@ -20,7 +20,6 @@ public class InternalStates { - public final static StateId MAXLOAD = new StateFactory.StateIdImpl("max_load", 0); public final static StateId LOAD = new StateFactory.StateIdImpl("load", 1); @@ -52,4 +51,6 @@ public class InternalStates { public static final StateId EARLIEST_WITHOUT_WAITING = new StateFactory.StateIdImpl("earliest_without_waiting", 14); public static final StateId SWITCH_NOT_FEASIBLE = new StateFactory.StateIdImpl("switch_not_feasible", 15); + + public static final StateId END_TIME = new StateFactory.StateIdImpl("end_time", 16); } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentFutureWaitingTimes.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentFutureWaitingTimes.java new file mode 100644 index 000000000..d4f0232f2 --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentFutureWaitingTimes.java @@ -0,0 +1,68 @@ +package com.graphhopper.jsprit.core.algorithm.state; + +import com.graphhopper.jsprit.core.problem.Location; +import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; +import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingTransportCosts; +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.ReverseActivityVisitor; +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.VehicleTypeKey; + +import java.util.*; + +/** + * Created by hehuang on 4/10/17. + */ +public class UpdateVehicleDependentFutureWaitingTimes implements ReverseActivityVisitor, StateUpdater { + + private final StateManager stateManager; + + private List uniqueVehicles; + + private Map states; + + public UpdateVehicleDependentFutureWaitingTimes(StateManager stateManager, + Collection vehicles) { + this.stateManager = stateManager; + uniqueVehicles = getUniqueVehicles(vehicles); + } + + private List getUniqueVehicles(Collection vehicles) { + Set types = new HashSet<>(); + List uniqueVehicles = new ArrayList<>(); + for(Vehicle v : vehicles){ + if(!types.contains(v.getVehicleTypeIdentifier())){ + types.add(v.getVehicleTypeIdentifier()); + uniqueVehicles.add(v); + } + } + return uniqueVehicles; + } + + @Override + public void begin(VehicleRoute route) { + states = new HashMap<>(); + for (Vehicle v : uniqueVehicles) { + states.put(v.getVehicleTypeIdentifier(), 0.); + } + } + + @Override + public void visit(TourActivity activity) { + for (Vehicle v : uniqueVehicles) { + double futureWaiting = states.get(v.getVehicleTypeIdentifier()); + stateManager.putInternalTypedActivityState(activity, v, InternalStates.FUTURE_WAITING, futureWaiting); + if(!(activity instanceof BreakActivity)) { + futureWaiting += stateManager.getActivityState(activity, v, InternalStates.WAITING, Double.class); + } + states.put(v.getVehicleTypeIdentifier(), futureWaiting); + } + } + + @Override + public void finish() { + + } +} diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentTimes.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentTimes.java new file mode 100644 index 000000000..dbb8983c1 --- /dev/null +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/state/UpdateVehicleDependentTimes.java @@ -0,0 +1,109 @@ +package com.graphhopper.jsprit.core.algorithm.state; + +import com.graphhopper.jsprit.core.problem.Location; +import com.graphhopper.jsprit.core.problem.cost.*; +import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; +import com.graphhopper.jsprit.core.problem.solution.route.activity.ActivityVisitor; +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.VehicleTypeKey; + +import java.util.*; + +/** + * Created by hehuang on 4/10/17. + */ +public class UpdateVehicleDependentTimes implements StateUpdater, ActivityVisitor { + + static class State { + + Location prevLocation; + + double time; + + public State(Location prevLocation, double time) { + this.prevLocation = prevLocation; + this.time = time; + } + + public Location getPrevLocation() { + return prevLocation; + } + + public double getTime() { + return time; + } + } + + private final VehicleRoutingTransportCosts transportCosts; + + private final VehicleRoutingActivityCosts activityCosts; + + private final StateManager stateManager; + + private VehicleRoute route; + + private List uniqueVehicles; + + private Map states; + + public UpdateVehicleDependentTimes(VehicleRoutingTransportCosts transportCosts, + VehicleRoutingActivityCosts activityCosts, + StateManager stateManager, + Collection vehicles) { + this.transportCosts = transportCosts; + this.activityCosts = activityCosts; + this.stateManager = stateManager; + uniqueVehicles = getUniqueVehicles(vehicles); + } + + private List getUniqueVehicles(Collection vehicles) { + Set types = new HashSet<>(); + List uniqueVehicles = new ArrayList<>(); + for(Vehicle v : vehicles){ + if(!types.contains(v.getVehicleTypeIdentifier())){ + types.add(v.getVehicleTypeIdentifier()); + uniqueVehicles.add(v); + } + } + return uniqueVehicles; + } + + @Override + public void begin(VehicleRoute route) { + this.route = route; + states = new HashMap<>(); + for (Vehicle v : uniqueVehicles) { + State state = new State(v.getStartLocation(), v.getEarliestDeparture()); + states.put(v.getVehicleTypeIdentifier(), state); + } + } + + @Override + public void visit(TourActivity activity) { + for (Vehicle v : uniqueVehicles) { + State old = states.get(v.getVehicleTypeIdentifier()); + double time = old.getTime(); + time += transportCosts.getTransportTime(old.getPrevLocation(), activity.getLocation(), time, null, v); + + double waiting = Math.max(activity.getTheoreticalEarliestOperationStartTime() - time, 0); + stateManager.putInternalTypedActivityState(activity, v, InternalStates.WAITING, waiting); + + time += waiting + activityCosts.getActivityDuration(activity, time, null, v); + stateManager.putInternalTypedActivityState(activity, v, InternalStates.END_TIME, time); + states.put(v.getVehicleTypeIdentifier(),new State(activity.getLocation(),time)); + } + } + + @Override + public void finish() { + for (Vehicle v : uniqueVehicles) { + State old = states.get(v.getVehicleTypeIdentifier()); + double time = old.getTime(); + if (v.isReturnToDepot()) { + time += transportCosts.getTransportTime(old.getPrevLocation(), v.getEndLocation(), time, null, v); + } + stateManager.putTypedInternalRouteState(route, v, InternalStates.END_TIME, time); + } + } +} diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreBreakTimeWindowTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreBreakTimeWindowTest.java index 12b50a96f..697cc422b 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreBreakTimeWindowTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/IgnoreBreakTimeWindowTest.java @@ -23,7 +23,9 @@ import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; import com.graphhopper.jsprit.core.problem.job.Break; import com.graphhopper.jsprit.core.problem.job.Service; +import com.graphhopper.jsprit.core.problem.job.Shipment; 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.BreakActivity; import com.graphhopper.jsprit.core.problem.solution.route.activity.TimeWindow; import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; @@ -57,11 +59,11 @@ public void doNotIgnoreBreakTW(){ vehicleBuilder.setBreak(Break.Builder.newInstance("lunch").setTimeWindow(TimeWindow.newInstance(14, 14)).setServiceTime(1.).build()); vehicle2 = vehicleBuilder.build(); } + /* * build services at the required locations, each with a capacity-demand of 1. */ - Service service4 = Service.Builder.newInstance("2").setLocation(Location.newInstance(0, 0)) .setServiceTime(1.).setTimeWindow(TimeWindow.newInstance(17,17)).build(); @@ -93,7 +95,6 @@ public void doNotIgnoreBreakTW(){ VehicleRoutingProblemSolution solution = Solutions.bestOf(vra.searchSolutions()); - Assert.assertTrue(breakShouldBeTime(solution)); } @@ -111,4 +112,113 @@ private boolean breakShouldBeTime(VehicleRoutingProblemSolution solution) { } return inTime; } + + @Test + public void breakCannotBeInserted_services() { + VehicleTypeImpl type = VehicleTypeImpl.Builder.newInstance("type") + .build(); + VehicleImpl v1 = VehicleImpl.Builder.newInstance("v1") + .setType(type) + .setReturnToDepot(false) + .setStartLocation(Location.newInstance(0, 0)) + .setEarliestStart(0) + .setLatestArrival(14) + .setBreak( + Break.Builder.newInstance("break") + .setServiceTime(1) + .addTimeWindow(10, 10) + .build() + ) + .build(); + + Service s1 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(0, 0)) + .setServiceTime(4) + .build(); + Service s2 = Service.Builder.newInstance("s2").setLocation(Location.newInstance(0, 0)) + .setServiceTime(4) + .build(); + Service s3 = Service.Builder.newInstance("s3").setLocation(Location.newInstance(0, 0)) + .setServiceTime(4) + .build(); + + VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance() + .addJob(s1).addJob(s2).addJob(s3) + .addVehicle(v1) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + Jsprit.Builder algoBuilder = Jsprit.Builder.newInstance(vrp); + VehicleRoutingAlgorithm algorithm = algoBuilder.buildAlgorithm(); + VehicleRoutingProblemSolution solution = Solutions.bestOf(algorithm.searchSolutions()); + + Assert.assertTrue(breakShouldNotBe(solution)); + } + + @Test + public void breakCannotBeInserted_shipments() { + VehicleTypeImpl type = VehicleTypeImpl.Builder.newInstance("type") + .build(); + VehicleImpl v1 = VehicleImpl.Builder.newInstance("v1") + .setType(type) + .setReturnToDepot(false) + .setStartLocation(Location.newInstance(0, 0)) + .setEarliestStart(0) + .setLatestArrival(14) + .setBreak( + Break.Builder.newInstance("break") + .setServiceTime(1) + .addTimeWindow(10, 10) + .build() + ) + .build(); + + Shipment s1 = Shipment.Builder.newInstance("s1") + .setPickupLocation(Location.newInstance(0, 0)) + .setPickupServiceTime(0) + .setDeliveryLocation(Location.newInstance(0, 0)) + .setDeliveryServiceTime(4) + .build(); + Shipment s2 = Shipment.Builder.newInstance("s2") + .setPickupLocation(Location.newInstance(0, 0)) + .setPickupServiceTime(0) + .setDeliveryLocation(Location.newInstance(0, 0)) + .setDeliveryServiceTime(4) + .build(); + Shipment s3 = Shipment.Builder.newInstance("s3") + .setPickupLocation(Location.newInstance(0, 0)) + .setPickupServiceTime(0) + .setDeliveryLocation(Location.newInstance(0, 0)) + .setDeliveryServiceTime(4) + .build(); + + VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance() + .addJob(s1).addJob(s2).addJob(s3) + .addVehicle(v1) + .setFleetSize(VehicleRoutingProblem.FleetSize.FINITE) + .build(); + + Jsprit.Builder algoBuilder = Jsprit.Builder.newInstance(vrp); + VehicleRoutingAlgorithm algorithm = algoBuilder.buildAlgorithm(); + VehicleRoutingProblemSolution solution = Solutions.bestOf(algorithm.searchSolutions()); + + Assert.assertTrue(breakShouldNotBe(solution)); + } + + private boolean breakShouldNotBe(VehicleRoutingProblemSolution solution) { + boolean breakNotBe = true; + for (VehicleRoute route : solution.getRoutes()) { + Break aBreak = route.getVehicle().getBreak(); + if (aBreak != null) { + if (route.getEnd().getArrTime() > aBreak.getTimeWindow().getEnd()) { + if (!route.getTourActivities().servesJob(aBreak)) + breakNotBe = false; + } + else { + if (route.getTourActivities().servesJob(aBreak)) + breakNotBe = false; + } + } + } + return breakNotBe; + } }