diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModule.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModule.java index 6eb48c27..5752a6a5 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModule.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModule.java @@ -28,9 +28,7 @@ import com.graphhopper.jsprit.core.problem.job.Job; import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution; import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.*; public class RuinAndRecreateModule implements SearchStrategyModule { @@ -54,6 +52,9 @@ public class RuinAndRecreateModule implements SearchStrategyModule { Set ruinedJobSet = new HashSet<>(); ruinedJobSet.addAll(ruinedJobs); ruinedJobSet.addAll(vrpSolution.getUnassignedJobs()); + + removeEmptyRoutes(vrpSolution.getRoutes()); + Collection unassignedJobs = insertion.insertJobs(vrpSolution.getRoutes(), ruinedJobSet); vrpSolution.getUnassignedJobs().clear(); vrpSolution.getUnassignedJobs().addAll(getUnassignedJobs(vrpSolution, unassignedJobs)); @@ -61,6 +62,17 @@ public class RuinAndRecreateModule implements SearchStrategyModule { return vrpSolution; } + static void removeEmptyRoutes(Collection routes) { + final Iterator iterator = routes.iterator(); + List emptyRoutes = new ArrayList<>(); + while (iterator.hasNext()) { + final VehicleRoute route = iterator.next(); + if (route.isEmpty()) + emptyRoutes.add(route); + } + routes.removeAll(emptyRoutes); + } + static Set getUnassignedJobs(VehicleRoutingProblemSolution vrpSolution, Collection unassignedJobs) { final Set unassigned = new HashSet<>(); for (Job job : unassignedJobs) { diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionStrategy.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionStrategy.java index 35f36815..c45079fc 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionStrategy.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/AbstractInsertionStrategy.java @@ -23,6 +23,7 @@ import com.graphhopper.jsprit.core.algorithm.recreate.listener.InsertionListener import com.graphhopper.jsprit.core.algorithm.recreate.listener.InsertionListeners; import com.graphhopper.jsprit.core.problem.VehicleRoutingProblem; import com.graphhopper.jsprit.core.problem.driver.Driver; +import com.graphhopper.jsprit.core.problem.job.Break; import com.graphhopper.jsprit.core.problem.job.Job; import com.graphhopper.jsprit.core.problem.solution.route.VehicleRoute; import com.graphhopper.jsprit.core.problem.vehicle.Vehicle; @@ -132,4 +133,17 @@ public abstract class AbstractInsertionStrategy implements InsertionStrategy { iData.setInsertionCost(iData.getInsertionCost() + iData.getSelectedVehicle().getType().getVehicleCostParams().fix); } + + protected void insertBreak(JobInsertionCostsCalculator jobInsertionCostsCalculator, List badJobs, VehicleRoute route, InsertionData iDataJob) { + if (iDataJob.getSelectedVehicle() != null && iDataJob.getSelectedVehicle().getBreak() != null) { + final Break aBreak = iDataJob.getSelectedVehicle().getBreak(); + InsertionData iData = jobInsertionCostsCalculator.getInsertionData(route, aBreak, iDataJob.getSelectedVehicle(), iDataJob.getSelectedVehicle().getEarliestDeparture(), iDataJob.getSelectedDriver(), Double.MAX_VALUE); + if (iData instanceof InsertionData.NoInsertionFound) { + badJobs.add(aBreak); + } else { + insertJob(aBreak, iData, route); + } + } + } + } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertion.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertion.java index e34474d1..18f78b45 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertion.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertion.java @@ -84,9 +84,12 @@ public final class BestInsertion extends AbstractInsertionStrategy { } VehicleRoute newRoute = VehicleRoute.emptyRoute(); InsertionData newIData = bestInsertionCostCalculator.getInsertionData(newRoute, unassignedJob, NO_NEW_VEHICLE_YET, NO_NEW_DEPARTURE_TIME_YET, NO_NEW_DRIVER_YET, bestInsertionCost); + + boolean isNewRoute = false; if (!(newIData instanceof InsertionData.NoInsertionFound)) { updateNewRouteInsertionData(newIData); if (newIData.getInsertionCost() < bestInsertionCost + noiseMaker.makeNoise()) { + isNewRoute = true; bestInsertion = new Insertion(newRoute, newIData); vehicleRoutes.add(newRoute); } @@ -96,8 +99,14 @@ public final class BestInsertion extends AbstractInsertionStrategy { if (bestInsertion == null) { badJobs.add(unassignedJob); markUnassigned(unassignedJob, empty.getFailedConstraintNames()); + } else { + insertJob(unassignedJob, bestInsertion.getInsertionData(), bestInsertion.getRoute()); + + if (isNewRoute || !bestInsertion.getRoute().getVehicle().getId().equals(bestInsertion.getInsertionData().getSelectedVehicle().getId())) { + insertBreak(bestInsertionCostCalculator, badJobs, bestInsertion.getRoute(), bestInsertion.getInsertionData()); + } } - else insertJob(unassignedJob, bestInsertion.getInsertionData(), bestInsertion.getRoute()); + } return badJobs; } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertionConcurrent.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertionConcurrent.java index ffb7ae92..961dcd47 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertionConcurrent.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/BestInsertionConcurrent.java @@ -139,7 +139,9 @@ public final class BestInsertionConcurrent extends AbstractInsertionStrategy { VehicleRoute newRoute = VehicleRoute.emptyRoute(); InsertionData newIData = bestInsertionCostCalculator.getInsertionData(newRoute, unassignedJob, NO_NEW_VEHICLE_YET, NO_NEW_DEPARTURE_TIME_YET, NO_NEW_DRIVER_YET, bestInsertionCost); updateNewRouteInsertionData(newIData); + boolean isNewRoute = false; if (newIData.getInsertionCost() < bestInsertionCost) { + isNewRoute = true; bestInsertion = new Insertion(newRoute, newIData); vehicleRoutes.add(newRoute); batches.get(random.nextInt(batches.size())).routes.add(newRoute); @@ -148,7 +150,13 @@ public final class BestInsertionConcurrent extends AbstractInsertionStrategy { badJobs.add(unassignedJob); markUnassigned(unassignedJob, failedConstraintNames); } - else insertJob(unassignedJob, bestInsertion.getInsertionData(), bestInsertion.getRoute()); + else { + insertJob(unassignedJob, bestInsertion.getInsertionData(), bestInsertion.getRoute()); + + if (isNewRoute || !bestInsertion.getRoute().getVehicle().getId().equals(bestInsertion.getInsertionData().getSelectedVehicle().getId())) { + insertBreak(bestInsertionCostCalculator, badJobs, bestInsertion.getRoute(), bestInsertion.getInsertionData()); + } + } } return badJobs; } diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertion.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertion.java index 1470676d..71d4d4a1 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertion.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertion.java @@ -111,11 +111,16 @@ public class RegretInsertion extends AbstractInsertionStrategy { List badJobList = new ArrayList<>(); ScoredJob bestScoredJob = nextJob(routes, unassignedJobList, badJobList); if (bestScoredJob != null) { + final VehicleRoute route = bestScoredJob.getRoute(); if (bestScoredJob.isNewRoute()) { - routes.add(bestScoredJob.getRoute()); + routes.add(route); } - insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); + insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), route); jobs.remove(bestScoredJob.getJob()); + + if (bestScoredJob.isNewRoute() || !route.getVehicle().getId().equals(bestScoredJob.getInsertionData().getSelectedVehicle().getId())) { + insertBreak(insertionCostsCalculator, badJobs, route, bestScoredJob.getInsertionData()); + } } for (ScoredJob bad : badJobList) { Job unassigned = bad.getJob(); diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java index b71ac5ab..0747b96c 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java @@ -120,6 +120,10 @@ public class RegretInsertionConcurrent extends AbstractInsertionStrategy { } insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); jobs.remove(bestScoredJob.getJob()); + + if (bestScoredJob.isNewRoute() || !bestScoredJob.getRoute().getVehicle().getId().equals(bestScoredJob.getInsertionData().getSelectedVehicle().getId())) { + insertBreak(insertionCostsCalculator, badJobs, bestScoredJob.getRoute(), bestScoredJob.getInsertionData()); + } } for (ScoredJob bad : badJobList) { Job unassigned = bad.getJob(); diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrentFast.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrentFast.java index be4f5d11..ebfffa00 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrentFast.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionConcurrentFast.java @@ -156,6 +156,10 @@ public class RegretInsertionConcurrentFast extends AbstractInsertionStrategy { } insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); jobs.remove(bestScoredJob.getJob()); + + if (bestScoredJob.isNewRoute() || !bestScoredJob.getRoute().getVehicle().getId().equals(bestScoredJob.getInsertionData().getSelectedVehicle().getId())) { + insertBreak(insertionCostsCalculator, badJobs, bestScoredJob.getRoute(), bestScoredJob.getInsertionData()); + } lastModified = bestScoredJob.getRoute(); } else lastModified = null; diff --git a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionFast.java b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionFast.java index d805e55d..51e160e0 100644 --- a/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionFast.java +++ b/jsprit-core/src/main/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionFast.java @@ -153,6 +153,10 @@ public class RegretInsertionFast extends AbstractInsertionStrategy { } insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); jobs.remove(bestScoredJob.getJob()); + + if (bestScoredJob.isNewRoute() || !bestScoredJob.getRoute().getVehicle().getId().equals(bestScoredJob.getInsertionData().getSelectedVehicle().getId())) { + insertBreak(insertionCostsCalculator, badJobs, bestScoredJob.getRoute(), bestScoredJob.getInsertionData()); + } lastModified = bestScoredJob.getRoute(); } else lastModified = null; diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModuleTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModuleTest.java index 0adec8a6..91ad8872 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModuleTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModuleTest.java @@ -11,7 +11,9 @@ import org.junit.Test; import java.util.*; import static com.graphhopper.jsprit.core.algorithm.module.RuinAndRecreateModule.getUnassignedJobs; +import static com.graphhopper.jsprit.core.algorithm.module.RuinAndRecreateModule.removeEmptyRoutes; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -39,4 +41,22 @@ public class RuinAndRecreateModuleTest { when(tourActivities.servesJob(aBreak)).thenReturn(false); assertEquals(1, getUnassignedJobs(new VehicleRoutingProblemSolution(routes, 212), unassigned).size()); } + + + @Test + public void testEmptyRoutesRemoved() { + List routes = new ArrayList<>(); + VehicleRoute vehicleRoute1 = mock(VehicleRoute.class); + when(vehicleRoute1.isEmpty()).thenReturn(false); + VehicleRoute vehicleRoute2 = mock(VehicleRoute.class); + when(vehicleRoute2.isEmpty()).thenReturn(true); + + routes.add(vehicleRoute1); + routes.add(vehicleRoute2); + + removeEmptyRoutes(routes); + + assertEquals(routes.size(), 1); + assertTrue(routes.contains(vehicleRoute1)); + } } diff --git a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionTest.java b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionTest.java index fbdf9008..511cd57c 100644 --- a/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionTest.java +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/recreate/RegretInsertionTest.java @@ -30,6 +30,7 @@ import com.graphhopper.jsprit.core.problem.constraint.ConstraintManager; import com.graphhopper.jsprit.core.problem.constraint.DependencyType; import com.graphhopper.jsprit.core.problem.constraint.HardRouteConstraint; import com.graphhopper.jsprit.core.problem.driver.Driver; +import com.graphhopper.jsprit.core.problem.job.Break; import com.graphhopper.jsprit.core.problem.job.Job; import com.graphhopper.jsprit.core.problem.job.Service; import com.graphhopper.jsprit.core.problem.job.Shipment; @@ -37,6 +38,7 @@ import com.graphhopper.jsprit.core.problem.misc.JobInsertionContext; 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.ActivityVisitor; +import com.graphhopper.jsprit.core.problem.solution.route.activity.BreakActivity; import com.graphhopper.jsprit.core.problem.solution.route.activity.TourActivity; import com.graphhopper.jsprit.core.problem.vehicle.*; import com.graphhopper.jsprit.core.util.Coordinate; @@ -46,6 +48,11 @@ import org.junit.Test; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; +import java.util.UUID; + +import static com.graphhopper.jsprit.core.algorithm.box.Jsprit.Parameter.BREAK_SCHEDULING; +import static org.junit.Assert.assertTrue; public class RegretInsertionTest { @@ -125,6 +132,44 @@ public class RegretInsertionTest { Assert.assertEquals(2, solution.getRoutes().size()); } + @Test + public void solutionHaveToBeWithBreak() { + Service s1 = Service.Builder.newInstance("s1").setLocation(Location.newInstance(0, 10)).addTimeWindow(0, 50).build(); + Service s2 = Service.Builder.newInstance("s2").setLocation(Location.newInstance(0, -10)).addTimeWindow(0, 50).build(); + + VehicleImpl v1 = VehicleImpl.Builder.newInstance("v1").setStartLocation(Location.newInstance(0, 5)) + .setBreak(Break.Builder.newInstance(UUID.randomUUID().toString()).addTimeWindow(0, 10).build()).build(); + VehicleImpl v2 = VehicleImpl.Builder.newInstance("v2").setStartLocation(Location.newInstance(0, -5)) + .setBreak(Break.Builder.newInstance(UUID.randomUUID().toString()).addTimeWindow(0, 10).build()).build(); + final VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(s1).addJob(s2) + .addVehicle(v1).addVehicle(v2).setFleetSize(VehicleRoutingProblem.FleetSize.FINITE).build(); + + optimizeAndValidate(vrp, Jsprit.Parameter.FAST_REGRET); + } + + private void optimizeAndValidate(VehicleRoutingProblem vrp, Jsprit.Parameter insertionStrategy) { + StateManager stateManager = new StateManager(vrp); + ConstraintManager constraintManager = new ConstraintManager(vrp,stateManager); + VehicleRoutingAlgorithm vra = Jsprit.Builder.newInstance(vrp) + .setProperty(BREAK_SCHEDULING, Boolean.FALSE.toString()) + .addCoreStateAndConstraintStuff(true) + .setProperty(insertionStrategy, Boolean.TRUE.toString()) + .setStateAndConstraintManager(stateManager, constraintManager).buildAlgorithm(); + + VehicleRoutingProblemSolution solution = Solutions.bestOf(vra.searchSolutions()); + + Assert.assertEquals(2, solution.getRoutes().size()); + final Iterator iterator = solution.getRoutes().iterator(); + while (iterator.hasNext()) { + final VehicleRoute route = iterator.next(); + boolean hasBreak = false; + for (TourActivity activity : route.getActivities()) + if (activity instanceof BreakActivity) + hasBreak = true; + assertTrue(hasBreak); + } + } + static class JobInRouteUpdater implements StateUpdater, ActivityVisitor { private StateManager stateManager;