From edc36906210eb9e2b8374b5dc3ee92dcbd7217f8 Mon Sep 17 00:00:00 2001 From: oblonski <4sschroeder@gmail.com> Date: Mon, 24 Nov 2014 11:41:49 +0100 Subject: [PATCH] add concurrent regret --- .../algorithm/recreate/InsertionBuilder.java | 7 +- .../algorithm/recreate/RegretInsertion.java | 54 +++-- .../recreate/RegretInsertionConcurrent.java | 220 ++++++++++++++++++ .../recreate/RegretInsertionTest.java | 45 ++++ 4 files changed, 312 insertions(+), 14 deletions(-) create mode 100644 jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertionBuilder.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertionBuilder.java index c9117119..f83f3357 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertionBuilder.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/InsertionBuilder.java @@ -167,7 +167,12 @@ public class InsertionBuilder { } } else if(strategy.equals(Strategy.REGRET)){ - insertion = new RegretInsertion(costCalculator, vrp); + if (executor == null) { + insertion = new RegretInsertion(costCalculator, vrp); + } + else { + insertion = new RegretInsertionConcurrent(costCalculator,vrp,executor); + } } else throw new IllegalStateException("you should never get here"); for(InsertionListener l : iListeners) insertion.addListener(l); diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java index 640f86d1..44840401 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertion.java @@ -20,8 +20,8 @@ package jsprit.core.algorithm.recreate; import jsprit.core.problem.VehicleRoutingProblem; import jsprit.core.problem.job.Job; import jsprit.core.problem.job.Service; +import jsprit.core.problem.job.Shipment; import jsprit.core.problem.solution.route.VehicleRoute; -import jsprit.core.problem.vehicle.Vehicle; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -111,6 +111,8 @@ public class RegretInsertion extends AbstractInsertionStrategy { private double depotDistance_param = + 0.1; + private double minTimeWindowScore = - 100000; + public DefaultScorer(VehicleRoutingProblem vrp) { this.vrp = vrp; } @@ -121,24 +123,50 @@ public class RegretInsertion extends AbstractInsertionStrategy { @Override public double score(InsertionData best, Job job) { - - double avgDepotDistance = getAvgDistance(best.getSelectedVehicle(),job); - - return Math.max(tw_param * (((Service)job).getTimeWindow().getEnd() - ((Service)job).getTimeWindow().getStart()),-100000) + - depotDistance_param * avgDepotDistance; + double score; + if(job instanceof Service){ + score = scoreService(best, job); + } + else if(job instanceof Shipment){ + score = scoreShipment(best,job); + } + else throw new IllegalStateException("not supported"); + return score; } - private double getAvgDistance(Vehicle vehicle, Job job) { - double distance = vrp.getTransportCosts().getTransportCost(vehicle.getStartLocationId(),((Service)job).getLocationId(),0.,null,vehicle); - if(vehicle.isReturnToDepot() && !vehicle.getStartLocationId().equals(vehicle.getEndLocationId())){ - distance = (distance + vrp.getTransportCosts().getTransportCost(vehicle.getEndLocationId(),((Service)job).getLocationId(),0.,null,vehicle))/2.; - } - return distance; + private double scoreShipment(InsertionData best, Job job) { + Shipment shipment = (Shipment)job; + double maxDepotDistance_1 = Math.max( + 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; + } + + 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 public String toString() { - return "[name=timeWindowScorer][scoringParam="+tw_param+"]"; + return "[name=defaultScorer][twParam="+tw_param+"][depotDistanceParam=" + depotDistance_param + "]"; } } diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java new file mode 100644 index 00000000..eeffe4fe --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java @@ -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 . + ******************************************************************************/ + +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. +* +*

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 completionService; + + + /** + * Sets the scoring function. + * + *

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(executorService); + logger.info("initialise " + this); + } + + @Override + public String toString() { + return "[name=regretInsertion][additionalScorer="+scoringFunction+"]"; + } + + + /** + * Runs insertion. + * + *

Before inserting a job, all unassigned jobs are scored according to its best- and secondBest-insertion plus additional scoring variables. + * + */ + @Override + public Collection insertUnassignedJobs(Collection routes, Collection unassignedJobs) { + List badJobs = new ArrayList(unassignedJobs.size()); + List jobs = new ArrayList(unassignedJobs); + + while (!jobs.isEmpty()) { + List unassignedJobList = new ArrayList(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 routes, List unassignedJobList) { + ScoredJob bestScoredJob = null; + + for (final Job unassignedJob : unassignedJobList) { + completionService.submit(new Callable() { + + @Override + public ScoredJob call() throws Exception { + return getScoredJob(routes, unassignedJob); + } + + }); + } + + try{ + for(int i=0; i < unassignedJobList.size(); i++){ + Future 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 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; + } + + +} diff --git a/jsprit-core/src/test/java/jsprit/core/algorithm/recreate/RegretInsertionTest.java b/jsprit-core/src/test/java/jsprit/core/algorithm/recreate/RegretInsertionTest.java index bfb3b4ec..b595a994 100644 --- a/jsprit-core/src/test/java/jsprit/core/algorithm/recreate/RegretInsertionTest.java +++ b/jsprit-core/src/test/java/jsprit/core/algorithm/recreate/RegretInsertionTest.java @@ -22,6 +22,7 @@ import jsprit.core.problem.VehicleRoutingProblem; import jsprit.core.problem.driver.Driver; import jsprit.core.problem.job.Job; 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.activity.TourActivity; import jsprit.core.problem.vehicle.Vehicle; @@ -85,6 +86,50 @@ public class RegretInsertionTest { 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 routes = new ArrayList(); + + 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 {