diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/box/Jsprit.java b/jsprit-core/src/main/java/jsprit/core/algorithm/box/Jsprit.java index 54005ecc..037e7b38 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/box/Jsprit.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/box/Jsprit.java @@ -407,7 +407,7 @@ public class Jsprit { final DefaultScorer scorer; if (es != null) { - RegretInsertionConcurrentSlow regretInsertion = (RegretInsertionConcurrentSlow) new InsertionBuilder(vrp, fm, stateManager, constraintManager) + RegretInsertionConcurrent regretInsertion = (RegretInsertionConcurrent) new InsertionBuilder(vrp, fm, stateManager, constraintManager) .setInsertionStrategy(InsertionBuilder.Strategy.REGRET) .setConcurrentMode(es, noThreads) .considerFixedCosts(toDouble(getProperty(Parameter.FIXED_COST_PARAM.toString()))) @@ -418,7 +418,7 @@ public class Jsprit { regretInsertion.setScoringFunction(scorer); regret = regretInsertion; } else { - RegretInsertionSlow regretInsertion = (RegretInsertionSlow) new InsertionBuilder(vrp, fm, stateManager, constraintManager) + RegretInsertion regretInsertion = (RegretInsertion) new InsertionBuilder(vrp, fm, stateManager, constraintManager) .setInsertionStrategy(InsertionBuilder.Strategy.REGRET) .setAllowVehicleSwitch(toBoolean(getProperty(Parameter.VEHICLE_SWITCH.toString()))) .considerFixedCosts(toDouble(getProperty(Parameter.FIXED_COST_PARAM.toString()))) 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 165173e6..8b44f774 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 @@ -166,12 +166,12 @@ public class InsertionBuilder { } } else if (strategy.equals(Strategy.REGRET)) { if (executor == null) { - RegretInsertionSlow regret = new RegretInsertionSlow(costCalculator, vrp); + RegretInsertion regret = new RegretInsertion(costCalculator, vrp); // regret.setSwitchAllowed(allowVehicleSwitch); insertion = regret; } else { - RegretInsertionConcurrentSlow regret = new RegretInsertionConcurrentSlow(costCalculator, vrp, executor); + RegretInsertionConcurrent regret = new RegretInsertionConcurrent(costCalculator, vrp, executor); // regret.setSwitchAllowed(allowVehicleSwitch); insertion = regret; } 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 624ddbb4..199b65ab 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 @@ -21,11 +21,13 @@ import jsprit.core.problem.VehicleRoutingProblem; import jsprit.core.problem.job.Break; import jsprit.core.problem.job.Job; import jsprit.core.problem.solution.route.VehicleRoute; -import jsprit.core.problem.vehicle.VehicleFleetManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; /** * Insertion based on regret approach. @@ -39,29 +41,14 @@ import java.util.*; */ public class RegretInsertion extends AbstractInsertionStrategy { - private static Logger logger = LogManager.getLogger(RegretInsertion.class); + + + private static Logger logger = LogManager.getLogger(RegretInsertionFast.class); private ScoringFunction scoringFunction; private JobInsertionCostsCalculator insertionCostsCalculator; - private VehicleFleetManager fleetManager; - - private Set initialVehicleIds; - - private boolean switchAllowed = true; - - - - public RegretInsertion(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem, VehicleFleetManager fleetManager) { - super(vehicleRoutingProblem); - this.scoringFunction = new DefaultScorer(vehicleRoutingProblem); - this.insertionCostsCalculator = jobInsertionCalculator; - this.fleetManager = fleetManager; - this.vrp = vehicleRoutingProblem; - this.initialVehicleIds = getInitialVehicleIds(vehicleRoutingProblem); - logger.debug("initialise {}", this); - } /** * Sets the scoring function. @@ -74,16 +61,12 @@ public class RegretInsertion extends AbstractInsertionStrategy { this.scoringFunction = scoringFunction; } - public void setSwitchAllowed(boolean switchAllowed) { - this.switchAllowed = switchAllowed; - } - - private Set getInitialVehicleIds(VehicleRoutingProblem vehicleRoutingProblem) { - Set ids = new HashSet(); - for(VehicleRoute r : vehicleRoutingProblem.getInitialVehicleRoutes()){ - ids.add(r.getVehicle().getId()); - } - return ids; + public RegretInsertion(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem) { + super(vehicleRoutingProblem); + this.scoringFunction = new DefaultScorer(vehicleRoutingProblem); + this.insertionCostsCalculator = jobInsertionCalculator; + this.vrp = vehicleRoutingProblem; + logger.debug("initialise {}", this); } @Override @@ -105,7 +88,7 @@ public class RegretInsertion extends AbstractInsertionStrategy { while (jobIterator.hasNext()){ Job job = jobIterator.next(); if(job instanceof Break){ - VehicleRoute route = InsertionDataUpdater.findRoute(routes, job); + VehicleRoute route = findRoute(routes,job); if(route == null){ badJobs.add(job); } @@ -122,35 +105,17 @@ public class RegretInsertion extends AbstractInsertionStrategy { } List jobs = new ArrayList(unassignedJobs); - TreeSet[] priorityQueues = new TreeSet[vrp.getJobs().values().size() + 2]; - VehicleRoute lastModified = null; - boolean firstRun = true; - int updateRound = 0; - Map updates = new HashMap(); while (!jobs.isEmpty()) { List unassignedJobList = new ArrayList(jobs); List badJobList = new ArrayList(); - if(!firstRun && lastModified == null) throw new IllegalStateException("fooo"); - if(firstRun){ - firstRun = false; - updateInsertionData(priorityQueues, routes, unassignedJobList, updateRound); - for(VehicleRoute r : routes) updates.put(r,updateRound); - } - else{ - updateInsertionData(priorityQueues, Arrays.asList(lastModified), unassignedJobList, updateRound); - updates.put(lastModified,updateRound); - } - updateRound++; - ScoredJob bestScoredJob = InsertionDataUpdater.getBest(switchAllowed,initialVehicleIds,fleetManager,insertionCostsCalculator,scoringFunction,priorityQueues,updates,unassignedJobList,badJobList); + ScoredJob bestScoredJob = nextJob(routes, unassignedJobList, badJobList); if (bestScoredJob != null) { if (bestScoredJob.isNewRoute()) { routes.add(bestScoredJob.getRoute()); } insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); jobs.remove(bestScoredJob.getJob()); - lastModified = bestScoredJob.getRoute(); } - else lastModified = null; for (Job bad : badJobList) { jobs.remove(bad); badJobs.add(bad); @@ -159,15 +124,99 @@ public class RegretInsertion extends AbstractInsertionStrategy { return badJobs; } - private void updateInsertionData(TreeSet[] priorityQueues, Collection routes, List unassignedJobList, int updateRound) { - for (Job unassignedJob : unassignedJobList) { - if(priorityQueues[unassignedJob.getIndex()] == null){ - priorityQueues[unassignedJob.getIndex()] = new TreeSet(InsertionDataUpdater.getComparator()); - } - InsertionDataUpdater.update(switchAllowed, initialVehicleIds,fleetManager,insertionCostsCalculator, priorityQueues[unassignedJob.getIndex()], updateRound, unassignedJob, routes); + private VehicleRoute findRoute(Collection routes, Job job) { + for(VehicleRoute r : routes){ + if(r.getVehicle().getBreak() == job) return r; } + return null; + } + + private ScoredJob nextJob(Collection routes, Collection unassignedJobList, List badJobs) { + ScoredJob bestScoredJob = null; + for (Job unassignedJob : unassignedJobList) { + ScoredJob scoredJob = getScoredJob(routes, unassignedJob, insertionCostsCalculator, scoringFunction); + if (scoredJob instanceof ScoredJob.BadJob) { + badJobs.add(unassignedJob); + continue; + } + if (bestScoredJob == null) bestScoredJob = scoredJob; + else { + if (scoredJob.getScore() > bestScoredJob.getScore()) { + bestScoredJob = scoredJob; + } else if (scoredJob.getScore() == bestScoredJob.getScore()) { + if (scoredJob.getJob().getId().compareTo(bestScoredJob.getJob().getId()) <= 0) { + bestScoredJob = scoredJob; + } + } + } + } + return bestScoredJob; + } + + static ScoredJob getScoredJob(Collection routes, Job unassignedJob, JobInsertionCostsCalculator insertionCostsCalculator, ScoringFunction scoringFunction) { + 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)) { + 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; + } + } + if (best == null) { + return new ScoredJob.BadJob(unassignedJob); + } + double score = score(unassignedJob, best, secondBest, scoringFunction); + ScoredJob scoredJob; + if (bestRoute == emptyRoute) { + scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, true); + } else scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, false); + return scoredJob; } + static double score(Job unassignedJob, InsertionData best, InsertionData secondBest, ScoringFunction scoringFunction) { + if (best == null) { + throw new IllegalStateException("cannot insert job " + unassignedJob.getId()); + } + double score; + if (secondBest == null) { //either there is only one vehicle or there are more vehicles, but they cannot load unassignedJob + //if only one vehicle, I want the job to be inserted with min iCosts + //if there are more vehicles, I want this job to be prioritized since there are no alternatives + score = Integer.MAX_VALUE - best.getInsertionCost() + scoringFunction.score(best, unassignedJob); + } else { + score = (secondBest.getInsertionCost() - best.getInsertionCost()) + scoringFunction.score(best, unassignedJob); + } + return score; + } + } 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 index eb80e67d..3bc622a9 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrent.java @@ -21,14 +21,14 @@ import jsprit.core.problem.VehicleRoutingProblem; import jsprit.core.problem.job.Break; import jsprit.core.problem.job.Job; import jsprit.core.problem.solution.route.VehicleRoute; -import jsprit.core.problem.vehicle.VehicleFleetManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.*; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Future; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.concurrent.*; /** * Insertion based on regret approach. @@ -43,20 +43,13 @@ import java.util.concurrent.Future; public class RegretInsertionConcurrent extends AbstractInsertionStrategy { - private static Logger logger = LogManager.getLogger(RegretInsertionConcurrent.class); + private static Logger logger = LogManager.getLogger(RegretInsertionConcurrentFast.class); private ScoringFunction scoringFunction; private final JobInsertionCostsCalculator insertionCostsCalculator; - private final ExecutorService executor; - - private VehicleFleetManager fleetManager; - - private Set initialVehicleIds; - - private boolean switchAllowed = true; - + private final ExecutorCompletionService completionService; /** * Sets the scoring function. @@ -69,14 +62,12 @@ public class RegretInsertionConcurrent extends AbstractInsertionStrategy { this.scoringFunction = scoringFunction; } - public RegretInsertionConcurrent(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem, ExecutorService executorService, VehicleFleetManager fleetManager) { + public RegretInsertionConcurrent(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem, ExecutorService executorService) { super(vehicleRoutingProblem); this.scoringFunction = new DefaultScorer(vehicleRoutingProblem); this.insertionCostsCalculator = jobInsertionCalculator; this.vrp = vehicleRoutingProblem; - this.executor = executorService; - this.fleetManager = fleetManager; - this.initialVehicleIds = getInitialVehicleIds(vehicleRoutingProblem); + completionService = new ExecutorCompletionService(executorService); logger.debug("initialise " + this); } @@ -85,18 +76,6 @@ public class RegretInsertionConcurrent extends AbstractInsertionStrategy { return "[name=regretInsertion][additionalScorer=" + scoringFunction + "]"; } - public void setSwitchAllowed(boolean switchAllowed) { - this.switchAllowed = switchAllowed; - } - - private Set getInitialVehicleIds(VehicleRoutingProblem vehicleRoutingProblem) { - Set ids = new HashSet(); - for(VehicleRoute r : vehicleRoutingProblem.getInitialVehicleRoutes()){ - ids.add(r.getVehicle().getId()); - } - return ids; - } - /** * Runs insertion. @@ -113,7 +92,7 @@ public class RegretInsertionConcurrent extends AbstractInsertionStrategy { while (jobIterator.hasNext()){ Job job = jobIterator.next(); if(job instanceof Break){ - VehicleRoute route = InsertionDataUpdater.findRoute(routes, job); + VehicleRoute route = findRoute(routes,job); if(route == null){ badJobs.add(job); } @@ -130,67 +109,72 @@ public class RegretInsertionConcurrent extends AbstractInsertionStrategy { } List jobs = new ArrayList(unassignedJobs); - TreeSet[] priorityQueues = new TreeSet[vrp.getJobs().values().size() + 2]; - VehicleRoute lastModified = null; - boolean firstRun = true; - int updateRound = 0; - Map updates = new HashMap(); while (!jobs.isEmpty()) { List unassignedJobList = new ArrayList(jobs); List badJobList = new ArrayList(); - if(!firstRun && lastModified == null) throw new IllegalStateException("ho. this must not be."); - if(firstRun){ - firstRun = false; - updateInsertionData(priorityQueues, routes, unassignedJobList, updateRound); - for(VehicleRoute r : routes) updates.put(r,updateRound); - } - else{ - updateInsertionData(priorityQueues, Arrays.asList(lastModified), unassignedJobList, updateRound); - updates.put(lastModified,updateRound); - } - updateRound++; - ScoredJob bestScoredJob = InsertionDataUpdater.getBest(switchAllowed,initialVehicleIds,fleetManager, insertionCostsCalculator, scoringFunction, priorityQueues, updates, unassignedJobList, badJobList); + ScoredJob bestScoredJob = nextJob(routes, unassignedJobList, badJobList); if (bestScoredJob != null) { if (bestScoredJob.isNewRoute()) { routes.add(bestScoredJob.getRoute()); } insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); jobs.remove(bestScoredJob.getJob()); - lastModified = bestScoredJob.getRoute(); } - else lastModified = null; - for (Job bad : badJobList) { - jobs.remove(bad); - badJobs.add(bad); + for (Job j : badJobList) { + jobs.remove(j); + badJobs.add(j); } } return badJobs; } - private void updateInsertionData(final TreeSet[] priorityQueues, final Collection routes, List unassignedJobList, final int updateRound) { - List> tasks = new ArrayList>(); + private ScoredJob nextJob(final Collection routes, List unassignedJobList, List badJobList) { + ScoredJob bestScoredJob = null; + for (final Job unassignedJob : unassignedJobList) { - if(priorityQueues[unassignedJob.getIndex()] == null){ - priorityQueues[unassignedJob.getIndex()] = new TreeSet(InsertionDataUpdater.getComparator()); - } - final TreeSet priorityQueue = priorityQueues[unassignedJob.getIndex()]; - tasks.add(new Callable() { + completionService.submit(new Callable() { + @Override - public Boolean call() throws Exception { - return InsertionDataUpdater.update(switchAllowed,initialVehicleIds,fleetManager, insertionCostsCalculator, priorityQueue, updateRound, unassignedJob, routes); + public ScoredJob call() throws Exception { + return RegretInsertion.getScoredJob(routes, unassignedJob, insertionCostsCalculator, scoringFunction); } + }); } + try { - List> futures = executor.invokeAll(tasks); + for (int i = 0; i < unassignedJobList.size(); i++) { + Future fsj = completionService.take(); + ScoredJob sJob = fsj.get(); + if (sJob instanceof ScoredJob.BadJob) { + badJobList.add(sJob.getJob()); + continue; + } + if (bestScoredJob == null) { + bestScoredJob = sJob; + } else if (sJob.getScore() > bestScoredJob.getScore()) { + bestScoredJob = sJob; + } else if (sJob.getScore() == bestScoredJob.getScore()) { + if (sJob.getJob().getId().compareTo(bestScoredJob.getJob().getId()) <= 0) { + bestScoredJob = sJob; + } + } + } } catch (InterruptedException e) { Thread.currentThread().interrupt(); + } catch (ExecutionException e) { throw new RuntimeException(e); } + + return bestScoredJob; + } + + private VehicleRoute findRoute(Collection routes, Job job) { + for(VehicleRoute r : routes){ + if(r.getVehicle().getBreak() == job) return r; + } + return null; } - - - } diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrentFast.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrentFast.java new file mode 100644 index 00000000..38f3d213 --- /dev/null +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrentFast.java @@ -0,0 +1,196 @@ +/******************************************************************************* + * 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.problem.VehicleRoutingProblem; +import jsprit.core.problem.job.Break; +import jsprit.core.problem.job.Job; +import jsprit.core.problem.solution.route.VehicleRoute; +import jsprit.core.problem.vehicle.VehicleFleetManager; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.util.*; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; + +/** + * 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 RegretInsertionConcurrentFast extends AbstractInsertionStrategy { + + + private static Logger logger = LogManager.getLogger(RegretInsertionConcurrentFast.class); + + private ScoringFunction scoringFunction; + + private final JobInsertionCostsCalculator insertionCostsCalculator; + + private final ExecutorService executor; + + private VehicleFleetManager fleetManager; + + private Set initialVehicleIds; + + private boolean switchAllowed = true; + + + /** + * Sets the scoring function. + *

+ *

By default, the this.TimeWindowScorer is used. + * + * @param scoringFunction to score + */ + public void setScoringFunction(ScoringFunction scoringFunction) { + this.scoringFunction = scoringFunction; + } + + public RegretInsertionConcurrentFast(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem, ExecutorService executorService, VehicleFleetManager fleetManager) { + super(vehicleRoutingProblem); + this.scoringFunction = new DefaultScorer(vehicleRoutingProblem); + this.insertionCostsCalculator = jobInsertionCalculator; + this.vrp = vehicleRoutingProblem; + this.executor = executorService; + this.fleetManager = fleetManager; + this.initialVehicleIds = getInitialVehicleIds(vehicleRoutingProblem); + logger.debug("initialise " + this); + } + + @Override + public String toString() { + return "[name=regretInsertion][additionalScorer=" + scoringFunction + "]"; + } + + public void setSwitchAllowed(boolean switchAllowed) { + this.switchAllowed = switchAllowed; + } + + private Set getInitialVehicleIds(VehicleRoutingProblem vehicleRoutingProblem) { + Set ids = new HashSet(); + for(VehicleRoute r : vehicleRoutingProblem.getInitialVehicleRoutes()){ + ids.add(r.getVehicle().getId()); + } + return ids; + } + + + /** + * Runs insertion. + *

+ *

Before inserting a job, all unassigned jobs are scored according to its best- and secondBest-insertion plus additional scoring variables. + * + * @throws java.lang.RuntimeException if smth went wrong with thread execution + */ + @Override + public Collection insertUnassignedJobs(Collection routes, Collection unassignedJobs) { + List badJobs = new ArrayList(unassignedJobs.size()); + + Iterator jobIterator = unassignedJobs.iterator(); + while (jobIterator.hasNext()){ + Job job = jobIterator.next(); + if(job instanceof Break){ + VehicleRoute route = InsertionDataUpdater.findRoute(routes, job); + if(route == null){ + badJobs.add(job); + } + else { + InsertionData iData = insertionCostsCalculator.getInsertionData(route, job, NO_NEW_VEHICLE_YET, NO_NEW_DEPARTURE_TIME_YET, NO_NEW_DRIVER_YET, Double.MAX_VALUE); + if (iData instanceof InsertionData.NoInsertionFound) { + badJobs.add(job); + } else { + insertJob(job, iData, route); + } + } + jobIterator.remove(); + } + } + + List jobs = new ArrayList(unassignedJobs); + TreeSet[] priorityQueues = new TreeSet[vrp.getJobs().values().size() + 2]; + VehicleRoute lastModified = null; + boolean firstRun = true; + int updateRound = 0; + Map updates = new HashMap(); + while (!jobs.isEmpty()) { + List unassignedJobList = new ArrayList(jobs); + List badJobList = new ArrayList(); + if(!firstRun && lastModified == null) throw new IllegalStateException("ho. this must not be."); + if(firstRun){ + firstRun = false; + updateInsertionData(priorityQueues, routes, unassignedJobList, updateRound); + for(VehicleRoute r : routes) updates.put(r,updateRound); + } + else{ + updateInsertionData(priorityQueues, Arrays.asList(lastModified), unassignedJobList, updateRound); + updates.put(lastModified,updateRound); + } + updateRound++; + ScoredJob bestScoredJob = InsertionDataUpdater.getBest(switchAllowed,initialVehicleIds,fleetManager, insertionCostsCalculator, scoringFunction, priorityQueues, updates, unassignedJobList, badJobList); + if (bestScoredJob != null) { + if (bestScoredJob.isNewRoute()) { + routes.add(bestScoredJob.getRoute()); + } + insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); + jobs.remove(bestScoredJob.getJob()); + lastModified = bestScoredJob.getRoute(); + } + else lastModified = null; + for (Job bad : badJobList) { + jobs.remove(bad); + badJobs.add(bad); + } + } + return badJobs; + } + + private void updateInsertionData(final TreeSet[] priorityQueues, final Collection routes, List unassignedJobList, final int updateRound) { + List> tasks = new ArrayList>(); + for (final Job unassignedJob : unassignedJobList) { + if(priorityQueues[unassignedJob.getIndex()] == null){ + priorityQueues[unassignedJob.getIndex()] = new TreeSet(InsertionDataUpdater.getComparator()); + } + final TreeSet priorityQueue = priorityQueues[unassignedJob.getIndex()]; + tasks.add(new Callable() { + @Override + public Boolean call() throws Exception { + return InsertionDataUpdater.update(switchAllowed,initialVehicleIds,fleetManager, insertionCostsCalculator, priorityQueue, updateRound, unassignedJob, routes); + } + }); + } + try { + List> futures = executor.invokeAll(tasks); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + throw new RuntimeException(e); + } + } + + + + + +} diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrentSlow.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionFast.java similarity index 59% rename from jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrentSlow.java rename to jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionFast.java index d8e75463..da0ea0dd 100644 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionConcurrentSlow.java +++ b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionFast.java @@ -21,14 +21,11 @@ import jsprit.core.problem.VehicleRoutingProblem; import jsprit.core.problem.job.Break; import jsprit.core.problem.job.Job; import jsprit.core.problem.solution.route.VehicleRoute; +import jsprit.core.problem.vehicle.VehicleFleetManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Iterator; -import java.util.List; -import java.util.concurrent.*; +import java.util.*; /** * Insertion based on regret approach. @@ -40,16 +37,31 @@ import java.util.concurrent.*; * * @author stefan schroeder */ -public class RegretInsertionConcurrentSlow extends AbstractInsertionStrategy { +public class RegretInsertionFast extends AbstractInsertionStrategy { - - private static Logger logger = LogManager.getLogger(RegretInsertionConcurrent.class); + private static Logger logger = LogManager.getLogger(RegretInsertionFast.class); private ScoringFunction scoringFunction; - private final JobInsertionCostsCalculator insertionCostsCalculator; + private JobInsertionCostsCalculator insertionCostsCalculator; - private final ExecutorCompletionService completionService; + private VehicleFleetManager fleetManager; + + private Set initialVehicleIds; + + private boolean switchAllowed = true; + + + + public RegretInsertionFast(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem, VehicleFleetManager fleetManager) { + super(vehicleRoutingProblem); + this.scoringFunction = new DefaultScorer(vehicleRoutingProblem); + this.insertionCostsCalculator = jobInsertionCalculator; + this.fleetManager = fleetManager; + this.vrp = vehicleRoutingProblem; + this.initialVehicleIds = getInitialVehicleIds(vehicleRoutingProblem); + logger.debug("initialise {}", this); + } /** * Sets the scoring function. @@ -62,13 +74,16 @@ public class RegretInsertionConcurrentSlow extends AbstractInsertionStrategy { this.scoringFunction = scoringFunction; } - public RegretInsertionConcurrentSlow(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem, ExecutorService executorService) { - super(vehicleRoutingProblem); - this.scoringFunction = new DefaultScorer(vehicleRoutingProblem); - this.insertionCostsCalculator = jobInsertionCalculator; - this.vrp = vehicleRoutingProblem; - completionService = new ExecutorCompletionService(executorService); - logger.debug("initialise " + this); + public void setSwitchAllowed(boolean switchAllowed) { + this.switchAllowed = switchAllowed; + } + + private Set getInitialVehicleIds(VehicleRoutingProblem vehicleRoutingProblem) { + Set ids = new HashSet(); + for(VehicleRoute r : vehicleRoutingProblem.getInitialVehicleRoutes()){ + ids.add(r.getVehicle().getId()); + } + return ids; } @Override @@ -81,8 +96,6 @@ public class RegretInsertionConcurrentSlow extends AbstractInsertionStrategy { * Runs insertion. *

*

Before inserting a job, all unassigned jobs are scored according to its best- and secondBest-insertion plus additional scoring variables. - * - * @throws java.lang.RuntimeException if smth went wrong with thread execution */ @Override public Collection insertUnassignedJobs(Collection routes, Collection unassignedJobs) { @@ -92,7 +105,7 @@ public class RegretInsertionConcurrentSlow extends AbstractInsertionStrategy { while (jobIterator.hasNext()){ Job job = jobIterator.next(); if(job instanceof Break){ - VehicleRoute route = findRoute(routes,job); + VehicleRoute route = InsertionDataUpdater.findRoute(routes, job); if(route == null){ badJobs.add(job); } @@ -109,72 +122,52 @@ public class RegretInsertionConcurrentSlow extends AbstractInsertionStrategy { } List jobs = new ArrayList(unassignedJobs); + TreeSet[] priorityQueues = new TreeSet[vrp.getJobs().values().size() + 2]; + VehicleRoute lastModified = null; + boolean firstRun = true; + int updateRound = 0; + Map updates = new HashMap(); while (!jobs.isEmpty()) { List unassignedJobList = new ArrayList(jobs); List badJobList = new ArrayList(); - ScoredJob bestScoredJob = nextJob(routes, unassignedJobList, badJobList); + if(!firstRun && lastModified == null) throw new IllegalStateException("fooo"); + if(firstRun){ + firstRun = false; + updateInsertionData(priorityQueues, routes, unassignedJobList, updateRound); + for(VehicleRoute r : routes) updates.put(r,updateRound); + } + else{ + updateInsertionData(priorityQueues, Arrays.asList(lastModified), unassignedJobList, updateRound); + updates.put(lastModified,updateRound); + } + updateRound++; + ScoredJob bestScoredJob = InsertionDataUpdater.getBest(switchAllowed,initialVehicleIds,fleetManager,insertionCostsCalculator,scoringFunction,priorityQueues,updates,unassignedJobList,badJobList); if (bestScoredJob != null) { if (bestScoredJob.isNewRoute()) { routes.add(bestScoredJob.getRoute()); } insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); jobs.remove(bestScoredJob.getJob()); + lastModified = bestScoredJob.getRoute(); } - for (Job j : badJobList) { - jobs.remove(j); - badJobs.add(j); + else lastModified = null; + for (Job bad : badJobList) { + jobs.remove(bad); + badJobs.add(bad); } } return badJobs; } - private ScoredJob nextJob(final Collection routes, List unassignedJobList, List badJobList) { - ScoredJob bestScoredJob = null; - - for (final Job unassignedJob : unassignedJobList) { - completionService.submit(new Callable() { - - @Override - public ScoredJob call() throws Exception { - return RegretInsertionSlow.getScoredJob(routes, unassignedJob, insertionCostsCalculator, scoringFunction); - } - - }); - } - - try { - for (int i = 0; i < unassignedJobList.size(); i++) { - Future fsj = completionService.take(); - ScoredJob sJob = fsj.get(); - if (sJob instanceof ScoredJob.BadJob) { - badJobList.add(sJob.getJob()); - continue; - } - if (bestScoredJob == null) { - bestScoredJob = sJob; - } else if (sJob.getScore() > bestScoredJob.getScore()) { - bestScoredJob = sJob; - } else if (sJob.getScore() == bestScoredJob.getScore()) { - if (sJob.getJob().getId().compareTo(bestScoredJob.getJob().getId()) <= 0) { - bestScoredJob = sJob; - } - } + private void updateInsertionData(TreeSet[] priorityQueues, Collection routes, List unassignedJobList, int updateRound) { + for (Job unassignedJob : unassignedJobList) { + if(priorityQueues[unassignedJob.getIndex()] == null){ + priorityQueues[unassignedJob.getIndex()] = new TreeSet(InsertionDataUpdater.getComparator()); } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (ExecutionException e) { - throw new RuntimeException(e); + InsertionDataUpdater.update(switchAllowed, initialVehicleIds,fleetManager,insertionCostsCalculator, priorityQueues[unassignedJob.getIndex()], updateRound, unassignedJob, routes); } - - return bestScoredJob; } - private VehicleRoute findRoute(Collection routes, Job job) { - for(VehicleRoute r : routes){ - if(r.getVehicle().getBreak() == job) return r; - } - return null; - } } diff --git a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionSlow.java b/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionSlow.java deleted file mode 100644 index 1a6e2236..00000000 --- a/jsprit-core/src/main/java/jsprit/core/algorithm/recreate/RegretInsertionSlow.java +++ /dev/null @@ -1,222 +0,0 @@ -/******************************************************************************* - * 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.problem.VehicleRoutingProblem; -import jsprit.core.problem.job.Break; -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.Iterator; -import java.util.List; - -/** - * 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 RegretInsertionSlow extends AbstractInsertionStrategy { - - - - private static Logger logger = LogManager.getLogger(RegretInsertion.class); - - private ScoringFunction scoringFunction; - - private JobInsertionCostsCalculator insertionCostsCalculator; - - - /** - * Sets the scoring function. - *

- *

By default, the this.TimeWindowScorer is used. - * - * @param scoringFunction to score - */ - public void setScoringFunction(ScoringFunction scoringFunction) { - this.scoringFunction = scoringFunction; - } - - public RegretInsertionSlow(JobInsertionCostsCalculator jobInsertionCalculator, VehicleRoutingProblem vehicleRoutingProblem) { - super(vehicleRoutingProblem); - this.scoringFunction = new DefaultScorer(vehicleRoutingProblem); - this.insertionCostsCalculator = jobInsertionCalculator; - this.vrp = vehicleRoutingProblem; - logger.debug("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()); - - Iterator jobIterator = unassignedJobs.iterator(); - while (jobIterator.hasNext()){ - Job job = jobIterator.next(); - if(job instanceof Break){ - VehicleRoute route = findRoute(routes,job); - if(route == null){ - badJobs.add(job); - } - else { - InsertionData iData = insertionCostsCalculator.getInsertionData(route, job, NO_NEW_VEHICLE_YET, NO_NEW_DEPARTURE_TIME_YET, NO_NEW_DRIVER_YET, Double.MAX_VALUE); - if (iData instanceof InsertionData.NoInsertionFound) { - badJobs.add(job); - } else { - insertJob(job, iData, route); - } - } - jobIterator.remove(); - } - } - - List jobs = new ArrayList(unassignedJobs); - while (!jobs.isEmpty()) { - List unassignedJobList = new ArrayList(jobs); - List badJobList = new ArrayList(); - ScoredJob bestScoredJob = nextJob(routes, unassignedJobList, badJobList); - if (bestScoredJob != null) { - if (bestScoredJob.isNewRoute()) { - routes.add(bestScoredJob.getRoute()); - } - insertJob(bestScoredJob.getJob(), bestScoredJob.getInsertionData(), bestScoredJob.getRoute()); - jobs.remove(bestScoredJob.getJob()); - } - for (Job bad : badJobList) { - jobs.remove(bad); - badJobs.add(bad); - } - } - return badJobs; - } - - private VehicleRoute findRoute(Collection routes, Job job) { - for(VehicleRoute r : routes){ - if(r.getVehicle().getBreak() == job) return r; - } - return null; - } - - private ScoredJob nextJob(Collection routes, Collection unassignedJobList, List badJobs) { - ScoredJob bestScoredJob = null; - for (Job unassignedJob : unassignedJobList) { - ScoredJob scoredJob = getScoredJob(routes, unassignedJob, insertionCostsCalculator, scoringFunction); - if (scoredJob instanceof ScoredJob.BadJob) { - badJobs.add(unassignedJob); - continue; - } - if (bestScoredJob == null) bestScoredJob = scoredJob; - else { - if (scoredJob.getScore() > bestScoredJob.getScore()) { - bestScoredJob = scoredJob; - } else if (scoredJob.getScore() == bestScoredJob.getScore()) { - if (scoredJob.getJob().getId().compareTo(bestScoredJob.getJob().getId()) <= 0) { - bestScoredJob = scoredJob; - } - } - } - } - return bestScoredJob; - } - - static ScoredJob getScoredJob(Collection routes, Job unassignedJob, JobInsertionCostsCalculator insertionCostsCalculator, ScoringFunction scoringFunction) { - 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)) { - 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; - } - } - if (best == null) { - return new ScoredJob.BadJob(unassignedJob); - } - double score = score(unassignedJob, best, secondBest, scoringFunction); - ScoredJob scoredJob; - if (bestRoute == emptyRoute) { - scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, true); - } else scoredJob = new ScoredJob(unassignedJob, score, best, bestRoute, false); - return scoredJob; - } - - - static double score(Job unassignedJob, InsertionData best, InsertionData secondBest, ScoringFunction scoringFunction) { - if (best == null) { - throw new IllegalStateException("cannot insert job " + unassignedJob.getId()); - } - double score; - if (secondBest == null) { //either there is only one vehicle or there are more vehicles, but they cannot load unassignedJob - //if only one vehicle, I want the job to be inserted with min iCosts - //if there are more vehicles, I want this job to be prioritized since there are no alternatives - score = Integer.MAX_VALUE - 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 08677edb..5b54e596 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 @@ -49,7 +49,7 @@ public class RegretInsertionTest { VehicleFleetManager fm = new FiniteFleetManagerFactory(vrp.getVehicles()).createFleetManager(); JobInsertionCostsCalculator calculator = getCalculator(vrp); - RegretInsertion regretInsertion = new RegretInsertion(calculator, vrp, fm); + RegretInsertionFast regretInsertion = new RegretInsertionFast(calculator, vrp, fm); Collection routes = new ArrayList(); regretInsertion.insertJobs(routes, vrp.getJobs().values()); @@ -66,7 +66,7 @@ public class RegretInsertionTest { VehicleFleetManager fm = new FiniteFleetManagerFactory(vrp.getVehicles()).createFleetManager(); JobInsertionCostsCalculator calculator = getCalculator(vrp); - RegretInsertion regretInsertion = new RegretInsertion(calculator, vrp, fm); + RegretInsertionFast regretInsertion = new RegretInsertionFast(calculator, vrp, fm); Collection routes = new ArrayList(); regretInsertion.insertJobs(routes, vrp.getJobs().values()); @@ -83,7 +83,7 @@ public class RegretInsertionTest { VehicleFleetManager fm = new FiniteFleetManagerFactory(vrp.getVehicles()).createFleetManager(); JobInsertionCostsCalculator calculator = getCalculator(vrp); - RegretInsertion regretInsertion = new RegretInsertion(calculator, vrp, fm); + RegretInsertionFast regretInsertion = new RegretInsertionFast(calculator, vrp, fm); Collection routes = new ArrayList(); CkeckJobSequence position = new CkeckJobSequence(2, s1); @@ -109,7 +109,7 @@ public class RegretInsertionTest { VehicleFleetManager fm = new FiniteFleetManagerFactory(vrp.getVehicles()).createFleetManager(); JobInsertionCostsCalculator calculator = getShipmentCalculator(vrp); - RegretInsertion regretInsertion = new RegretInsertion(calculator, vrp, fm); + RegretInsertionFast regretInsertion = new RegretInsertionFast(calculator, vrp, fm); Collection routes = new ArrayList(); CkeckJobSequence position = new CkeckJobSequence(2, s2); diff --git a/jsprit-core/src/test/resources/infiniteWriterV2Test.xml b/jsprit-core/src/test/resources/infiniteWriterV2Test.xml index 2d9058ab..07c5406a 100644 --- a/jsprit-core/src/test/resources/infiniteWriterV2Test.xml +++ b/jsprit-core/src/test/resources/infiniteWriterV2Test.xml @@ -2,9 +2,24 @@ - INFINITE + FINITE + + v2 + vehType2 + + loc + + + loc + + + 0.0 + 1.7976931348623157E308 + + true + v1 vehType @@ -33,58 +48,16 @@ + + vehType2 + + 200 + + + 0.0 + 1.0 + + + - - - - loc - - - 1 - - 2.0 - - - 0.0 - 1.7976931348623157E308 - - - - - - loc2 - - - 1 - - 4.0 - - - 0.0 - 1.7976931348623157E308 - - - - - - - 10.0 - - - noDriver - v1 - 0.0 - - 1 - 0.0 - 0.0 - - 0.0 - - - - - - - diff --git a/jsprit-examples/src/main/java/jsprit/examples/BuildAlgorithmFromScratch.java b/jsprit-examples/src/main/java/jsprit/examples/BuildAlgorithmFromScratch.java index b594e5a6..22d60d2d 100644 --- a/jsprit-examples/src/main/java/jsprit/examples/BuildAlgorithmFromScratch.java +++ b/jsprit-examples/src/main/java/jsprit/examples/BuildAlgorithmFromScratch.java @@ -143,7 +143,7 @@ public class BuildAlgorithmFromScratch { //regret insertion InsertionBuilder iBuilder = new InsertionBuilder(vrp, fleetManager, stateManager, constraintManager); iBuilder.setInsertionStrategy(InsertionBuilder.Strategy.REGRET); - RegretInsertion regret = (RegretInsertion) iBuilder.build(); + RegretInsertionFast regret = (RegretInsertionFast) iBuilder.build(); DefaultScorer scoringFunction = new DefaultScorer(vrp); scoringFunction.setDepotDistanceParam(0.2); scoringFunction.setTimeWindowParam(-.2);