mirror of
https://github.com/graphhopper/jsprit.git
synced 2020-01-24 07:45:05 +01:00
OPTI-644 tune termination condition (#85)
* Replacing the termination logic to reduce run-times with marginal effect on quality. * Replacing the termination logic to reduce run-times with marginal effect on quality. * Replacing the termination logic to reduce run-times with marginal effect on quality. * Replacing the termination logic to reduce run-times with marginal effect on quality. * Replacing the termination logic to reduce run-times with marginal effect on quality. * add unit tests
This commit is contained in:
parent
2a04804748
commit
8696f5ed25
4 changed files with 126 additions and 6 deletions
|
|
@ -164,7 +164,5 @@ public class SearchStrategy {
|
||||||
for (SearchStrategyModule module : searchStrategyModules) {
|
for (SearchStrategyModule module : searchStrategyModules) {
|
||||||
module.addModuleListener(moduleListener);
|
module.addModuleListener(moduleListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,9 +18,13 @@
|
||||||
package com.graphhopper.jsprit.core.algorithm.termination;
|
package com.graphhopper.jsprit.core.algorithm.termination;
|
||||||
|
|
||||||
import com.graphhopper.jsprit.core.algorithm.SearchStrategy;
|
import com.graphhopper.jsprit.core.algorithm.SearchStrategy;
|
||||||
|
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Terminates algorithm prematurely based on iterations without any improvement (i.e. new solution acceptance).
|
* Terminates algorithm prematurely based on iterations without any improvement (i.e. new solution acceptance).
|
||||||
|
|
@ -39,13 +43,28 @@ public class IterationWithoutImprovementTermination implements PrematureAlgorith
|
||||||
|
|
||||||
private int iterationsWithoutImprovement = 0;
|
private int iterationsWithoutImprovement = 0;
|
||||||
|
|
||||||
|
private List<Double> costs;
|
||||||
|
|
||||||
|
private List<Integer> unassignedJobsCount;
|
||||||
|
|
||||||
|
private double bestCost = Double.MAX_VALUE;
|
||||||
|
|
||||||
|
private double terminationByCostPercentage = 0.0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs termination.
|
* Constructs termination.
|
||||||
*
|
*
|
||||||
* @param noIterationsWithoutImprovement previous iterations without any improvement
|
* @param noIterationsWithoutImprovement previous iterations without any improvement
|
||||||
*/
|
*/
|
||||||
public IterationWithoutImprovementTermination(int noIterationsWithoutImprovement) {
|
public IterationWithoutImprovementTermination(int noIterationsWithoutImprovement) {
|
||||||
|
this(noIterationsWithoutImprovement, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IterationWithoutImprovementTermination(int noIterationsWithoutImprovement, double terminationByCostPercentage) {
|
||||||
this.noIterationWithoutImprovement = noIterationsWithoutImprovement;
|
this.noIterationWithoutImprovement = noIterationsWithoutImprovement;
|
||||||
|
this.terminationByCostPercentage = terminationByCostPercentage;
|
||||||
|
costs = new ArrayList<>();
|
||||||
|
unassignedJobsCount = new ArrayList<>();
|
||||||
log.debug("initialise " + this);
|
log.debug("initialise " + this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,10 +75,39 @@ public class IterationWithoutImprovementTermination implements PrematureAlgorith
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isPrematureBreak(SearchStrategy.DiscoveredSolution discoveredSolution) {
|
public boolean isPrematureBreak(SearchStrategy.DiscoveredSolution discoveredSolution) {
|
||||||
if (discoveredSolution.isAccepted()) iterationsWithoutImprovement = 0;
|
if(this.terminationByCostPercentage == 0.0)
|
||||||
else iterationsWithoutImprovement++;
|
return checkStrictTerminationCondition(discoveredSolution);
|
||||||
|
else
|
||||||
|
return checkPercentageTerminationCondition(discoveredSolution);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean checkStrictTerminationCondition(SearchStrategy.DiscoveredSolution discoveredSolution){
|
||||||
|
// The original logic that is counting the number of iterations without any change
|
||||||
|
if (discoveredSolution.isAccepted())
|
||||||
|
iterationsWithoutImprovement = 0;
|
||||||
|
else
|
||||||
|
iterationsWithoutImprovement++;
|
||||||
return (iterationsWithoutImprovement > noIterationWithoutImprovement);
|
return (iterationsWithoutImprovement > noIterationWithoutImprovement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkPercentageTerminationCondition(SearchStrategy.DiscoveredSolution discoveredSolution){
|
||||||
|
// The alternative logic that detects also very slow improvment
|
||||||
|
// On large tasks small improvments to the route may significantly increase the runtime
|
||||||
|
VehicleRoutingProblemSolution sol = discoveredSolution.getSolution();
|
||||||
|
|
||||||
|
double currentCost = sol.getCost();
|
||||||
|
bestCost = Math.min(currentCost, bestCost);
|
||||||
|
costs.add(bestCost);
|
||||||
|
|
||||||
|
int currentJobsUnassigned = sol.getUnassignedJobs().size();
|
||||||
|
unassignedJobsCount.add(currentJobsUnassigned);
|
||||||
|
|
||||||
|
int i = costs.size() - 1;
|
||||||
|
if (i < noIterationWithoutImprovement)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
boolean unassignedJobsEqual = (currentJobsUnassigned == unassignedJobsCount.get(i - noIterationWithoutImprovement));
|
||||||
|
boolean progressTooSlow = 100 * ((costs.get(i - noIterationWithoutImprovement) - currentCost) / currentCost) <= terminationByCostPercentage;
|
||||||
|
return (unassignedJobsEqual && progressTooSlow);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -131,7 +131,7 @@ public class SolutionPrinter {
|
||||||
out.format("+---------------+------------------------------------------+%n");
|
out.format("+---------------+------------------------------------------+%n");
|
||||||
out.printf("| indicator | value |%n");
|
out.printf("| indicator | value |%n");
|
||||||
out.format("+---------------+------------------------------------------+%n");
|
out.format("+---------------+------------------------------------------+%n");
|
||||||
out.format(leftAlignSolution, "costs", solution.getCost());
|
out.format(leftAlignSolution, "costs", Math.round(solution.getCost()));
|
||||||
out.format(leftAlignSolution, "noVehicles", solution.getRoutes().size());
|
out.format(leftAlignSolution, "noVehicles", solution.getRoutes().size());
|
||||||
out.format(leftAlignSolution, "unassgndJobs", solution.getUnassignedJobs().size());
|
out.format(leftAlignSolution, "unassgndJobs", solution.getUnassignedJobs().size());
|
||||||
out.format("+----------------------------------------------------------+%n");
|
out.format("+----------------------------------------------------------+%n");
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,13 @@ package com.graphhopper.jsprit.core.algorithm.termination;
|
||||||
|
|
||||||
|
|
||||||
import com.graphhopper.jsprit.core.algorithm.SearchStrategy;
|
import com.graphhopper.jsprit.core.algorithm.SearchStrategy;
|
||||||
import junit.framework.Assert;
|
import com.graphhopper.jsprit.core.problem.job.Job;
|
||||||
|
import com.graphhopper.jsprit.core.problem.solution.VehicleRoutingProblemSolution;
|
||||||
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
@ -76,4 +81,73 @@ public class IterationsWithoutImprovementTest {
|
||||||
}
|
}
|
||||||
Assert.assertEquals(150, terminatedAfter);
|
Assert.assertEquals(150, terminatedAfter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isPrematureBreakZeroPercentage() {
|
||||||
|
int maxIterations = 200;
|
||||||
|
IterationWithoutImprovementTermination termination = new IterationWithoutImprovementTermination(100, 0);
|
||||||
|
SearchStrategy.DiscoveredSolution discoveredSolution = mock(SearchStrategy.DiscoveredSolution.class);
|
||||||
|
|
||||||
|
int terminatedAfter = maxIterations;
|
||||||
|
for (int i = 0; i < maxIterations; i++) {
|
||||||
|
when(discoveredSolution.isAccepted()).thenReturn(i< 50 ? true : false);
|
||||||
|
if (termination.isPrematureBreak(discoveredSolution)) {
|
||||||
|
terminatedAfter = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertEquals(150, terminatedAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isPrematureBreakWithPercentageShouldBreak() {
|
||||||
|
int maxIterations = 200;
|
||||||
|
IterationWithoutImprovementTermination termination = new IterationWithoutImprovementTermination(10, 1.0);
|
||||||
|
SearchStrategy.DiscoveredSolution discoveredSolution = mock(SearchStrategy.DiscoveredSolution.class);
|
||||||
|
VehicleRoutingProblemSolution solution = mock(VehicleRoutingProblemSolution.class);
|
||||||
|
when(discoveredSolution.getSolution()).thenReturn(solution);
|
||||||
|
when(solution.getUnassignedJobs()).thenReturn(new ArrayList<Job>());
|
||||||
|
|
||||||
|
int terminatedAfter = maxIterations;
|
||||||
|
for (int i = 0; i < maxIterations; i++) {
|
||||||
|
|
||||||
|
when(solution.getCost()).thenReturn(i < 100 ? 100.0 - (0.1*i) : 40-((i-100)*0.01));
|
||||||
|
boolean terminate = termination.isPrematureBreak(discoveredSolution);
|
||||||
|
if (terminate) {
|
||||||
|
terminatedAfter = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertEquals(110, terminatedAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isPrematureBreakWithPercentageCostNotImprovedButUnassignedImproved() {
|
||||||
|
int maxIterations = 200;
|
||||||
|
IterationWithoutImprovementTermination termination = new IterationWithoutImprovementTermination(10, 1.0);
|
||||||
|
SearchStrategy.DiscoveredSolution discoveredSolution = mock(SearchStrategy.DiscoveredSolution.class);
|
||||||
|
VehicleRoutingProblemSolution solution = mock(VehicleRoutingProblemSolution.class);
|
||||||
|
Job job = mock(Job.class);
|
||||||
|
when(discoveredSolution.getSolution()).thenReturn(solution);
|
||||||
|
List<Job> unassignedJobs = new ArrayList<>();
|
||||||
|
for (int i = 0; i < 100; i++) {
|
||||||
|
unassignedJobs.add(job);
|
||||||
|
}
|
||||||
|
|
||||||
|
int terminatedAfter = maxIterations;
|
||||||
|
for (int i = 0; i < maxIterations; i++) {
|
||||||
|
when(solution.getCost()).thenReturn(100.0);
|
||||||
|
if (i <= 50){
|
||||||
|
unassignedJobs.remove(0);
|
||||||
|
}
|
||||||
|
when(solution.getUnassignedJobs()).thenReturn(unassignedJobs);
|
||||||
|
boolean terminate = termination.isPrematureBreak(discoveredSolution);
|
||||||
|
if (terminate) {
|
||||||
|
terminatedAfter = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertEquals(60, terminatedAfter);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue