diff --git a/docs/Getting-Started.md b/docs/Getting-Started.md index aea9645f..e666ef52 100644 --- a/docs/Getting-Started.md +++ b/docs/Getting-Started.md @@ -13,11 +13,11 @@ jsprit is a multi-module project and consists of: If you want to use the latest release of jsprit-core, add the following lines to your pom: ``` -<dependency> - <groupId>com.graphhopper</groupId> - <artifactId>jsprit-core</artifactId> - <version>{version}</version> -</dependency> + + com.graphhopper + jsprit-core + {version} + ``` Find the latest versions here: [mvn repository](https://mvnrepository.com/artifact/com.graphhopper/jsprit-core) diff --git a/docs/Other-Projects.md b/docs/Other-Projects.md index f61ccecb..20e9a196 100644 --- a/docs/Other-Projects.md +++ b/docs/Other-Projects.md @@ -15,6 +15,9 @@ VROOM is an optimization engine written in C++14 that aim at providing good solu #### [Hipster4j](http://www.hipster4j.org/) Hipster is an easy to use yet powerful and flexible type-safe library for heuristic search, written in pure Java. It relies on a flexible model with generic operators to define search problems. So you can also model and solve vehicle routing problems. +#### [OscaR](https://bitbucket.org/oscarlib/oscar/wiki/Home) +OscaR, an Open Source Toolbox for Optimising Logistics and Supply Chain Systems. + ### Territory Design #### [OpenDoorLogistics](http://www.opendoorlogistics.com) @@ -23,4 +26,4 @@ standalone open source application for performing geographic analysis of your cu -If you know another promising open source implementation, report it. \ No newline at end of file +If you know another promising open source implementation, report it. 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 20745716..3496f3a1 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 @@ -33,17 +33,19 @@ import java.util.*; */ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ - private Map> openPickupEndTimes = new HashMap<>(); + private Map> openPickupEndTimesPerVehicle = new HashMap<>(); - private Map> slackTimes = new HashMap<>(); + private Map> slackTimesPerVehicle = new HashMap<>(); - private Map> actStartTimes = new HashMap<>(); + private Map> actStartTimesPerVehicle = new HashMap<>(); private VehicleRoute route; private final StateManager stateManager; - private final StateId latestStartId; + private final StateId minSlackId; + + private final StateId openJobsId; private double[] prevActEndTimes; @@ -65,9 +67,10 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ }; - public UpdateMaxTimeInVehicle(StateManager stateManager, StateId slackTimeId, TransportTime transportTime, VehicleRoutingActivityCosts activityCosts) { + public UpdateMaxTimeInVehicle(StateManager stateManager, StateId slackTimeId, TransportTime transportTime, VehicleRoutingActivityCosts activityCosts, StateId openJobsId) { this.stateManager = stateManager; - this.latestStartId = slackTimeId; + this.minSlackId = slackTimeId; + this.openJobsId = openJobsId; this.transportTime = transportTime; prevActEndTimes = new double[stateManager.getMaxIndexOfVehicleTypeIdentifiers() + 1]; prevActLocations = new Location[stateManager.getMaxIndexOfVehicleTypeIdentifiers() + 1]; @@ -82,16 +85,16 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ @Override public void begin(VehicleRoute route) { - openPickupEndTimes.clear(); - slackTimes.clear(); - actStartTimes.clear(); + openPickupEndTimesPerVehicle.clear(); + slackTimesPerVehicle.clear(); + actStartTimesPerVehicle.clear(); vehicles = vehiclesToUpdate.get(route); this.route = route; for(Vehicle v : vehicles){ int vehicleIndex = v.getVehicleTypeIdentifier().getIndex(); - openPickupEndTimes.put(vehicleIndex,new HashMap()); - slackTimes.put(vehicleIndex,new HashMap()); - actStartTimes.put(vehicleIndex,new HashMap()); + openPickupEndTimesPerVehicle.put(vehicleIndex, new HashMap()); + slackTimesPerVehicle.put(vehicleIndex, new HashMap()); + actStartTimesPerVehicle.put(vehicleIndex, new HashMap()); prevActEndTimes[vehicleIndex] = v.getEarliestDeparture(); prevActLocations[vehicleIndex] = v.getStartLocation(); } @@ -109,7 +112,7 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ double activityStart = Math.max(activityArrival,activity.getTheoreticalEarliestOperationStartTime()); memorizeActStart(activity,v,activityStart); double activityEnd = activityStart + activityCosts.getActivityDuration(null, activity, activityArrival, route.getDriver(), v); - Map openPickups = openPickupEndTimes.get(vehicleIndex); + Map openPickups = openPickupEndTimesPerVehicle.get(vehicleIndex); if (activity instanceof ServiceActivity || activity instanceof PickupActivity) { openPickups.put(((TourActivity.JobActivity) activity).getJob(), activityEnd); } else if (activity instanceof DeliveryActivity) { @@ -120,7 +123,7 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ openPickups.remove(job); } else pickupEnd = v.getEarliestDeparture(); double slackTime = maxTime - (activityStart - pickupEnd); - slackTimes.get(vehicleIndex).put(activity, slackTime); + slackTimesPerVehicle.get(vehicleIndex).put(activity, slackTime); } prevActLocations[vehicleIndex] = activity.getLocation(); prevActEndTimes[vehicleIndex] = activityEnd; @@ -136,16 +139,8 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ return maxTime; } -// private double getMaxTimeInVehicle(String jobId) { -// double maxTime = Double.MAX_VALUE; -// if(maxTimes.containsKey(jobId)){ -// maxTime = maxTimes.get(jobId); -// } -// return maxTime; -// } - private void memorizeActStart(TourActivity activity, Vehicle v, double activityStart) { - actStartTimes.get(v.getVehicleTypeIdentifier().getIndex()).put(activity,activityStart); + actStartTimesPerVehicle.get(v.getVehicleTypeIdentifier().getIndex()).put(activity, activityStart); } @Override @@ -158,33 +153,35 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ if(!v.isReturnToDepot()) routeEnd = prevActEndTimes[vehicleIndex]; else routeEnd = prevActEndTimes[vehicleIndex] + transportTime.getTransportTime(prevActLocations[vehicleIndex],v.getEndLocation(),prevActEndTimes[vehicleIndex],route.getDriver(),v); - Map openDeliveries = new HashMap<>(); - for (Job job : openPickupEndTimes.get(vehicleIndex).keySet()) { - double actEndTime = openPickupEndTimes.get(vehicleIndex).get(job); + Map openDeliveries = new HashMap<>(); + for (Job job : openPickupEndTimesPerVehicle.get(vehicleIndex).keySet()) { + double actEndTime = openPickupEndTimesPerVehicle.get(vehicleIndex).get(job); double slackTime = job.getMaxTimeInVehicle() - (routeEnd - actEndTime); - openDeliveries.put(job.getId(), slackTime); + openDeliveries.put(job, slackTime); } double minSlackTimeAtEnd = minSlackTime(openDeliveries); - stateManager.putRouteState(route, v, latestStartId, routeEnd + minSlackTimeAtEnd); + stateManager.putRouteState(route, v, minSlackId, minSlackTimeAtEnd); + stateManager.putRouteState(route, v, openJobsId, new HashMap<>(openDeliveries)); List acts = new ArrayList<>(this.route.getActivities()); Collections.reverse(acts); for (TourActivity act : acts) { + Job job = ((TourActivity.JobActivity) act).getJob(); if (act instanceof ServiceActivity || act instanceof PickupActivity) { - String jobId = ((TourActivity.JobActivity) act).getJob().getId(); - openDeliveries.remove(jobId); + openDeliveries.remove(job); double minSlackTime = minSlackTime(openDeliveries); - double latestStart = actStart(act, v) + minSlackTime; - stateManager.putActivityState(act, v, latestStartId, latestStart); +// double latestStart = actStart(act, v) + minSlackTime; + stateManager.putActivityState(act, v, openJobsId, new HashMap<>(openDeliveries)); + stateManager.putActivityState(act, v, minSlackId, minSlackTime); } else { - String jobId = ((TourActivity.JobActivity) act).getJob().getId(); - if(slackTimes.get(vehicleIndex).containsKey(act)){ - double slackTime = slackTimes.get(vehicleIndex).get(act); - openDeliveries.put(jobId,slackTime); + if (slackTimesPerVehicle.get(vehicleIndex).containsKey(act)) { + double slackTime = slackTimesPerVehicle.get(vehicleIndex).get(act); + openDeliveries.put(job, slackTime); } double minSlackTime = minSlackTime(openDeliveries); - double latestStart = actStart(act, v) + minSlackTime; - stateManager.putActivityState(act, v, latestStartId, latestStart); +// double latestStart = actStart(act, v) + minSlackTime; + stateManager.putActivityState(act, v, openJobsId, new HashMap<>(openDeliveries)); + stateManager.putActivityState(act, v, minSlackId, minSlackTime); } } } @@ -200,44 +197,44 @@ public class UpdateMaxTimeInVehicle implements StateUpdater, ActivityVisitor{ else routeEnd = prevActEndTimes[vehicleIndex] + transportTime.getTransportTime(prevActLocations[vehicleIndex], v.getEndLocation(), prevActEndTimes[vehicleIndex], route.getDriver(), v); - Map openDeliveries = new HashMap<>(); - for (Job job : openPickupEndTimes.get(vehicleIndex).keySet()) { + Map openDeliveries = new HashMap<>(); + for (Job job : openPickupEndTimesPerVehicle.get(vehicleIndex).keySet()) { if (job == ignore) continue; - double actEndTime = openPickupEndTimes.get(vehicleIndex).get(job); + double actEndTime = openPickupEndTimesPerVehicle.get(vehicleIndex).get(job); double slackTime = job.getMaxTimeInVehicle() - (routeEnd - actEndTime); - openDeliveries.put(job.getId(), slackTime); + openDeliveries.put(job, slackTime); } double minSlackTimeAtEnd = minSlackTime(openDeliveries); - stateManager.putRouteState(route, v, latestStartId, routeEnd + minSlackTimeAtEnd); + stateManager.putRouteState(route, v, minSlackId, routeEnd + minSlackTimeAtEnd); List acts = new ArrayList<>(activities); Collections.reverse(acts); for (TourActivity act : acts) { + Job job = ((TourActivity.JobActivity) act).getJob(); if (act instanceof ServiceActivity || act instanceof PickupActivity) { - String jobId = ((TourActivity.JobActivity) act).getJob().getId(); + String jobId = job.getId(); openDeliveries.remove(jobId); double minSlackTime = minSlackTime(openDeliveries); double latestStart = actStart(act, v) + minSlackTime; - stateManager.putActivityState(act, v, latestStartId, latestStart); + stateManager.putActivityState(act, v, minSlackId, latestStart); } else { - String jobId = ((TourActivity.JobActivity) act).getJob().getId(); - if (slackTimes.get(vehicleIndex).containsKey(act)) { - double slackTime = slackTimes.get(vehicleIndex).get(act); - openDeliveries.put(jobId, slackTime); + if (slackTimesPerVehicle.get(vehicleIndex).containsKey(act)) { + double slackTime = slackTimesPerVehicle.get(vehicleIndex).get(act); + openDeliveries.put(job, slackTime); } double minSlackTime = minSlackTime(openDeliveries); double latestStart = actStart(act, v) + minSlackTime; - stateManager.putActivityState(act, v, latestStartId, latestStart); + stateManager.putActivityState(act, v, minSlackId, latestStart); } } } } private double actStart(TourActivity act, Vehicle v) { - return actStartTimes.get(v.getVehicleTypeIdentifier().getIndex()).get(act); + return actStartTimesPerVehicle.get(v.getVehicleTypeIdentifier().getIndex()).get(act); } - private double minSlackTime(Map openDeliveries) { + private double minSlackTime(Map openDeliveries) { double min = Double.MAX_VALUE; for(Double value : openDeliveries.values()){ if(value < min) min = value; diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/VehicleRoutingProblem.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/VehicleRoutingProblem.java index 99a1788e..c08e05cc 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/VehicleRoutingProblem.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/VehicleRoutingProblem.java @@ -232,9 +232,9 @@ public class VehicleRoutingProblem { */ public Builder addJob(AbstractJob job) { if (tentativeJobs.containsKey(job.getId())) - throw new IllegalArgumentException("vehicle routing problem already contains a service or shipment with id " + job.getId() + ". make sure you use unique ids for all services and shipments"); + throw new IllegalArgumentException("The vehicle routing problem already contains a service or shipment with id " + job.getId() + ". Please make sure you use unique ids for all services and shipments."); if (!(job instanceof Service || job instanceof Shipment)) - throw new IllegalArgumentException("job must be either a service or a shipment"); + throw new IllegalArgumentException("Job must be either a service or a shipment."); job.setIndex(jobIndexCounter); incJobIndexCounter(); tentativeJobs.put(job.getId(), job); @@ -285,10 +285,11 @@ public class VehicleRoutingProblem { for (Vehicle v : uniqueVehicles) { if (v.getBreak() != null) { if (!uniqueBreakIds.add(v.getBreak().getId())) - throw new IllegalArgumentException("problem already contains a vehicle break with id " + v.getBreak().getId() + ". choose unique ids for each vehicle break."); + throw new IllegalArgumentException("The vehicle routing roblem already contains a vehicle break with id " + v.getBreak().getId() + ". Please choose unique ids for each vehicle break."); hasBreaks = true; List breakActivities = jobActivityFactory.createActivities(v.getBreak()); - if(breakActivities.isEmpty()) throw new IllegalArgumentException("at least one activity for break needs to be created by activityFactory"); + if (breakActivities.isEmpty()) + throw new IllegalArgumentException("At least one activity for break needs to be created by activityFactory."); for(AbstractActivity act : breakActivities){ act.setIndex(activityIndexCounter); incActivityIndexCounter(); @@ -351,7 +352,7 @@ public class VehicleRoutingProblem { private void addShipment(Shipment job) { if (jobs.containsKey(job.getId())) { - logger.warn("job " + job + " already in job list. overrides existing job."); + logger.warn("The job " + job + " has already been added to the job list. This overrides the existing job."); } addLocationToTentativeLocations(job); // tentative_coordinates.put(job.getPickupLocation().getId(), job.getPickupLocation().getCoordinate()); @@ -367,7 +368,7 @@ public class VehicleRoutingProblem { * */ public Builder addVehicle(Vehicle vehicle) { if (!(vehicle instanceof AbstractVehicle)) - throw new IllegalArgumentException("vehicle must be an AbstractVehicle"); + throw new IllegalArgumentException("A vehicle must be an AbstractVehicle."); return addVehicle((AbstractVehicle) vehicle); } @@ -379,7 +380,7 @@ public class VehicleRoutingProblem { */ public Builder addVehicle(AbstractVehicle vehicle) { if(addedVehicleIds.contains(vehicle.getId())){ - throw new IllegalArgumentException("problem already contains a vehicle with id " + vehicle.getId() + ". choose unique ids for each vehicle."); + throw new IllegalArgumentException("The vehicle routing problem already contains a vehicle with id " + vehicle.getId() + ". Please choose unique ids for each vehicle."); } else addedVehicleIds.add(vehicle.getId()); if (!uniqueVehicles.contains(vehicle)) { @@ -443,7 +444,7 @@ public class VehicleRoutingProblem { } boolean hasBreaks = addBreaksToActivityMap(); if (hasBreaks && fleetSize.equals(FleetSize.INFINITE)) - throw new UnsupportedOperationException("breaks are not yet supported when dealing with infinite fleet. either set it to finite or omit breaks."); + throw new UnsupportedOperationException("Breaks are not yet supported when dealing with infinite fleet. Either set it to finite or omit breaks."); return new VehicleRoutingProblem(this); } @@ -510,7 +511,7 @@ public class VehicleRoutingProblem { // tentative_coordinates.put(service.getLocation().getId(), service.getLocation().getCoordinate()); addLocationToTentativeLocations(service); if (jobs.containsKey(service.getId())) { - logger.warn("service " + service + " already in job list. overrides existing job."); + logger.warn("The service " + service + " has already been added to job list. This overrides existing job."); } jobs.put(service.getId(), service); return this; diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraint.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraint.java index 01d3472c..d078f849 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraint.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraint.java @@ -20,23 +20,19 @@ package com.graphhopper.jsprit.core.problem.constraint; import com.graphhopper.jsprit.core.algorithm.state.StateId; import com.graphhopper.jsprit.core.algorithm.state.StateManager; -import com.graphhopper.jsprit.core.algorithm.state.UpdateMaxTimeInVehicle; -import com.graphhopper.jsprit.core.algorithm.state.UpdateVehicleDependentPracticalTimeWindows; import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; import com.graphhopper.jsprit.core.problem.cost.TransportTime; import com.graphhopper.jsprit.core.problem.cost.VehicleRoutingActivityCosts; +import com.graphhopper.jsprit.core.problem.job.Job; +import com.graphhopper.jsprit.core.problem.job.Shipment; 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.DeliveryActivity; import com.graphhopper.jsprit.core.problem.solution.route.activity.End; import com.graphhopper.jsprit.core.problem.solution.route.activity.PickupActivity; 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.List; +import java.util.Collections; +import java.util.Map; /** * Created by schroeder on 15/09/16. @@ -49,16 +45,19 @@ public class MaxTimeInVehicleConstraint implements HardActivityConstraint { private final VehicleRoutingActivityCosts activityCosts; - private final StateId latestStartId; + private final StateId minSlackId; + + private final StateId openJobsId; private final StateManager stateManager; - public MaxTimeInVehicleConstraint(TransportTime transportTime, VehicleRoutingActivityCosts activityCosts, StateId latestStartId, StateManager stateManager, VehicleRoutingProblem vrp) { + public MaxTimeInVehicleConstraint(TransportTime transportTime, VehicleRoutingActivityCosts activityCosts, StateId minSlackId, StateManager stateManager, VehicleRoutingProblem vrp, StateId openJobsId) { this.transportTime = transportTime; - this.latestStartId = latestStartId; + this.minSlackId = minSlackId; this.stateManager = stateManager; this.activityCosts = activityCosts; this.vrp = vrp; + this.openJobsId = openJobsId; } @Override @@ -102,55 +101,53 @@ public class MaxTimeInVehicleConstraint implements HardActivityConstraint { //************ 2. check whether insertion of new shipment satisfies all other max-in-vehicle-constraints - if(newActIsPickup || iFacts.getAssociatedActivities().size() == 1) { - double latest; - if (iFacts.getRoute().isEmpty()) latest = Double.MAX_VALUE; - else if (nextAct instanceof End) { - latest = stateManager.getRouteState(iFacts.getRoute(), iFacts.getNewVehicle(), latestStartId, Double.class); - } else latest = stateManager.getActivityState(nextAct, iFacts.getNewVehicle(), latestStartId, Double.class); - - if (nextActStart > latest) { - return ConstraintsStatus.NOT_FULFILLED; - } - - } else { - boolean isShipment = iFacts.getAssociatedActivities().size() == 2; - if (newActIsDelivery && isShipment) { - StateManager localStateManager = new StateManager(vrp); - StateId stateId = localStateManager.createStateId("local-slack"); - UpdateMaxTimeInVehicle updateMaxTimeInVehicle = new UpdateMaxTimeInVehicle(localStateManager, stateId, transportTime, activityCosts); - updateMaxTimeInVehicle.setVehiclesToUpdate(new UpdateVehicleDependentPracticalTimeWindows.VehiclesToUpdate() { - @Override - public Collection get(VehicleRoute route) { - return Arrays.asList(iFacts.getNewVehicle()); + double minSlack = Double.MAX_VALUE; + if (!(nextAct instanceof End)) { + minSlack = stateManager.getActivityState(nextAct, iFacts.getNewVehicle(), minSlackId, Double.class); + } + double directArrTimeNextAct = prevActDepTime + transportTime.getTransportTime(prevAct.getLocation(), nextAct.getLocation(), prevActDepTime, iFacts.getNewDriver(), iFacts.getNewVehicle()); + double directNextActStart = Math.max(directArrTimeNextAct, nextAct.getTheoreticalEarliestOperationStartTime()); + double additionalTimeOfNewAct = (nextActStart - prevActDepTime) - (directNextActStart - prevActDepTime); + if (additionalTimeOfNewAct > minSlack) { + if (newActIsPickup) return ConstraintsStatus.NOT_FULFILLED; + else return ConstraintsStatus.NOT_FULFILLED; + } + if (newActIsDelivery) { + Map openJobsAtNext; + if (nextAct instanceof End) + openJobsAtNext = stateManager.getRouteState(iFacts.getRoute(), iFacts.getNewVehicle(), openJobsId, Map.class); + else openJobsAtNext = stateManager.getActivityState(nextAct, iFacts.getNewVehicle(), openJobsId, Map.class); + if (openJobsAtNext == null) openJobsAtNext = Collections.emptyMap(); + for (Job openJob : openJobsAtNext.keySet()) { + double slack = openJobsAtNext.get(openJob); + double additionalTimeOfNewJob = additionalTimeOfNewAct; + if (openJob instanceof Shipment) { + Map openJobsAtNextOfPickup = Collections.emptyMap(); + TourActivity nextAfterPickup; + if (iFacts.getAssociatedActivities().size() == 1 && !iFacts.getRoute().isEmpty()) + nextAfterPickup = iFacts.getRoute().getActivities().get(0); + else + nextAfterPickup = iFacts.getRoute().getActivities().get(iFacts.getRelatedActivityContext().getInsertionIndex()); + if (nextAfterPickup != null) + openJobsAtNextOfPickup = stateManager.getActivityState(nextAfterPickup, iFacts.getNewVehicle(), openJobsId, Map.class); + if (openJobsAtNextOfPickup.containsKey(openJob)) { + TourActivity pickupAct = iFacts.getAssociatedActivities().get(0); + double pickupActArrTime = iFacts.getRelatedActivityContext().getArrivalTime(); + double pickupActEndTime = startOf(pickupAct, pickupActArrTime) + activityCosts.getActivityDuration(pickupAct, pickupActArrTime, iFacts.getNewDriver(), iFacts.getNewVehicle()); + double nextAfterPickupArr = pickupActEndTime + transportTime.getTransportTime(pickupAct.getLocation(), nextAfterPickup.getLocation(), pickupActArrTime, iFacts.getNewDriver(), iFacts.getNewVehicle()); + additionalTimeOfNewJob += startOf(nextAfterPickup, nextAfterPickupArr) - startOf(nextAfterPickup, nextAfterPickup.getArrTime()); } - }); - updateMaxTimeInVehicle.begin(iFacts.getRoute()); - List tourActivities = new ArrayList<>(iFacts.getRoute().getActivities()); - tourActivities.add(iFacts.getRelatedActivityContext().getInsertionIndex(), iFacts.getAssociatedActivities().get(0)); - for (TourActivity act : tourActivities) { - updateMaxTimeInVehicle.visit(act); } - updateMaxTimeInVehicle.finish(tourActivities, iFacts.getJob()); - - double latest; - if (iFacts.getRoute().isEmpty()) latest = Double.MAX_VALUE; - else if (nextAct instanceof End) { - latest = localStateManager.getRouteState(iFacts.getRoute(), iFacts.getNewVehicle(), stateId, Double.class); - } else - latest = localStateManager.getActivityState(nextAct, iFacts.getNewVehicle(), stateId, Double.class); - - if (nextActStart > latest) { + if (additionalTimeOfNewJob > slack) { return ConstraintsStatus.NOT_FULFILLED; } - } } return ConstraintsStatus.FULFILLED; } -// private double getMaxTime(String jobId) { -// if(maxTimes.containsKey(jobId)) return maxTimes.get(jobId); -// else return Double.MAX_VALUE; -// } + private double startOf(TourActivity act, double arrTime) { + return Math.max(arrTime, act.getTheoreticalEarliestOperationStartTime()); + } + } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Job.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Job.java index 55728235..76b65084 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Job.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Job.java @@ -30,6 +30,7 @@ import com.graphhopper.jsprit.core.problem.Skills; */ public interface Job extends HasId, HasIndex { + /** * Returns the unique identifier (id) of a job. * diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java index af4e6b85..99608c23 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Service.java @@ -92,7 +92,7 @@ public class Service extends AbstractJob { protected Object userData; protected double maxTimeInVehicle = Double.MAX_VALUE; - + Builder(String id){ this.id = id; timeWindows = new TimeWindowsImpl(); @@ -135,7 +135,7 @@ public class Service extends AbstractJob { */ public Builder setServiceTime(double serviceTime) { if (serviceTime < 0) - throw new IllegalArgumentException("serviceTime must be greater than or equal to zero"); + throw new IllegalArgumentException("The service time of a service must be greater than or equal to zero."); this.serviceTime = serviceTime; return this; } @@ -167,20 +167,20 @@ public class Service extends AbstractJob { * @throws IllegalArgumentException if dimensionValue < 0 */ public Builder addSizeDimension(int dimensionIndex, int dimensionValue) { - if (dimensionValue < 0) throw new IllegalArgumentException("capacity value cannot be negative"); + if (dimensionValue < 0) throw new IllegalArgumentException("The capacity value must not be negative."); capacityBuilder.addDimension(dimensionIndex, dimensionValue); return this; } public Builder setTimeWindow(TimeWindow tw){ - if(tw == null) throw new IllegalArgumentException("time-window arg must not be null"); + if (tw == null) throw new IllegalArgumentException("The time window must not be null."); this.timeWindows = new TimeWindowsImpl(); timeWindows.add(tw); return this; } public Builder addTimeWindow(TimeWindow timeWindow) { - if(timeWindow == null) throw new IllegalArgumentException("time-window arg must not be null"); + if (timeWindow == null) throw new IllegalArgumentException("The time window must not be null."); if(!twAdded){ timeWindows = new TimeWindowsImpl(); twAdded = true; @@ -205,7 +205,7 @@ public class Service extends AbstractJob { * @throws IllegalArgumentException if neither locationId nor coordinate is set. */ public T build() { - if (location == null) throw new IllegalArgumentException("location is missing"); + if (location == null) throw new IllegalArgumentException("The location of service " + id + " is missing."); this.setType("service"); capacity = capacityBuilder.build(); skills = skillBuilder.build(); @@ -222,10 +222,13 @@ public class Service extends AbstractJob { return this; } + public Builder addAllRequiredSkills(Collection skills) { + skillBuilder.addAllSkills(skills); + return this; + } + public Builder addAllRequiredSkills(Skills skills){ - for(String s : skills.values()){ - skillBuilder.addSkill(s); - } + skillBuilder.addAllSkills(skills.values()); return this; } @@ -246,13 +249,13 @@ public class Service extends AbstractJob { */ public Builder setPriority(int priority) { if (priority < 1 || priority > 10) - throw new IllegalArgumentException("incorrect priority. only priority values from 1 to 10 are allowed where 1 = high and 10 is low"); + throw new IllegalArgumentException("The priority value is not valid. Only 1 (very high) to 10 (very low) are allowed."); this.priority = priority; return this; } public Builder setMaxTimeInVehicle(double maxTimeInVehicle){ - throw new UnsupportedOperationException("maxTimeInVehicle is not yet supported for Pickups and Services (only for Deliveries and Shipments)"); + throw new UnsupportedOperationException("The maximum time in vehicle is not yet supported for Pickups and Services (only for Deliveries and Shipments)."); // if(maxTimeInVehicle < 0) throw new IllegalArgumentException("maxTimeInVehicle should be positive"); // this.maxTimeInVehicle = maxTimeInVehicle; // return this; diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java index a3eeaaf6..8d356a02 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/job/Shipment.java @@ -148,7 +148,8 @@ public class Shipment extends AbstractJob { * @throws IllegalArgumentException if servicTime < 0.0 */ public Builder setPickupServiceTime(double serviceTime) { - if (serviceTime < 0.0) throw new IllegalArgumentException("serviceTime must not be < 0.0"); + if (serviceTime < 0.0) + throw new IllegalArgumentException("The service time of a shipment must not be < 0.0."); this.pickupServiceTime = serviceTime; return this; } @@ -164,7 +165,7 @@ public class Shipment extends AbstractJob { * @throws IllegalArgumentException if timeWindow is null */ public Builder setPickupTimeWindow(TimeWindow timeWindow) { - if (timeWindow == null) throw new IllegalArgumentException("delivery time-window must not be null"); + if (timeWindow == null) throw new IllegalArgumentException("The delivery time window must not be null."); this.pickupTimeWindows = new TimeWindowsImpl(); this.pickupTimeWindows.add(timeWindow); return this; @@ -193,7 +194,8 @@ public class Shipment extends AbstractJob { * @throws IllegalArgumentException if serviceTime < 0.0 */ public Builder setDeliveryServiceTime(double deliveryServiceTime) { - if (deliveryServiceTime < 0.0) throw new IllegalArgumentException("deliveryServiceTime must not be < 0.0"); + if (deliveryServiceTime < 0.0) + throw new IllegalArgumentException("The service time of a delivery must not be < 0.0."); this.deliveryServiceTime = deliveryServiceTime; return this; } @@ -209,7 +211,7 @@ public class Shipment extends AbstractJob { * @throws IllegalArgumentException if timeWindow is null */ public Builder setDeliveryTimeWindow(TimeWindow timeWindow) { - if (timeWindow == null) throw new IllegalArgumentException("delivery time-window must not be null"); + if (timeWindow == null) throw new IllegalArgumentException("The delivery time window must not be null."); this.deliveryTimeWindows = new TimeWindowsImpl(); this.deliveryTimeWindows.add(timeWindow); return this; @@ -224,7 +226,8 @@ public class Shipment extends AbstractJob { * @throws IllegalArgumentException if dimVal < 0 */ public Builder addSizeDimension(int dimensionIndex, int dimensionValue) { - if (dimensionValue < 0) throw new IllegalArgumentException("capacity value cannot be negative"); + if (dimensionValue < 0) + throw new IllegalArgumentException("The capacity value must not be negative, but is " + dimensionValue + "."); capacityBuilder.addDimension(dimensionIndex, dimensionValue); return this; } @@ -245,8 +248,8 @@ public class Shipment extends AbstractJob { * is set */ public Shipment build() { - if (pickupLocation_ == null) throw new IllegalArgumentException("pickup location is missing"); - if (deliveryLocation_ == null) throw new IllegalArgumentException("delivery location is missing"); + if (pickupLocation_ == null) throw new IllegalArgumentException("The pickup location is missing."); + if (deliveryLocation_ == null) throw new IllegalArgumentException("The delivery location is missing."); capacity = capacityBuilder.build(); skills = skillBuilder.build(); return new Shipment(this); @@ -258,10 +261,13 @@ public class Shipment extends AbstractJob { return this; } + public Builder addAllRequiredSkills(Collection skills) { + skillBuilder.addAllSkills(skills); + return this; + } + public Builder addAllRequiredSkills(Skills skills) { - for (String s : skills.values()) { - addRequiredSkill(s); - } + addAllRequiredSkills(skills.values()); return this; } @@ -271,7 +277,7 @@ public class Shipment extends AbstractJob { } public Builder addDeliveryTimeWindow(TimeWindow timeWindow) { - if(timeWindow == null) throw new IllegalArgumentException("time-window arg must not be null"); + if (timeWindow == null) throw new IllegalArgumentException("The time window must not be null."); if(!deliveryTimeWindowAdded){ deliveryTimeWindows = new TimeWindowsImpl(); deliveryTimeWindowAdded = true; @@ -291,7 +297,7 @@ public class Shipment extends AbstractJob { } public Builder addPickupTimeWindow(TimeWindow timeWindow) { - if(timeWindow == null) throw new IllegalArgumentException("time-window arg must not be null"); + if (timeWindow == null) throw new IllegalArgumentException("The time window must not be null."); if(!pickupTimeWindowAdded){ pickupTimeWindows = new TimeWindowsImpl(); pickupTimeWindowAdded = true; @@ -319,7 +325,7 @@ public class Shipment extends AbstractJob { */ public Builder setPriority(int priority) { if (priority < 1 || priority > 10) - throw new IllegalArgumentException("incorrect priority. only 1 (very high) to 10 (very low) are allowed"); + throw new IllegalArgumentException("The priority value is not valid. Only 1 (very high) to 10 (very low) are allowed."); this.priority = priority; return this; } @@ -331,7 +337,8 @@ public class Shipment extends AbstractJob { * @return */ public Builder setMaxTimeInVehicle(double maxTimeInVehicle){ - if(maxTimeInVehicle < 0) throw new IllegalArgumentException("maxTimeInVehicle should be positive"); + if (maxTimeInVehicle < 0) + throw new IllegalArgumentException("The maximum time in vehicle must be positive."); this.maxTimeInVehicle = maxTimeInVehicle; return this; } @@ -436,7 +443,7 @@ public class Shipment extends AbstractJob { return pickupTimeWindows.getTimeWindows(); } - + /** * Returns a string with the shipment's attributes. *

diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleImpl.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleImpl.java index f77f7b9c..0731abf2 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleImpl.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleImpl.java @@ -24,6 +24,8 @@ import com.graphhopper.jsprit.core.problem.job.Break; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Collection; + /** * Implementation of {@link Vehicle}. @@ -135,6 +137,7 @@ public class VehicleImpl extends AbstractVehicle { private Builder(String id) { super(); this.id = id; + if (id == null) throw new IllegalArgumentException("Vehicle id must not be null."); } /** @@ -145,7 +148,7 @@ public class VehicleImpl extends AbstractVehicle { * @throws IllegalArgumentException if type is null */ public Builder setType(VehicleType type) { - if (type == null) throw new IllegalArgumentException("type cannot be null."); + if (type == null) throw new IllegalArgumentException("Vehicle type must not be null."); this.type = type; return this; } @@ -196,6 +199,8 @@ public class VehicleImpl extends AbstractVehicle { * @return start location */ public Builder setStartLocation(Location startLocation) { + if (startLocation == null) + throw new IllegalArgumentException("Start location of vehicle " + id + " must not be null."); this.startLocation = startLocation; return this; } @@ -213,7 +218,7 @@ public class VehicleImpl extends AbstractVehicle { */ public Builder setEarliestStart(double earliest_startTime) { if (earliest_startTime < 0) - throw new IllegalArgumentException("earliest start of vehicle " + id + " must not be negative"); + throw new IllegalArgumentException("The earliest start time of vehicle " + id + " must not be negative."); this.earliestStart = earliest_startTime; return this; } @@ -226,12 +231,19 @@ public class VehicleImpl extends AbstractVehicle { */ public Builder setLatestArrival(double latest_arrTime) { if (latest_arrTime < 0) - throw new IllegalArgumentException("latest arrival time of vehicle " + id + " must not be negative"); + throw new IllegalArgumentException("The latest arrival time of vehicle " + id + " must not be negative."); this.latestArrival = latest_arrTime; return this; } + public Builder addAllSkills(Collection skills) { + if (skills == null) throw new IllegalArgumentException("Skills of vehicle " + id + " must not be null"); + skillBuilder.addAllSkills(skills); + return this; + } + public Builder addSkill(String skill) { + if (skill == null) throw new IllegalArgumentException("Skill of vehicle " + id + " must not be null"); skillBuilder.addSkill(skill); return this; } @@ -254,17 +266,17 @@ public class VehicleImpl extends AbstractVehicle { */ public VehicleImpl build() { if (latestArrival < earliestStart) - throw new IllegalArgumentException("latest arrival of vehicle " + id + " must not be smaller than its start time"); + throw new IllegalArgumentException("The latest arrival time of vehicle " + id + " must not be smaller than its start time."); if (startLocation != null && endLocation != null) { if (!startLocation.getId().equals(endLocation.getId()) && !returnToDepot) - throw new IllegalArgumentException("this must not be. you specified both endLocationId and open-routes. this is contradictory.
" + - "if you set endLocation, returnToDepot must be true. if returnToDepot is false, endLocationCoord must not be specified."); + throw new IllegalArgumentException("You specified both the end location and that the vehicle " + id + " does not need to return to its end location. This must not be. " + + "Either specify end location and return to depot or leave end location unspecified."); } if (startLocation != null && endLocation == null) { endLocation = startLocation; } if (startLocation == null && endLocation == null) - throw new IllegalArgumentException("vehicle requires startLocation. but neither locationId nor locationCoord nor startLocationId nor startLocationCoord has been set"); + throw new IllegalArgumentException("Every vehicle requires a start location, but vehicle " + id + " does not have one."); skills = skillBuilder.build(); return new VehicleImpl(this); } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java index 3e8da61d..112c137c 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/problem/vehicle/VehicleTypeImpl.java @@ -151,7 +151,8 @@ public class VehicleTypeImpl implements VehicleType { * if velocity is smaller than zero */ public VehicleTypeImpl.Builder setMaxVelocity(double inMeterPerSeconds) { - if (inMeterPerSeconds < 0.0) throw new IllegalArgumentException("velocity cannot be smaller than zero"); + if (inMeterPerSeconds < 0.0) + throw new IllegalArgumentException("The velocity of a vehicle (type) cannot be smaller than zero."); this.maxVelo = inMeterPerSeconds; return this; } @@ -166,7 +167,7 @@ public class VehicleTypeImpl implements VehicleType { * @throws IllegalArgumentException if fixedCost is smaller than zero */ public VehicleTypeImpl.Builder setFixedCost(double fixedCost) { - if (fixedCost < 0.0) throw new IllegalArgumentException("fixed costs cannot be smaller than zero"); + if (fixedCost < 0.0) throw new IllegalArgumentException("Fixed costs must not be smaller than zero."); this.fixedCost = fixedCost; return this; } @@ -181,7 +182,8 @@ public class VehicleTypeImpl implements VehicleType { * @throws IllegalArgumentException if perDistance is smaller than zero */ public VehicleTypeImpl.Builder setCostPerDistance(double perDistance) { - if (perDistance < 0.0) throw new IllegalArgumentException("cost per distance must not be smaller than zero"); + if (perDistance < 0.0) + throw new IllegalArgumentException("Cost per distance must not be smaller than zero."); this.perDistance = perDistance; return this; } @@ -260,9 +262,9 @@ public class VehicleTypeImpl implements VehicleType { * @throws IllegalArgumentException if capacity dimension is already set */ public Builder addCapacityDimension(int dimIndex, int dimVal) { - if (dimVal < 0) throw new IllegalArgumentException("capacity value cannot be negative"); + if (dimVal < 0) throw new IllegalArgumentException("The capacity value must not be negative."); if (capacityDimensions != null) - throw new IllegalArgumentException("either build your dimension with build your dimensions with " + + throw new IllegalArgumentException("Either build your dimension with build your dimensions with " + "addCapacityDimension(int dimIndex, int dimVal) or set the already built dimensions with .setCapacityDimensions(Capacity capacity)." + "You used both methods."); dimensionAdded = true; @@ -283,7 +285,7 @@ public class VehicleTypeImpl implements VehicleType { */ public Builder setCapacityDimensions(Capacity capacity) { if (dimensionAdded) - throw new IllegalArgumentException("either build your dimension with build your dimensions with " + + throw new IllegalArgumentException("Either build your dimension with build your dimensions with " + "addCapacityDimension(int dimIndex, int dimVal) or set the already built dimensions with .setCapacityDimensions(Capacity capacity)." + "You used both methods."); this.capacityDimensions = capacity; diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTracker.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTracker.java index 63dbf938..0ab34dec 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTracker.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTracker.java @@ -29,15 +29,19 @@ import java.util.*; */ public class UnassignedJobReasonTracker implements JobUnassignedListener { + private final static String NO_REASON = "cannot determine a particular reason"; + public static String getMostLikelyFailedConstraintName(Frequency failedConstraintNamesFrequency) { - if (failedConstraintNamesFrequency == null) return "no reason found"; + if (failedConstraintNamesFrequency == null || failedConstraintNamesFrequency.getUniqueCount() == 0) + return NO_REASON; Iterator, Long>> entryIterator = failedConstraintNamesFrequency.entrySetIterator(); - int maxCount = 0; + long maxCount = 0; String mostLikely = null; while (entryIterator.hasNext()) { Map.Entry, Long> entry = entryIterator.next(); if (entry.getValue() > maxCount) { Comparable key = entry.getKey(); + maxCount = entry.getValue(); mostLikely = key.toString(); } } @@ -53,6 +57,7 @@ public class UnassignedJobReasonTracker implements JobUnassignedListener { Set failedConstraintNamesToBeIgnored = new HashSet<>(); public UnassignedJobReasonTracker() { + codesToHumanReadableReason.put(-1, NO_REASON); codesToHumanReadableReason.put(1, "cannot serve required skill"); codesToHumanReadableReason.put(2, "cannot be visited within time window"); codesToHumanReadableReason.put(3, "does not fit into any vehicle due to capacity"); @@ -164,8 +169,9 @@ public class UnassignedJobReasonTracker implements JobUnassignedListener { * @return */ public String getMostLikelyReason(String jobId) { - if (!this.failedConstraintNamesFrequencyMapping.containsKey(jobId)) return "no reason found"; + if (!this.failedConstraintNamesFrequencyMapping.containsKey(jobId)) return codesToHumanReadableReason.get(-1); Frequency reasons = this.failedConstraintNamesFrequencyMapping.get(jobId); + String mostLikelyReason = getMostLikelyFailedConstraintName(reasons); int code = toCode(mostLikelyReason); if (code == -1) return mostLikelyReason; diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/MaxTimeInVehicle_IT.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/MaxTimeInVehicle_IT.java index 18285a11..a592b9da 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/MaxTimeInVehicle_IT.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/MaxTimeInVehicle_IT.java @@ -54,10 +54,11 @@ public class MaxTimeInVehicle_IT { StateManager stateManager = new StateManager(vrp); StateId id = stateManager.createStateId("max-time"); - stateManager.addStateUpdater(new UpdateMaxTimeInVehicle(stateManager,id,vrp.getTransportCosts(),vrp.getActivityCosts())); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); + stateManager.addStateUpdater(new UpdateMaxTimeInVehicle(stateManager, id, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId)); ConstraintManager constraintManager = new ConstraintManager(vrp,stateManager); - constraintManager.addConstraint(new MaxTimeInVehicleConstraint(vrp.getTransportCosts(),vrp.getActivityCosts(),id,stateManager, vrp), ConstraintManager.Priority.CRITICAL); + constraintManager.addConstraint(new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), id, stateManager, vrp, openJobsId), ConstraintManager.Priority.CRITICAL); VehicleRoutingAlgorithm vra = Jsprit.Builder.newInstance(vrp).setStateAndConstraintManager(stateManager,constraintManager).buildAlgorithm(); VehicleRoutingProblemSolution solution = Solutions.bestOf(vra.searchSolutions()); diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicleTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicleTest.java index f39164d1..75aaf145 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicleTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/state/UpdateMaxTimeInVehicleTest.java @@ -30,15 +30,12 @@ import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; 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 org.junit.Assert; import org.junit.Before; import org.junit.Test; import java.util.Arrays; import java.util.Collection; -import java.util.HashMap; -import java.util.Map; /** * Created by schroeder on 15/09/16. @@ -61,7 +58,9 @@ public class UpdateMaxTimeInVehicleTest { private StateManager stateManager; - private StateId latestStartId; + private StateId minSlackId; + + private StateId openJobsId; @Before public void doBefore() { @@ -107,12 +106,13 @@ public class UpdateMaxTimeInVehicleTest { stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(),vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(route), null); - latestStartId = stateManager.createStateId("slack-time-id"); + minSlackId = stateManager.createStateId("min-slack-id"); + openJobsId = stateManager.createStateId("open-jobs-id"); // Map maxTimes = new HashMap<>(); // maxTimes.put("s",40d); // maxTimes.put("shipment",20d); - maxTimeInVehicleConstraint = new UpdateMaxTimeInVehicle(stateManager, latestStartId,vrp.getTransportCosts(), vrp.getActivityCosts()); + maxTimeInVehicleConstraint = new UpdateMaxTimeInVehicle(stateManager, minSlackId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); maxTimeInVehicleConstraint.setVehiclesToUpdate(new UpdateVehicleDependentPracticalTimeWindows.VehiclesToUpdate() { @Override public Collection get(VehicleRoute route) { @@ -128,23 +128,23 @@ public class UpdateMaxTimeInVehicleTest { // for(TourActivity act : route.getActivities()){ // String jobId = ((TourActivity.JobActivity)act).getJob().getId(); // if(jobId.equals("s4")){ -// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), minSlackId,Double.class); // Assert.assertEquals(40, slackTime, 0.001); // } // if(jobId.equals("s3")){ -// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), minSlackId,Double.class); // Assert.assertEquals(30, slackTime, 0.001); // } // if(jobId.equals("s2")){ -// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), minSlackId,Double.class); // Assert.assertEquals(20, slackTime, 0.001); // } // if(jobId.equals("s")){ -// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,route.getVehicle(), minSlackId,Double.class); // Assert.assertEquals(Double.MAX_VALUE, slackTime, 0.001); // } // } -// Double slackTime = stateManager.getRouteState(route,route.getVehicle(), latestStartId,Double.class); +// Double slackTime = stateManager.getRouteState(route,route.getVehicle(), minSlackId,Double.class); // Assert.assertNotNull(slackTime); // Assert.assertEquals(50,slackTime,0.001); // } @@ -155,23 +155,23 @@ public class UpdateMaxTimeInVehicleTest { // for(TourActivity act : route.getActivities()){ // String jobId = ((TourActivity.JobActivity)act).getJob().getId(); // if(jobId.equals("s4")){ -// Double slackTime = stateManager.getActivityState(act,vehicle2, latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,vehicle2, minSlackId,Double.class); // Assert.assertEquals(40, slackTime, 0.001); // } // if(jobId.equals("s3")){ -// Double slackTime = stateManager.getActivityState(act,vehicle2, latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,vehicle2, minSlackId,Double.class); // Assert.assertEquals(30, slackTime, 0.001); // } // if(jobId.equals("s2")){ -// Double slackTime = stateManager.getActivityState(act,vehicle2, latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,vehicle2, minSlackId,Double.class); // Assert.assertEquals(20, slackTime, 0.001); // } // if(jobId.equals("s")){ -// Double slackTime = stateManager.getActivityState(act,vehicle2, latestStartId,Double.class); +// Double slackTime = stateManager.getActivityState(act,vehicle2, minSlackId,Double.class); // Assert.assertEquals(Double.MAX_VALUE, slackTime, 0.001); // } // } -// Double slackTime = stateManager.getRouteState(route,vehicle2, latestStartId,Double.class); +// Double slackTime = stateManager.getRouteState(route,vehicle2, minSlackId,Double.class); // Assert.assertNotNull(slackTime); // Assert.assertEquals(40,slackTime,0.001); // } @@ -182,17 +182,17 @@ public class UpdateMaxTimeInVehicleTest { for(TourActivity act : route2.getActivities()){ String jobId = ((TourActivity.JobActivity)act).getJob().getId(); if(jobId.equals("d1")){ - Double slackTime = stateManager.getActivityState(act,v, latestStartId,Double.class); + Double slackTime = stateManager.getActivityState(act, v, minSlackId, Double.class); Assert.assertEquals(Double.MAX_VALUE, slackTime, 0.001); } if(jobId.equals("shipment")){ if(act instanceof PickupActivity){ - Double slackTime = stateManager.getActivityState(act,v, latestStartId,Double.class); + Double slackTime = stateManager.getActivityState(act, v, minSlackId, Double.class); Assert.assertEquals(Double.MAX_VALUE, slackTime, 0.001); } else{ - Double slackTime = stateManager.getActivityState(act,v, latestStartId,Double.class); - Assert.assertEquals(40, slackTime, 0.001); + Double slackTime = stateManager.getActivityState(act, v, minSlackId, Double.class); + Assert.assertEquals(0, slackTime, 0.001); } } diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraintTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraintTest.java index 3e96ef7c..95def3f0 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraintTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/problem/constraint/MaxTimeInVehicleConstraintTest.java @@ -20,6 +20,7 @@ package com.graphhopper.jsprit.core.problem.constraint; import com.graphhopper.jsprit.core.algorithm.state.StateId; import com.graphhopper.jsprit.core.algorithm.state.StateManager; +import com.graphhopper.jsprit.core.algorithm.state.UpdateActivityTimes; import com.graphhopper.jsprit.core.algorithm.state.UpdateMaxTimeInVehicle; import com.graphhopper.jsprit.core.problem.AbstractActivity; import com.graphhopper.jsprit.core.problem.Location; @@ -112,13 +113,15 @@ public class MaxTimeInVehicleConstraintTest { .addPickup(s1).addDelivery(s1).build(); StateManager stateManager = new StateManager(vrp); - StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId minSlackId = stateManager.createStateId("min-slack-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager,latestStartId,vrp.getTransportCosts(), vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, minSlackId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(route),new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(),vrp.getActivityCosts() , latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), minSlackId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(route,s2,v,route.getDriver(),0.); List acts = vrp.getActivities(s2); @@ -126,7 +129,7 @@ public class MaxTimeInVehicleConstraintTest { c.getAssociatedActivities().add(acts.get(1)); Assert.assertEquals(HardActivityConstraint.ConstraintsStatus.FULFILLED, constraint.fulfilled(c, route.getStart(), acts.get(0), route.getActivities().get(0), 0)); - Assert.assertEquals(HardActivityConstraint.ConstraintsStatus.NOT_FULFILLED, constraint.fulfilled(c, act(route,0), acts.get(0), act(route,1), 20)); + Assert.assertEquals(HardActivityConstraint.ConstraintsStatus.NOT_FULFILLED, constraint.fulfilled(c, act(route, 0), acts.get(0), act(route, 1), 20)); Assert.assertEquals(HardActivityConstraint.ConstraintsStatus.FULFILLED, constraint.fulfilled(c, act(route,1), acts.get(0), route.getEnd(), 40)); //insert pickup at 0 @@ -148,12 +151,14 @@ public class MaxTimeInVehicleConstraintTest { ini(30d, Double.MAX_VALUE, Double.MAX_VALUE); StateManager stateManager = new StateManager(vrp); StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager,latestStartId,vrp.getTransportCosts(), vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(route),new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(),vrp.getActivityCosts() , latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(route,d2,v,route.getDriver(),0.); List acts = vrp.getActivities(d2); c.getAssociatedActivities().add(acts.get(0)); @@ -165,24 +170,67 @@ public class MaxTimeInVehicleConstraintTest { } + @Test + public void insertingD2JustAfterStartShouldWork() { + ini(20d, 30, Double.MAX_VALUE); + + StateManager stateManager = new StateManager(vrp); + StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); + + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); + stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); + stateManager.informInsertionStarts(Arrays.asList(route), new ArrayList()); + + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); + JobInsertionContext c = new JobInsertionContext(route, d2, v, route.getDriver(), 0.); + List acts = vrp.getActivities(d2); + c.getAssociatedActivities().add(acts.get(0)); + + Assert.assertEquals("inserting d2 just after start should work", HardActivityConstraint.ConstraintsStatus.FULFILLED, constraint.fulfilled(c, route.getStart(), acts.get(0), route.getActivities().get(0), 0)); + } + + @Test + public void insertingD2AfterFirstDeliveryShouldWork() { + ini(20d, 30, Double.MAX_VALUE); + + StateManager stateManager = new StateManager(vrp); + StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); + + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); + stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); + stateManager.informInsertionStarts(Arrays.asList(route), new ArrayList()); + + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); + JobInsertionContext c = new JobInsertionContext(route, d2, v, route.getDriver(), 0.); + List acts = vrp.getActivities(d2); + c.getAssociatedActivities().add(acts.get(0)); + + + Assert.assertEquals("inserting d2 after first delivery should work", HardActivityConstraint.ConstraintsStatus.FULFILLED, constraint.fulfilled(c, route.getActivities().get(0), acts.get(0), route.getActivities().get(1), 10)); + } + @Test public void insertingDeliveryInBetweenShipmentShouldFail(){ ini(20d, 30, Double.MAX_VALUE); StateManager stateManager = new StateManager(vrp); StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager,latestStartId,vrp.getTransportCosts(),vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(route),new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(),vrp.getActivityCosts() , latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(route,d2,v,route.getDriver(),0.); List acts = vrp.getActivities(d2); c.getAssociatedActivities().add(acts.get(0)); - Assert.assertEquals("inserting d2 just after start should work", HardActivityConstraint.ConstraintsStatus.FULFILLED, constraint.fulfilled(c, route.getStart(), acts.get(0), route.getActivities().get(0), 0)); - Assert.assertEquals("inserting d2 after first delivery should work", HardActivityConstraint.ConstraintsStatus.FULFILLED, constraint.fulfilled(c, route.getActivities().get(0), acts.get(0), route.getActivities().get(1), 10)); Assert.assertEquals("inserting d2 between pickup and delivery shipment should fail due to max-in-vehicle constraint of shipment", HardActivityConstraint.ConstraintsStatus.NOT_FULFILLED, constraint.fulfilled(c, route.getActivities().get(1), acts.get(0), route.getActivities().get(2), 20)); Assert.assertEquals("inserting d2 at end should fail", HardActivityConstraint.ConstraintsStatus.NOT_FULFILLED, constraint.fulfilled(c, route.getActivities().get(2), acts.get(0), route.getEnd(), 40)); } @@ -197,12 +245,14 @@ public class MaxTimeInVehicleConstraintTest { StateManager stateManager = new StateManager(vrp); StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager,latestStartId,vrp.getTransportCosts(), vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(r),new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(),vrp.getActivityCosts() , latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(r, s1,v,r.getDriver(),0.); List acts = vrp.getActivities(s1); c.getAssociatedActivities().add(acts.get(0)); @@ -223,12 +273,14 @@ public class MaxTimeInVehicleConstraintTest { StateManager stateManager = new StateManager(vrp); StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(r), new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(r, s1, v, r.getDriver(), 0.); List acts = vrp.getActivities(s1); c.getAssociatedActivities().add(acts.get(0)); @@ -250,12 +302,14 @@ public class MaxTimeInVehicleConstraintTest { StateManager stateManager = new StateManager(vrp); StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(r), new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(r, s1, v, r.getDriver(), 0.); List acts = vrp.getActivities(s1); c.getAssociatedActivities().add(acts.get(0)); @@ -308,12 +362,14 @@ public class MaxTimeInVehicleConstraintTest { StateManager stateManager = new StateManager(vrp); StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager,latestStartId,vrp.getTransportCosts(), vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(r),new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(),vrp.getActivityCosts() , latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(r, s1,v,r.getDriver(),0.); List acts = vrp.getActivities(s1); c.getAssociatedActivities().add(acts.get(0)); @@ -337,14 +393,16 @@ public class MaxTimeInVehicleConstraintTest { StateManager stateManager = new StateManager(vrp); StateId latestStartId = stateManager.createStateId("latest-start-id"); + StateId openJobsId = stateManager.createStateId("open-jobs-id"); Map maxTimes = new HashMap<>(); maxTimes.put("s1",25d); - UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager,latestStartId,vrp.getTransportCosts(), vrp.getActivityCosts()); + UpdateMaxTimeInVehicle updater = new UpdateMaxTimeInVehicle(stateManager, latestStartId, vrp.getTransportCosts(), vrp.getActivityCosts(), openJobsId); stateManager.addStateUpdater(updater); + stateManager.addStateUpdater(new UpdateActivityTimes(vrp.getTransportCosts(), vrp.getActivityCosts())); stateManager.informInsertionStarts(Arrays.asList(r),new ArrayList()); - MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(),vrp.getActivityCosts() , latestStartId, stateManager, vrp); + MaxTimeInVehicleConstraint constraint = new MaxTimeInVehicleConstraint(vrp.getTransportCosts(), vrp.getActivityCosts(), latestStartId, stateManager, vrp, openJobsId); JobInsertionContext c = new JobInsertionContext(r, s1,v,r.getDriver(),0.); List acts = vrp.getActivities(s1); c.getAssociatedActivities().add(acts.get(0)); diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTrackerTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTrackerTest.java index 7b975e9a..0b8f639f 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTrackerTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/util/UnassignedJobReasonTrackerTest.java @@ -134,6 +134,19 @@ public class UnassignedJobReasonTrackerTest { Assert.assertEquals(4, reasonTracker.getMostLikelyReasonCode(solution.getUnassignedJobs().iterator().next().getId())); } + @Test + public void getMostLikelyTest() { + Frequency frequency = new Frequency(); + frequency.addValue("a"); + frequency.addValue("b"); + frequency.addValue("a"); + frequency.addValue("a"); + frequency.addValue("a"); + frequency.addValue("a"); + frequency.addValue("a"); + Assert.assertEquals("a", UnassignedJobReasonTracker.getMostLikelyFailedConstraintName(frequency)); + } + @Test public void testFreq() { Frequency frequency = new Frequency();