/**
*
*/
package vroom.common.heuristics.alns;
import umontreal.iro.lecuyer.rng.RandomStream;
import vroom.common.heuristics.LocalSearchBase;
import vroom.common.heuristics.alns.IDestroy.IDestroyResult;
import vroom.common.heuristics.utils.HeuristicsLogging;
import vroom.common.utilities.ProgressMonitor;
import vroom.common.utilities.Stopwatch;
import vroom.common.utilities.callbacks.CallbackManagerDelegate;
import vroom.common.utilities.callbacks.ICallback;
import vroom.common.utilities.logging.LoggerHelper;
import vroom.common.utilities.optimization.IComponentHandler;
import vroom.common.utilities.optimization.IComponentHandler.Outcome;
import vroom.common.utilities.optimization.IInstance;
import vroom.common.utilities.optimization.IParameters;
import vroom.common.utilities.optimization.ISolution;
import vroom.common.utilities.optimization.OptimizationSense;
/**
* <code>AdaptiveLargeNeighborhoodSearch</code> is a generic implementation of the ALNS algorithm as 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 12, 2011 - 1:15:15 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 AdaptiveLargeNeighborhoodSearch<S extends ISolution> extends LocalSearchBase<S> {
/** The Constant LOGGER. */
private final static LoggerHelper sLogger = HeuristicsLogging.getLogger("ALNS");
/**
* Getter for the logger used in this ALNS
*
* @return the logger used in this ALNS
*/
public static LoggerHelper getLogger() {
return sLogger;
}
/** A callback handler for this procedure */
private final CallbackManagerDelegate<AdaptiveLargeNeighborhoodSearch<S>, ALNSEventType> mCallbacks;
CallbackManagerDelegate<AdaptiveLargeNeighborhoodSearch<S>, ALNSEventType> getCallbacks() {
return mCallbacks;
}
/**
* Register the given <code>callback</code> to the event <code>type</code><br/>
*
* @param callback
* the callback that will be associated with <code>event</code>
* @param type
* the event to which the given <code>callback</code> will be associated
*/
public void registerCallback(ICallback<AdaptiveLargeNeighborhoodSearch<S>, ALNSEventType> callback,
ALNSEventType type) {
getCallbacks().registerCallback(callback, type);
}
/** the destroy components **/
private final IComponentHandler<IDestroy<S>> mDestroyComponents;
/**
* Getter for the destroy components
*
* @return the destroy components
*/
public IComponentHandler<IDestroy<S>> getDestroyComponents() {
return this.mDestroyComponents;
}
/** the repair components **/
private final IComponentHandler<IRepair<S>> mRepairComponents;
/**
* Getter for the repair components
*
* @return the repair components
*/
public IComponentHandler<IRepair<S>> getRepairComponents() {
return this.mRepairComponents;
}
/** the global parameters for this ALNS **/
private final ALNSGlobalParameters mGlobalParameters;
/**
* Getter for the global parameters for this ALNS
*
* @return the value of global parameters
*/
public ALNSGlobalParameters getGlobalParameters() {
return this.mGlobalParameters;
}
/** A progress monitor for the procedure */
private ProgressMonitor mProgress;
/**
* Returns the progress monitor for this procedure
*
* @return the progress monitor for this procedure
*/
public ProgressMonitor getProgress() {
return mProgress;
}
private IInstance mCurrentInstance;
/**
* Returns the instance currently solved
*
* @return the instance currently solved
*/
protected IInstance getCurrentInstance() {
return mCurrentInstance;
}
/**
* Creates a new <code>AdaptiveLargeNeighborhoodSearch</code>
*
* @param optimizationSense
* the {@link OptimizationSense}
* @param rndStream
* the random stream used in this ALNS
* @param params
* global parameters for this ALNS
* @param destroyComponents
* the {@link IDestroy destroy components} used in this ALNS
* @param repairComponents
* the {@link IDestroy repair components} used in this ALNS
*/
public AdaptiveLargeNeighborhoodSearch(OptimizationSense optimizationSense, RandomStream rndStream,
ALNSGlobalParameters params, IComponentHandler<IDestroy<S>> destroyComponents,
IComponentHandler<IRepair<S>> repairComponents) {
super(optimizationSense, rndStream);
mCallbacks = new CallbackManagerDelegate<AdaptiveLargeNeighborhoodSearch<S>, ALNSEventType>(
ALNSEventType.class, "alns");
mGlobalParameters = params;
mDestroyComponents = destroyComponents;
mRepairComponents = repairComponents;
mCurrentInstance = null;
}
void initialize(IInstance instance, S solution, IParameters params) {
setRunning();
if (instance != mCurrentInstance) {
for (IALNSComponent<?> comp : mDestroyComponents.getComponents())
comp.initialize(instance);
for (IALNSComponent<?> comp : mRepairComponents.getComponents())
comp.initialize(instance);
mDestroyComponents.initialize(instance);
mRepairComponents.initialize(instance);
}
mCurrentInstance = instance;
mProgress = new ProgressMonitor(params.getMaxIterations(), false);
if (params.getAcceptanceCriterion() != null)
setAcceptanceCriterion(params.getAcceptanceCriterion().clone());
setStoppingCriterion(params.getStoppingCriterion().clone());
// getAcceptanceCriterion().reset();
// getAcceptanceCriterion().initialize();
getStoppingCriterion().reset();
getStoppingCriterion().init();
getLogger().info("ALNS started with solution %s \t(params: %s)", solution, params);
getLogger().info("Destroy: %s", mDestroyComponents);
getLogger().info("Repair : %s", mRepairComponents);
getLogger().debug("Stopping criterion: %s", getStoppingCriterion());
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.STARTED, this, getTimer().readTimeMS(), getProgress()
.getIteration(), instance, solution));
}
@Override
public S localSearch(IInstance instance, S solution, IParameters params) {
if (params.getAcceptanceCriterion() != null)
setAcceptanceCriterion(params.getAcceptanceCriterion());
initialize(instance, solution, params);
S best = solution;
S current = solution;
getProgress().start();
Stopwatch itTimer = new Stopwatch();
while (!getStoppingCriterion().isStopCriterionMet()) {
itTimer.reset();
itTimer.start();
getLogger().lowDebug("ALNS %s: New iteration, stopping criterion: %s", getProgress(),
getStoppingCriterion());
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.IT_STARTED, this, getTimer().readTimeMS(), getProgress()
.getIteration(), current));
@SuppressWarnings("unchecked")
S tmp = (S) current.clone();
// Select destroy operator
IDestroy<S> destroy = mDestroyComponents.nextComponent();
double sizeMin = getGlobalParameters().get(ALNSGlobalParameters.DESTROY_SIZE_RANGE)[0];
double sizeMax = getGlobalParameters().get(ALNSGlobalParameters.DESTROY_SIZE_RANGE)[1];
double size = sizeMin + params.getRandomStream().nextDouble() * (sizeMax - sizeMin);
getLogger().lowDebug("ALNS %s: Selecting destroy %s (size=%.2f)", getProgress(), destroy, size);
// Destroy solution
IDestroyResult<S> result = destroy.destroy(tmp, params, size);
getLogger().lowDebug("ALNS %s: Destroy result: %s ", getProgress(), result);
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.DESTROYED, this, getTimer().readTimeMS(), getProgress()
.getIteration(), best, current, tmp, destroy, result));
// Select repair operator
IRepair<S> repair = mRepairComponents.nextComponent();
getLogger().lowDebug("ALNS %s: Selecting repair %s", getProgress(), repair);
// Repair solution
boolean repaired = repair.repair(tmp, result, params);
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.REPAIRED, this, getTimer().readTimeMS(), getProgress()
.getIteration(), best, current, tmp, repair, repaired));
if (isCheckSolutionAfterMove()) {
String err = checkSolution(tmp);
if (!err.isEmpty()) {
getLogger().warn("ALNS %s: Infeasible temporary solution: %s", getProgress(), err);
}
}
itTimer.stop();
// FIXME Apply an optional post-repair local search
double improvement = getOptimizationSense().getImprovement(current.getObjectiveValue(),
tmp.getObjectiveValue());
if (!repaired) {
getLogger().lowDebug("ALNS %s: Unable to fully repair solution, iteration (sol:%s d:%s r:%s)",
getProgress(), tmp, destroy, repair);
}
// Test the solution and update the acceptance criterion
boolean accept = getAcceptanceCriterion().accept(current, tmp);
boolean compUpdated = false;
if (getOptimizationSense().isBetter(best.getObjectiveValue(), tmp.getObjectiveValue(), false) && accept) {
// A new best solution was found
best = tmp;
current = tmp;
// Update the component evaluation
compUpdated |= mDestroyComponents.updateStats(destroy, improvement, itTimer.readTimeMS(), getProgress()
.getIteration(), Outcome.NEW_BEST);
compUpdated |= mRepairComponents.updateStats(repair, improvement, itTimer.readTimeMS(), getProgress()
.getIteration(), Outcome.NEW_BEST);
getLogger().debug("ALNS %s: New best [%.2f] (d:%s,r:%s)", getProgress(),
current.getObjectiveValue(), destroy, repair);
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.SOL_NEW_BEST, this, getTimer().readTimeMS(),
getProgress().getIteration(), best));
} else if (accept) {
// The new solution is accepted as current solution
current = tmp;
// Update the component evaluation
compUpdated |= mDestroyComponents.updateStats(destroy, improvement, itTimer.readTimeMS(), getProgress()
.getIteration(), Outcome.ACCEPTED);
compUpdated |= mRepairComponents.updateStats(repair, improvement, itTimer.readTimeMS(), getProgress()
.getIteration(), Outcome.ACCEPTED);
getLogger().debug("ALNS %s: New current [%.2f] (d:%s,r:%s)", getProgress(),
current.getObjectiveValue(), destroy, repair);
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.SOL_NEW_CURRENT, this, getTimer().readTimeMS(),
getProgress().getIteration(), best, current));
} else {
getLogger().lowDebug("ALNS %s: Solution rejected (d:%s,r:%s) %s", getProgress(), destroy, repair,
tmp);
// Update the component evaluation
compUpdated |= mDestroyComponents.updateStats(destroy, improvement, itTimer.readTimeMS(), getProgress()
.getIteration(), Outcome.REJECTED);
compUpdated |= mRepairComponents.updateStats(repair, improvement, itTimer.readTimeMS(), getProgress()
.getIteration(), Outcome.REJECTED);
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.SOL_REJECTED, this, getTimer().readTimeMS(),
getProgress().getIteration(), best, current, tmp));
}
if (compUpdated)
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.COMP_UPDATED, this, getTimer().readTimeMS(),
getProgress().getIteration(), best, current, tmp, itTimer.getReadOnlyStopwatch()));
// Update stopping criterion
getStoppingCriterion().update(current);
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.IT_FINISHED, this, getTimer().readTimeMS(), getProgress()
.getIteration(), best, current, tmp, itTimer.getReadOnlyStopwatch()));
getProgress().iterationFinished();
}
getProgress().stop();
//
setStopped();
getCallbacks().callbacks(
new ALNSCallbackEvent<S>(ALNSEventType.FINISHED, this, (long) getTimer().readTimeMS(), getProgress()
.getIteration(), instance, best));
return best;
}
/**
* This methods should be called when the ALNS will no longer be used. It stops the callback threads.
*/
public void stop() {
getCallbacks().stop();
}
@Override
protected void finalize() throws Throwable {
stop();
super.finalize();
};
@Override
public String toString() {
return String.format("D: %s\nR: %s\nParams: %s\nStop: %s\nAccept:%s", getDestroyComponents(),
getRepairComponents(), getGlobalParameters(), getStoppingCriterion(), getAcceptanceCriterion());
}
/**
* Dispose this object to help the garbage collector freeing up memory
*/
@Override
public void dispose() {
mDestroyComponents.dispose();
mRepairComponents.dispose();
}
}