mirror of
https://github.com/graphhopper/jsprit.git
synced 2020-01-24 07:45:05 +01:00
add concurrent regret
This commit is contained in:
parent
b7a35a26b0
commit
edc3690621
4 changed files with 312 additions and 14 deletions
|
|
@ -167,8 +167,13 @@ public class InsertionBuilder {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if(strategy.equals(Strategy.REGRET)){
|
else if(strategy.equals(Strategy.REGRET)){
|
||||||
|
if (executor == null) {
|
||||||
insertion = new RegretInsertion(costCalculator, vrp);
|
insertion = new RegretInsertion(costCalculator, vrp);
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
insertion = new RegretInsertionConcurrent(costCalculator,vrp,executor);
|
||||||
|
}
|
||||||
|
}
|
||||||
else throw new IllegalStateException("you should never get here");
|
else throw new IllegalStateException("you should never get here");
|
||||||
for(InsertionListener l : iListeners) insertion.addListener(l);
|
for(InsertionListener l : iListeners) insertion.addListener(l);
|
||||||
return insertion;
|
return insertion;
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,8 @@ package jsprit.core.algorithm.recreate;
|
||||||
import jsprit.core.problem.VehicleRoutingProblem;
|
import jsprit.core.problem.VehicleRoutingProblem;
|
||||||
import jsprit.core.problem.job.Job;
|
import jsprit.core.problem.job.Job;
|
||||||
import jsprit.core.problem.job.Service;
|
import jsprit.core.problem.job.Service;
|
||||||
|
import jsprit.core.problem.job.Shipment;
|
||||||
import jsprit.core.problem.solution.route.VehicleRoute;
|
import jsprit.core.problem.solution.route.VehicleRoute;
|
||||||
import jsprit.core.problem.vehicle.Vehicle;
|
|
||||||
import org.apache.logging.log4j.LogManager;
|
import org.apache.logging.log4j.LogManager;
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
|
@ -111,6 +111,8 @@ public class RegretInsertion extends AbstractInsertionStrategy {
|
||||||
|
|
||||||
private double depotDistance_param = + 0.1;
|
private double depotDistance_param = + 0.1;
|
||||||
|
|
||||||
|
private double minTimeWindowScore = - 100000;
|
||||||
|
|
||||||
public DefaultScorer(VehicleRoutingProblem vrp) {
|
public DefaultScorer(VehicleRoutingProblem vrp) {
|
||||||
this.vrp = vrp;
|
this.vrp = vrp;
|
||||||
}
|
}
|
||||||
|
|
@ -121,24 +123,50 @@ public class RegretInsertion extends AbstractInsertionStrategy {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public double score(InsertionData best, Job job) {
|
public double score(InsertionData best, Job job) {
|
||||||
|
double score;
|
||||||
double avgDepotDistance = getAvgDistance(best.getSelectedVehicle(),job);
|
if(job instanceof Service){
|
||||||
|
score = scoreService(best, job);
|
||||||
return Math.max(tw_param * (((Service)job).getTimeWindow().getEnd() - ((Service)job).getTimeWindow().getStart()),-100000) +
|
}
|
||||||
depotDistance_param * avgDepotDistance;
|
else if(job instanceof Shipment){
|
||||||
|
score = scoreShipment(best,job);
|
||||||
|
}
|
||||||
|
else throw new IllegalStateException("not supported");
|
||||||
|
return score;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double getAvgDistance(Vehicle vehicle, Job job) {
|
private double scoreShipment(InsertionData best, Job job) {
|
||||||
double distance = vrp.getTransportCosts().getTransportCost(vehicle.getStartLocationId(),((Service)job).getLocationId(),0.,null,vehicle);
|
Shipment shipment = (Shipment)job;
|
||||||
if(vehicle.isReturnToDepot() && !vehicle.getStartLocationId().equals(vehicle.getEndLocationId())){
|
double maxDepotDistance_1 = Math.max(
|
||||||
distance = (distance + vrp.getTransportCosts().getTransportCost(vehicle.getEndLocationId(),((Service)job).getLocationId(),0.,null,vehicle))/2.;
|
getDistance(best.getSelectedVehicle().getStartLocationId(),shipment.getPickupLocationId()),
|
||||||
|
getDistance(best.getSelectedVehicle().getStartLocationId(),shipment.getDeliveryLocationId())
|
||||||
|
);
|
||||||
|
double maxDepotDistance_2 = Math.max(
|
||||||
|
getDistance(best.getSelectedVehicle().getEndLocationId(),shipment.getPickupLocationId()),
|
||||||
|
getDistance(best.getSelectedVehicle().getEndLocationId(),shipment.getDeliveryLocationId())
|
||||||
|
);
|
||||||
|
double maxDepotDistance = Math.max(maxDepotDistance_1,maxDepotDistance_2);
|
||||||
|
double minTimeToOperate = Math.min(shipment.getPickupTimeWindow().getEnd()-shipment.getPickupTimeWindow().getStart(),
|
||||||
|
shipment.getDeliveryTimeWindow().getEnd()-shipment.getDeliveryTimeWindow().getStart());
|
||||||
|
return Math.max(tw_param * minTimeToOperate,minTimeWindowScore) + depotDistance_param * maxDepotDistance;
|
||||||
}
|
}
|
||||||
return distance;
|
|
||||||
|
private double scoreService(InsertionData best, Job job) {
|
||||||
|
double maxDepotDistance = Math.max(
|
||||||
|
getDistance(best.getSelectedVehicle().getStartLocationId(), ((Service) job).getLocationId()),
|
||||||
|
getDistance(best.getSelectedVehicle().getEndLocationId(), ((Service) job).getLocationId())
|
||||||
|
);
|
||||||
|
return Math.max(tw_param * (((Service)job).getTimeWindow().getEnd() - ((Service)job).getTimeWindow().getStart()),minTimeWindowScore) +
|
||||||
|
depotDistance_param * maxDepotDistance;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private double getDistance(String loc1, String loc2) {
|
||||||
|
return vrp.getTransportCosts().getTransportCost(loc1,loc2,0.,null,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "[name=timeWindowScorer][scoringParam="+tw_param+"]";
|
return "[name=defaultScorer][twParam="+tw_param+"][depotDistanceParam=" + depotDistance_param + "]";
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,220 @@
|
||||||
|
/*******************************************************************************
|
||||||
|
* Copyright (C) 2014 Stefan Schroeder
|
||||||
|
*
|
||||||
|
* This library is free software; you can redistribute it and/or
|
||||||
|
* modify it under the terms of the GNU Lesser General Public
|
||||||
|
* License as published by the Free Software Foundation; either
|
||||||
|
* version 3.0 of the License, or (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This library is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||||
|
* Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public
|
||||||
|
* License along with this library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
******************************************************************************/
|
||||||
|
|
||||||
|
package jsprit.core.algorithm.recreate;
|
||||||
|
|
||||||
|
import jsprit.core.algorithm.recreate.RegretInsertion.DefaultScorer;
|
||||||
|
import jsprit.core.algorithm.recreate.RegretInsertion.ScoredJob;
|
||||||
|
import jsprit.core.algorithm.recreate.RegretInsertion.ScoringFunction;
|
||||||
|
import jsprit.core.problem.VehicleRoutingProblem;
|
||||||
|
import jsprit.core.problem.job.Job;
|
||||||
|
import jsprit.core.problem.solution.route.VehicleRoute;
|
||||||
|
import org.apache.logging.log4j.LogManager;
|
||||||
|
import org.apache.logging.log4j.Logger;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Insertion based on regret approach.
|
||||||
|
*
|
||||||
|
* <p>Basically calculates the insertion cost of the firstBest and the secondBest alternative. The score is then calculated as difference
|
||||||
|
* between secondBest and firstBest, plus additional scoring variables that can defined in this.ScoringFunction.
|
||||||
|
* The idea is that if the cost of the secondBest alternative is way higher than the first best, it seems to be important to insert this
|
||||||
|
* customer immediatedly. If difference is not that high, it might not impact solution if this customer is inserted later.
|
||||||
|
*
|
||||||
|
* @author stefan schroeder
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class RegretInsertionConcurrent extends AbstractInsertionStrategy {
|
||||||
|
|
||||||
|
|
||||||
|
private static Logger logger = LogManager.getLogger(RegretInsertionConcurrent.class);
|
||||||
|
|
||||||
|
private ScoringFunction scoringFunction;
|
||||||
|
|
||||||
|
private final JobInsertionCostsCalculator insertionCostsCalculator;
|
||||||
|
|
||||||
|
private final ExecutorCompletionService<ScoredJob> completionService;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the scoring function.
|
||||||
|
*
|
||||||
|
* <p>By default, the this.TimeWindowScorer is used.
|
||||||
|
*
|
||||||
|
* @param scoringFunction to score
|
||||||
|
*/
|
||||||
|
public void setScoringFunction(ScoringFunction scoringFunction) {
|
||||||
|
this.scoringFunction = scoringFunction;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RegretInsertionConcurrent(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem, ExecutorService executorService) {
|
||||||
|
super(vehicleRoutingProblem);
|
||||||
|
this.scoringFunction = new DefaultScorer(vehicleRoutingProblem);
|
||||||
|
this.insertionCostsCalculator = jobInsertionCalculator;
|
||||||
|
this.vrp = vehicleRoutingProblem;
|
||||||
|
completionService = new ExecutorCompletionService<ScoredJob>(executorService);
|
||||||
|
logger.info("initialise " + this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "[name=regretInsertion][additionalScorer="+scoringFunction+"]";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs insertion.
|
||||||
|
*
|
||||||
|
* <p>Before inserting a job, all unassigned jobs are scored according to its best- and secondBest-insertion plus additional scoring variables.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public Collection<Job> insertUnassignedJobs(Collection<VehicleRoute> routes, Collection<Job> unassignedJobs) {
|
||||||
|
List<Job> badJobs = new ArrayList<Job>(unassignedJobs.size());
|
||||||
|
List<Job> jobs = new ArrayList<Job>(unassignedJobs);
|
||||||
|
|
||||||
|
while (!jobs.isEmpty()) {
|
||||||
|
List<Job> unassignedJobList = new ArrayList<Job>(jobs);
|
||||||
|
ScoredJob bestScoredJob = nextJob(routes, unassignedJobList);
|
||||||
|
Job handledJob;
|
||||||
|
if(bestScoredJob == null){
|
||||||
|
handledJob = unassignedJobList.get(0);
|
||||||
|
badJobs.add(handledJob);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if(bestScoredJob.isNewRoute()){
|
||||||
|
routes.add(bestScoredJob.getRoute());
|
||||||
|
}
|
||||||
|
insertJob(bestScoredJob.getJob(),bestScoredJob.getInsertionData(),bestScoredJob.getRoute());
|
||||||
|
handledJob = bestScoredJob.getJob();
|
||||||
|
}
|
||||||
|
jobs.remove(handledJob);
|
||||||
|
}
|
||||||
|
return badJobs;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScoredJob nextJob(final Collection<VehicleRoute> routes, List<Job> unassignedJobList) {
|
||||||
|
ScoredJob bestScoredJob = null;
|
||||||
|
|
||||||
|
for (final Job unassignedJob : unassignedJobList) {
|
||||||
|
completionService.submit(new Callable<ScoredJob>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ScoredJob call() throws Exception {
|
||||||
|
return getScoredJob(routes, unassignedJob);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
for(int i=0; i < unassignedJobList.size(); i++){
|
||||||
|
Future<ScoredJob> fsj = completionService.take();
|
||||||
|
ScoredJob sJob = fsj.get();
|
||||||
|
if(sJob == null){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(bestScoredJob == null){
|
||||||
|
bestScoredJob = sJob;
|
||||||
|
}
|
||||||
|
else if(sJob.getScore() > bestScoredJob.getScore()){
|
||||||
|
bestScoredJob = sJob;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch(InterruptedException e){
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
catch (ExecutionException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
logger.error(e.getCause().toString());
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bestScoredJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScoredJob getScoredJob(Collection<VehicleRoute> routes, Job unassignedJob) {
|
||||||
|
InsertionData best = null;
|
||||||
|
InsertionData secondBest = null;
|
||||||
|
VehicleRoute bestRoute = null;
|
||||||
|
|
||||||
|
double benchmark = Double.MAX_VALUE;
|
||||||
|
for (VehicleRoute route : routes) {
|
||||||
|
if (secondBest != null) {
|
||||||
|
benchmark = secondBest.getInsertionCost();
|
||||||
|
}
|
||||||
|
InsertionData iData = insertionCostsCalculator.getInsertionData(route, unassignedJob, NO_NEW_VEHICLE_YET, NO_NEW_DEPARTURE_TIME_YET, NO_NEW_DRIVER_YET, benchmark);
|
||||||
|
if (iData instanceof InsertionData.NoInsertionFound) continue;
|
||||||
|
if (best == null) {
|
||||||
|
best = iData;
|
||||||
|
bestRoute = route;
|
||||||
|
} else if (iData.getInsertionCost() < best.getInsertionCost()) {
|
||||||
|
secondBest = best;
|
||||||
|
best = iData;
|
||||||
|
bestRoute = route;
|
||||||
|
} else if (secondBest == null || (iData.getInsertionCost() < secondBest.getInsertionCost())) {
|
||||||
|
secondBest = iData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VehicleRoute emptyRoute = VehicleRoute.emptyRoute();
|
||||||
|
InsertionData iData = insertionCostsCalculator.getInsertionData(emptyRoute, unassignedJob, NO_NEW_VEHICLE_YET, NO_NEW_DEPARTURE_TIME_YET, NO_NEW_DRIVER_YET, benchmark);
|
||||||
|
if (!(iData instanceof InsertionData.NoInsertionFound)) {
|
||||||
|
// iData = new InsertionData(iData.getInsertionCost()*1000.,iData.getPickupInsertionIndex(),iData.getDeliveryInsertionIndex(),iData.getSelectedVehicle(),iData.getSelectedDriver());
|
||||||
|
if (best == null) {
|
||||||
|
best = iData;
|
||||||
|
bestRoute = emptyRoute;
|
||||||
|
} else if (iData.getInsertionCost() < best.getInsertionCost()) {
|
||||||
|
secondBest = best;
|
||||||
|
best = iData;
|
||||||
|
bestRoute = emptyRoute;
|
||||||
|
} else if (secondBest == null || (iData.getInsertionCost() < secondBest.getInsertionCost())) {
|
||||||
|
secondBest = iData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double score = score(unassignedJob, best, secondBest);
|
||||||
|
ScoredJob scoredJob;
|
||||||
|
if(bestRoute == emptyRoute){
|
||||||
|
scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, true);
|
||||||
|
}
|
||||||
|
else scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, false);
|
||||||
|
return scoredJob;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private double score(Job unassignedJob, InsertionData best, InsertionData secondBest) {
|
||||||
|
if(best == null){
|
||||||
|
throw new IllegalStateException("cannot insert job " + unassignedJob.getId());
|
||||||
|
}
|
||||||
|
double score;
|
||||||
|
if(secondBest == null){
|
||||||
|
score = best.getInsertionCost() + scoringFunction.score(best,unassignedJob);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
score = (secondBest.getInsertionCost()-best.getInsertionCost()) + scoringFunction.score(best,unassignedJob);
|
||||||
|
}
|
||||||
|
return score;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -22,6 +22,7 @@ import jsprit.core.problem.VehicleRoutingProblem;
|
||||||
import jsprit.core.problem.driver.Driver;
|
import jsprit.core.problem.driver.Driver;
|
||||||
import jsprit.core.problem.job.Job;
|
import jsprit.core.problem.job.Job;
|
||||||
import jsprit.core.problem.job.Service;
|
import jsprit.core.problem.job.Service;
|
||||||
|
import jsprit.core.problem.job.Shipment;
|
||||||
import jsprit.core.problem.solution.route.VehicleRoute;
|
import jsprit.core.problem.solution.route.VehicleRoute;
|
||||||
import jsprit.core.problem.solution.route.activity.TourActivity;
|
import jsprit.core.problem.solution.route.activity.TourActivity;
|
||||||
import jsprit.core.problem.vehicle.Vehicle;
|
import jsprit.core.problem.vehicle.Vehicle;
|
||||||
|
|
@ -85,6 +86,50 @@ public class RegretInsertionTest {
|
||||||
Assert.assertTrue(position.isCorrect());
|
Assert.assertTrue(position.isCorrect());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shipment1ShouldBeAddedFirst(){
|
||||||
|
Shipment s1 = Shipment.Builder.newInstance("s1")
|
||||||
|
.setPickupLocationId("pick1")
|
||||||
|
.setPickupCoord(Coordinate.newInstance(-1, 10))
|
||||||
|
.setDeliveryCoord(Coordinate.newInstance(1, 10))
|
||||||
|
.setDeliveryLocationId("del1")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Shipment s2 = Shipment.Builder.newInstance("s2")
|
||||||
|
.setPickupCoord(Coordinate.newInstance(-1,20))
|
||||||
|
.setDeliveryCoord(Coordinate.newInstance(1, 20))
|
||||||
|
.setPickupLocationId("pick2")
|
||||||
|
.setDeliveryLocationId("del2")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
VehicleImpl v = VehicleImpl.Builder.newInstance("v").setStartLocationCoordinate(Coordinate.newInstance(0,0)).build();
|
||||||
|
final VehicleRoutingProblem vrp = VehicleRoutingProblem.Builder.newInstance().addJob(s1).addJob(s2).addVehicle(v).build();
|
||||||
|
|
||||||
|
JobInsertionCostsCalculator calculator = getShipmentCalculator(vrp);
|
||||||
|
RegretInsertion regretInsertion = new RegretInsertion(calculator,vrp);
|
||||||
|
Collection<VehicleRoute> routes = new ArrayList<VehicleRoute>();
|
||||||
|
|
||||||
|
CkeckJobSequence position = new CkeckJobSequence(1, s2);
|
||||||
|
regretInsertion.addListener(position);
|
||||||
|
regretInsertion.insertJobs(routes,vrp.getJobs().values());
|
||||||
|
Assert.assertTrue(position.isCorrect());
|
||||||
|
}
|
||||||
|
|
||||||
|
private JobInsertionCostsCalculator getShipmentCalculator(final VehicleRoutingProblem vrp) {
|
||||||
|
return new JobInsertionCostsCalculator() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InsertionData getInsertionData(VehicleRoute currentRoute, Job newJob, Vehicle newVehicle, double newVehicleDepartureTime, Driver newDriver, double bestKnownCosts) {
|
||||||
|
Vehicle vehicle = vrp.getVehicles().iterator().next();
|
||||||
|
if(newJob.getId().equals("s1")){
|
||||||
|
return new InsertionData(10,0,0,vehicle,newDriver);
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
return new InsertionData(20,0,0,vehicle,newDriver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
static class CkeckJobSequence implements BeforeJobInsertionListener {
|
static class CkeckJobSequence implements BeforeJobInsertionListener {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue