/**
*
*/
package vroom..optimization.alns;
import java.util.Arrays;
import java.util.ConcurrentModificationException;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import vroom.common.heuristics.alns.IDestroy.IDestroyResult;
import vroom.common.heuristics.alns.IRepair;
import vroom.common.utilities.BatchThreadPoolExecutor;
import vroom.common.utilities.optimization.IInstance;
import vroom.common.utilities.optimization.IParameters;
import vroom..datamodel.Solution;
import vroom..datamodel.costDelegates.CostDelegate;
import vroom..datamodel.costDelegates.TourBalanceDelegate;
import vroom..optimization.InsertionMove;
import vroom..optimization.constraints.ConstraintHandler;
import vroom..util.GlobalParameters;
import vroom..util.Logging;
/**
* <code>RepairBestInsertion</code> is an implementation of the <em>best insertion</em> presented in:
* <p>
* Ropke, S. & Pisinger, D.<br/>
* An adaptive large neighborhood search heuristic for the pickup and delivery problem with time windows<br/>
* Transportation Science, 2006, 40, 455-472
* </p>
* <p>
* Creation date: May 13, 2011 - 3:20:23 PM
*
* @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a
* href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a
* href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a>
* @version 1.0
*/
public class RepairBestInsertion implements IRepair<Solution> {
private boolean mBusy = false;
/** The currently optimized solution */
private Solution mSolution;
/** A matrix containing the insertion cost of each request in each tour */
private InsertionMove[][] mInsCostMatrix;
/** The id of the best insertion tour for each request */
private int[] mBestInsTour;
/** The best insertion move for each request */
private InsertionMove[] mBestInsMove;
/** The tour in which a request was inserted last */
private int mLastInsTour;
/** The best insertion overall</code> */
private InsertionMove mBestInsOverall = null;
/** The best insertion tour overall</code> */
private int mBestInsTourOverall;
/** A lock used to prevent concurrent updates */
private final Lock mUpdateLock = new ReentrantLock();
/** the maximum number of threads that will be used **/
private int mNumThreads = Runtime.getRuntime().availableProcessors();
/**
* Getter for the maximum number of threads that will be used
*
* @return the maximum number of threads that will be used
*/
public int getNumThreads() {
return this.mNumThreads;
}
/**
* Setter for the maximum number of threads that will be used
*
* @param numThreads
* the maximum number of threads that will be used
*/
public void setNumThreads(int numThreads) {
if (!mExecutor.isBatchComplete())
throw new IllegalStateException("Cannot set the number of threads while there are active tasks");
this.mNumThreads = numThreads;
// Create a new executor
mExecutor.shutdown();
try {
mExecutor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
Logging.getBaseLogger().exception("RepairBestInsertion.setNumThreads", e);
}
int proc = Math.min(Runtime.getRuntime().availableProcessors(), getNumThreads());
mExecutor = new BatchThreadPoolExecutor(proc, "repairBI");
}
/** An executor that will evaluate the request insertion costs */
private BatchThreadPoolExecutor mExecutor;
/** A constraint handler for this repair component **/
private final ConstraintHandler mConstraintHandler;
/**
* Getter for the constraint handler
*
* @return A constraint handler for this repair component
*/
public ConstraintHandler getConstraintHandler() {
return this.mConstraintHandler;
}
/**
* Creates a new <code>RepairBestInsertion</code>
*
* @param constraintHandler
*/
public RepairBestInsertion(ConstraintHandler constraintHandler) {
super();
mConstraintHandler = constraintHandler;
int proc = Math.min(Runtime.getRuntime().availableProcessors(), getNumThreads());
mExecutor = new BatchThreadPoolExecutor(proc, "repairBI");
}
@Override
public boolean repair(Solution solution, IDestroyResult<Solution> destroyResult, IParameters params) {
if (mBusy)
throw new ConcurrentModificationException("This instance is already in use");
mBusy = true;
mSolution = solution;
mLastInsTour = -1;
mBestInsTour = new int[solution.getInstance().getMaxId()];
Arrays.fill(mBestInsTour, -1);
mBestInsMove = new InsertionMove[solution.getInstance().getMaxId()];
mInsCostMatrix = new InsertionMove[solution.getInstance().getMaxId()][];
for (int r : solution.getUnservedRequests())
mInsCostMatrix[r] = new InsertionMove[solution.getTourCount()];
while (!solution.getUnservedRequests().isEmpty()) {
// Evaluate the insertion cost matrix
evaluateInsCostMatrix(solution);
// Execute the best insertion
if (mBestInsOverall != null) {
boolean inserted = InsertionMove.executeMove(mBestInsOverall);
if (inserted) {
Logging.getOptimizationLogger().lowDebug(
"RepairBestInsertion.repair: insertion successfull - %s", mBestInsOverall);
// Remove the request from the set of pending requests
solution.markAsServed(mBestInsOverall.getNodeId());
// Store the id of the last modified tour
mLastInsTour = mBestInsTourOverall;
// Clear stored data
mBestInsMove[mBestInsOverall.getNodeId()] = null;
mInsCostMatrix[mBestInsOverall.getNodeId()] = null;
} else {
Logging.getOptimizationLogger().lowDebug("RepairBestInsertion.repair: insertion failed - %s",
mBestInsOverall);
break;
}
} else {
break;
}
}
if (!solution.getUnservedRequests().isEmpty()) {
Logging.getOptimizationLogger().lowDebug(
"RepairBestInsertion.repair: Unable to repair the solution, unfeasible requests: %s",
solution.getUnservedRequests());
}
mBusy = false;
return solution.getUnservedRequests().isEmpty();
}
/**
* Evaluates the insertion cost matrix stored in {@link #mInsCostMatrix} and updates the best insertion for each
* request {@link #mBestInsTour} and overall {@link #mBestInsOverall}
*
* @param solution
* the solution which unserved requests will be evaluated
*/
private void evaluateInsCostMatrix(Solution solution) {
if (getNumThreads())
evaluateInsCostMatrixParallel(solution);
else
evaluateInsCostMatrixSequential(solution);
}
/**
* Evaluates the insertion cost matrix stored in {@link #mInsCostMatrix} and updates the best insertion for each
* request {@link #mBestInsTour} and overall {@link #mBestInsOverall}
*
* @param solution
* the solution which unserved requests will be evaluated
*/
private void evaluateInsCostMatrixSequential(Solution solution) {
// Reset overall best insertion
mBestInsOverall = null;
mBestInsTourOverall = -1;
// Select the tour cost delegate
// We do this to ensure we have good insertions within a tour
CostDelegate costDelegate = mSolution.getCostDelegate() instanceof TourBalanceDelegate ? ((TourBalanceDelegate) mSolution
.getCostDelegate()).getTourCostDelegate() : mSolution.getCostDelegate();
for (int req : solution.getUnservedRequests()) {
if (mLastInsTour < 0 // First evaluation
|| mBestInsTour[req] == mLastInsTour // or the best insertion tour has changed
) {
// Evaluate insertion costs and update best request overall
int bestTour = -1;
InsertionMove bestIns = null;
// Evaluate all tours in the first iteration, and only the last modified tour otherwise
// int tmin = mLastInsTour < 0 ? 0 : mLastInsTour;
// int tmax = mLastInsTour < 0 ? mSolution.getTourCount() : mLastInsTour + 1;
// for (int t = tmin; t < tmax; t++) {
for (int t = 0; t < mSolution.getTourCount(); t++) {
// Evaluate the insertion cost of the given request in tour t
InsertionMove ins = InsertionMove.bestInsertion(req, mSolution.getTour(t), costDelegate,
getConstraintHandler(), GlobalParameters.CTR_CHK_FWD_FEAS);
if (!ins.isFeasible())
// The move cannot be inserted in this tour, skip
continue;
// Reevaluate the insertion if needed to evaluate the insertion for the whole solution
if (mSolution.getCostDelegate() instanceof TourBalanceDelegate) {
mSolution.getCostDelegate().evaluateMove(ins);
}
// Store the value
mInsCostMatrix[req][t] = ins;
// Compare against best tour
if (bestIns == null || ins.getCost() < bestIns.getCost()) {
// Update best tour
bestTour = t;
bestIns = ins;
}
}
// Store best tour
mBestInsTour[req] = bestTour;
mBestInsMove[req] = bestIns;
if (bestTour == -1)
// No feasible was found
return;
// Update best request overall
if (mBestInsOverall == null || mBestInsOverall.getCost() > mInsCostMatrix[req][bestTour].getCost()) {
mBestInsOverall = mBestInsMove[req];
mBestInsTourOverall = bestTour;
}
} else {
if (mBestInsOverall == null
|| (mBestInsTour[req] >= 0 && mBestInsOverall.getCost() > mInsCostMatrix[req][mBestInsTour[req]]
.getCost())) {
mBestInsOverall = mBestInsMove[req];
mBestInsTourOverall = mBestInsTour[req];
}
}
}
}
/**
* Evaluates the insertion cost matrix stored in {@link #mInsCostMatrix} and updates the best insertion for each
* request {@link #mBestInsTour} and overall {@link #mBestInsOverall}
*
* @param solution
* the solution which unserved requests will be evaluated
*/
private void evaluateInsCostMatrixParallel(Solution solution) {
// Reset overall best insertion
mBestInsOverall = null;
mBestInsTourOverall = -1;
LinkedList<InsertionEvaluator> eval = new LinkedList<RepairBestInsertion.InsertionEvaluator>();
for (int r : solution.getUnservedRequests()) {
if (mLastInsTour < 0 // First evaluation
|| mBestInsTour[r] == mLastInsTour // or the best insertion tour has changed
) {
// Evaluate insertion costs and update best request overall
eval.add(new InsertionEvaluator(r));
} else {
// Update best request overall
mUpdateLock.lock();
try {
if (mBestInsOverall == null
|| mBestInsOverall.getCost() > mInsCostMatrix[r][mBestInsTour[r]].getCost()) {
mBestInsOverall = mBestInsMove[r];
mBestInsTourOverall = mBestInsTour[r];
}
} finally {
mUpdateLock.unlock();
}
}
}
try {
// Evaluate all requests in parallel
Map<InsertionEvaluator, Future<?>> future = mExecutor.executeBatch(eval, true);
// Log exceptions if any
for (Future<?> f : future.values()) {
try {
f.get();
} catch (ExecutionException e) {
Logging.getBaseLogger().exception("pInsertion.executeAllAgents", e);
}
}
} catch (InterruptedException e) {
// Wait for evaluation to finish
Logging.getBaseLogger().exception("pInsertion.executeAllAgents", e);
} finally {
}
}
/**
* <code>InsertionEvaluator</code> is a {@link Runnable} that evaluates the insertion cost of a route in all
* scenarios
* <p>
* Creation date: May 17, 2011 - 5:46:26 PM
*
* @author Victor Pillac, <a href="http://uniandes.edu.co">Universidad de Los Andes</a>-<a
* href="http://copa.uniandes.edu.co">Copa</a> <a href="http://www.emn.fr">Ecole des Mines de Nantes</a>-<a
* href="http://www.irccyn.ec-nantes.fr/irccyn/d/en/equipes/Slp">SLP</a>
* @version 1.0
*/
private class InsertionEvaluator implements Runnable {
private final int mRequest;
public InsertionEvaluator(int request) {
super();
mRequest = request;
}
@Override
public void run() {
int bestTour = -1;
InsertionMove bestIns = null;
// Select the tour cost delegate
// We do this to ensure we have good insertions within a tour
CostDelegate costDelegate = mSolution.getCostDelegate() instanceof TourBalanceDelegate ? ((TourBalanceDelegate) mSolution
.getCostDelegate()).getTourCostDelegate() : mSolution.getCostDelegate();
// Evaluate all tours in the first iteration, and only the last modified tour otherwise
// int tmin = mLastInsTour < 0 ? 0 : mLastInsTour;
// int tmax = mLastInsTour < 0 ? mSolution.getTourCount() : mLastInsTour + 1;
// for (int t = tmin; t < tmax; t++) {
for (int t = 0; t < mSolution.getTourCount(); t++) {
// Evaluate the insertion cost of the given request in tour t
InsertionMove ins = InsertionMove.bestInsertion(mRequest, mSolution.getTour(t), costDelegate,
getConstraintHandler(), GlobalParameters.CTR_CHK_FWD_FEAS);
if (!ins.isFeasible())
// The move cannot be inserted in this tour, skip
continue;
// Reevaluate the insertion if needed to evaluate the insertion for the whole solution
if (mSolution.getCostDelegate() instanceof TourBalanceDelegate) {
mSolution.getCostDelegate().evaluateMove(ins);
}
// Store the value
mInsCostMatrix[mRequest][t] = ins;
// Compare against best tour
if (bestIns == null || ins.getCost() < bestIns.getCost()) {
// Update best tour
bestTour = t;
bestIns = ins;
}
}
// Store best tour
mBestInsTour[mRequest] = bestTour;
mBestInsMove[mRequest] = bestIns;
if (bestTour == -1)
// No feasible was found
return;
// Update best request overall
mUpdateLock.lock();
try {
if (mBestInsOverall == null || mBestInsOverall.getCost() > mInsCostMatrix[mRequest][bestTour].getCost()) {
mBestInsOverall = mBestInsMove[mRequest];
mBestInsTourOverall = bestTour;
}
} finally {
mUpdateLock.unlock();
}
}
}
@Override
public String toString() {
return getClass().getSimpleName();
}
@Override
protected void finalize() throws Throwable {
mExecutor.shutdownNow();
super.finalize();
}
@Override
public void initialize(IInstance instance) {
// Do nothing
}
@Override
public String getName() {
return "bestIns";
}
@Override
public void dispose() {
mInsCostMatrix = null;
mBestInsTour = null;
mBestInsMove = null;
mExecutor.shutdownNow();
mExecutor = null;
}
}