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 9c6e85cc..71facbc2 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 @@ -26,9 +26,7 @@ import com.graphhopper.jsprit.core.algorithm.ruin.listener.RuinListener; import com.graphhopper.jsprit.core.problem.job.Job; import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; +import java.util.*; public class RuinAndRecreateModule implements SearchStrategyModule { @@ -39,6 +37,12 @@ public class RuinAndRecreateModule implements SearchStrategyModule { private String moduleName; + private Random random = new Random(4711); + + private int minUnassignedJobsToBeReinserted = Integer.MAX_VALUE; + + private double proportionOfUnassignedJobsToBeReinserted = 1d; + public RuinAndRecreateModule(String moduleName, InsertionStrategy insertion, RuinStrategy ruin) { super(); this.insertion = insertion; @@ -46,16 +50,58 @@ public class RuinAndRecreateModule implements SearchStrategyModule { this.moduleName = moduleName; } + /** + * To make overall results reproducible, make sure this class is provided with the "global" random number generator. + * + * @param random + */ + public void setRandom(Random random) { + this.random = random; + } + + /** + * Minimum number of unassigned jobs that is reinserted in each iteration. + * + * @param minUnassignedJobsToBeReinserted + */ + public void setMinUnassignedJobsToBeReinserted(int minUnassignedJobsToBeReinserted) { + this.minUnassignedJobsToBeReinserted = minUnassignedJobsToBeReinserted; + } + + /** + * Proportion of unassigned jobs that is reinserted in each iteration. + * + * @param proportionOfUnassignedJobsToBeReinserted + */ + public void setProportionOfUnassignedJobsToBeReinserted(double proportionOfUnassignedJobsToBeReinserted) { + this.proportionOfUnassignedJobsToBeReinserted = proportionOfUnassignedJobsToBeReinserted; + } + @Override - public VehicleRoutingProblemSolution runAndGetSolution(VehicleRoutingProblemSolution vrpSolution) { - Collection ruinedJobs = ruin.ruin(vrpSolution.getRoutes()); - Set ruinedJobSet = new HashSet(); + public VehicleRoutingProblemSolution runAndGetSolution(VehicleRoutingProblemSolution previousVrpSolution) { + Collection ruinedJobs = ruin.ruin(previousVrpSolution.getRoutes()); + Set ruinedJobSet = new HashSet<>(); ruinedJobSet.addAll(ruinedJobs); - ruinedJobSet.addAll(vrpSolution.getUnassignedJobs()); - Collection unassignedJobs = insertion.insertJobs(vrpSolution.getRoutes(), ruinedJobSet); - vrpSolution.getUnassignedJobs().clear(); - vrpSolution.getUnassignedJobs().addAll(unassignedJobs); - return vrpSolution; + List stillUnassignedInThisIteration = new ArrayList<>(); + if (previousVrpSolution.getUnassignedJobs().size() < minUnassignedJobsToBeReinserted) { + ruinedJobSet.addAll(previousVrpSolution.getUnassignedJobs()); + } else { + int noUnassignedToBeInserted = Math.max(minUnassignedJobsToBeReinserted, (int) (previousVrpSolution.getUnassignedJobs().size() * proportionOfUnassignedJobsToBeReinserted)); + List jobList = new ArrayList<>(previousVrpSolution.getUnassignedJobs()); + Collections.shuffle(jobList, random); + for (int i = 0; i < noUnassignedToBeInserted; i++) { + ruinedJobSet.add(jobList.get(i)); + } + for (int i = noUnassignedToBeInserted; i < jobList.size(); i++) { + stillUnassignedInThisIteration.add(jobList.get(i)); + } + } + Collection unassignedJobs = insertion.insertJobs(previousVrpSolution.getRoutes(), ruinedJobSet); + previousVrpSolution.getUnassignedJobs().clear(); + previousVrpSolution.getUnassignedJobs().addAll(unassignedJobs); + previousVrpSolution.getUnassignedJobs().addAll(stillUnassignedInThisIteration); + VehicleRoutingProblemSolution newSolution = previousVrpSolution; + return newSolution; } 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 new file mode 100644 index 00000000..ceb6d0cc --- /dev/null +++ b/jsprit-core/src/test/java/com/graphhopper/jsprit/core/algorithm/module/RuinAndRecreateModuleTest.java @@ -0,0 +1,85 @@ +/* + * Licensed to GraphHopper GmbH under one or more contributor + * license agreements. See the NOTICE file distributed with this work for + * additional information regarding copyright ownership. + * + * GraphHopper GmbH licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except in + * compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.graphhopper.jsprit.core.algorithm.module; + +import com.graphhopper.jsprit.core.algorithm.recreate.InsertionStrategy; +import com.graphhopper.jsprit.core.algorithm.ruin.RuinStrategy; +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 org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.mockito.Mockito.mock; + +public class RuinAndRecreateModuleTest { + + @Test + public void initialNumOfUnassignedShouldWorkCorrectly() { + InsertionStrategy insertionStrategy = mock(InsertionStrategy.class); + RuinStrategy ruinStrategy = mock(RuinStrategy.class); + RuinAndRecreateModule module = new RuinAndRecreateModule("name", insertionStrategy, ruinStrategy); + Collection routes = new ArrayList<>(); + List unassigned = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + unassigned.add(mock(Job.class)); + } + VehicleRoutingProblemSolution previousSolution = new VehicleRoutingProblemSolution(routes, unassigned, 0); + VehicleRoutingProblemSolution newSolution = module.runAndGetSolution(previousSolution); + Assert.assertEquals(0, newSolution.getUnassignedJobs().size()); + } + + @Test + public void proportionOfUnassignedShouldWorkCorrectly() { + InsertionStrategy insertionStrategy = mock(InsertionStrategy.class); + RuinStrategy ruinStrategy = mock(RuinStrategy.class); + RuinAndRecreateModule module = new RuinAndRecreateModule("name", insertionStrategy, ruinStrategy); + module.setMinUnassignedJobsToBeReinserted(5); + module.setProportionOfUnassignedJobsToBeReinserted(0.01); + Collection routes = new ArrayList<>(); + List unassigned = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + unassigned.add(mock(Job.class)); + } + VehicleRoutingProblemSolution previousSolution = new VehicleRoutingProblemSolution(routes, unassigned, 0); + VehicleRoutingProblemSolution newSolution = module.runAndGetSolution(previousSolution); + Assert.assertEquals(15, newSolution.getUnassignedJobs().size()); + } + + @Test + public void proportionOfUnassignedShouldWorkCorrectly2() { + InsertionStrategy insertionStrategy = mock(InsertionStrategy.class); + RuinStrategy ruinStrategy = mock(RuinStrategy.class); + RuinAndRecreateModule module = new RuinAndRecreateModule("name", insertionStrategy, ruinStrategy); + module.setMinUnassignedJobsToBeReinserted(5); + module.setProportionOfUnassignedJobsToBeReinserted(0.5); + Collection routes = new ArrayList<>(); + List unassigned = new ArrayList<>(); + for (int i = 0; i < 20; i++) { + unassigned.add(mock(Job.class)); + } + VehicleRoutingProblemSolution previousSolution = new VehicleRoutingProblemSolution(routes, unassigned, 0); + VehicleRoutingProblemSolution newSolution = module.runAndGetSolution(previousSolution); + Assert.assertEquals(10, newSolution.getUnassignedJobs().size()); + } +}