package vroom.common.heuristics.vns; import java.util.Arrays; import java.util.List; import umontreal.iro.lecuyer.rng.RandomStream; import vroom.common.heuristics.GenericNeighborhoodHandler; import vroom.common.heuristics.Identity; import vroom.common.heuristics.LocalSearchBase; import vroom.common.heuristics.utils.HeuristicsLogging; import vroom.common.heuristics.vrp.IVRPSolutionNeighborhood; import vroom.common.utilities.Stopwatch; import vroom.common.utilities.logging.LoggerHelper; import vroom.common.utilities.optimization.IComponentHandler; import vroom.common.utilities.optimization.IInstance; import vroom.common.utilities.optimization.ILocalSearch; import vroom.common.utilities.optimization.INeighborhood; import vroom.common.utilities.optimization.IParameters; import vroom.common.utilities.optimization.ISolution; import vroom.common.utilities.optimization.IStoppingCriterion; import vroom.common.utilities.optimization.OptimizationSense; import vroom.common.utilities.optimization.SimpleParameters; import vroom.common.utilities.optimization.SimpleStoppingCriterion; /** * <code>VariableNeighborhoodSearch</code> is an implementation of. * * @param <S> * the generic type {@link ILocalSearch} that uses different {@linkplain IVRPSolutionNeighborhood * neighborhoods} to explore the vicinity of a mSolution. * <p> * <b>Pseudo-code:</b> * </p> * <p> * <code> * <b>input :</b> S - a solution <br/> * <b>output:</b> S* - the best solution found * </p> * <p> * S* <- S<br/> * k <- 1 // Select first neighborhood<br/> * while !stopCriterion() <br/> *  S’ <- shake(S,Nk) // Generate neighbor <br/> *  S’ <- ls(S’,N,k) // Improve neighbor <br/> *  if f(S’) < f(S) // Improvement found <br/> *   S <- S’ k <- 1 // Select first neighborhood <br/> *  else <br/> *   k <- k+1 // Select next neighborhood <br/> *  end-if<br/> * end-while<br/> * return S<br/> * </code> * </p> * <p> * Creation date: Apr 26, 2010 - 4:38:07 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 VariableNeighborhoodSearch<S extends ISolution> extends LocalSearchBase<S> { /** * <code>VNSVariant</code> is an enumeration of some generic VNS variants that can be used to instantiate a * {@link VariableNeighborhoodSearch} with default components. * <p> * Creation date: 11 juil. 2010 - 20:57:55 * * @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 static enum VNSVariant { /** Descendant Variable Neighborhood Search with best improvement. */ VND(true, true, false), /** Reduced Variable Neighborhood Search. */ RVNS(true, true, true), /** Basic Variable Neighborhood Search. */ BVNS(false, false, false), /** General Variable Neighborhood Search. */ GVNS(false, false, false); /** <code>true</code> if no shake should be applied. */ protected final boolean identityShake; /** * <code>true</code> if the local search should explore the current neighborhood. */ protected final boolean neighLS; /** <code>true</code> if the local search should be randomized. */ protected final boolean rndLS; private VNSVariant(boolean identityShake, boolean neighLS, boolean rndLS) { this.identityShake = identityShake; this.neighLS = neighLS; this.rndLS = rndLS; } } /** The Constant LOGGER. */ public final static LoggerHelper LOGGER = HeuristicsLogging .getLogger("VNS"); /** the neighborhood handler associated with this VNS *. */ private final IComponentHandler<INeighborhood<S, ?>> mNeighHandler; /** * Getter for the neighborhood handler associated with this VNS. * * @return the value of neighHandler */ public IComponentHandler<INeighborhood<S, ?>> getNeighHandler() { return this.mNeighHandler; } /** <code>true</code> if the shake operator should be applied */ private final boolean mShake; /** the local search operator *. */ private ILocalSearch<S> mLocalSearch; /** * Getter for the local search operator. * * @return the value of localSearch */ public ILocalSearch<S> getLocalSearch() { return this.mLocalSearch; } /** * Setter for the local search operator. * * @param localSearch * the value to be set for localSearch */ public void setLocalSearch(ILocalSearch<S> localSearch) { this.mLocalSearch = localSearch; } /** The Shake parameters. */ private IParameters mLSParameters, mShakeParameters; /** * Creates a new <code>VariableNeighborhoodSearch</code>. * * @param sense * the optimization sense * @param localSearch * the local search component, if <code>null</code>, then the current neighborhood will be used as local * search. * @param neighborhoods * a list of neighborhoods to be used in this vns * @param shake * <code>true</code> if the shake operator should be applied with the current neighborhood * @param rndStream * the {@link RandomStream} to be used in this instance */ public VariableNeighborhoodSearch(OptimizationSense sense, ILocalSearch<S> localSearch, List<INeighborhood<S, ?>> neighborhoods, boolean shake, RandomStream rndStream) { super(sense); mLocalSearch = localSearch; mNeighHandler = new GenericNeighborhoodHandler<S>(neighborhoods, rndStream); setDefaultParameters(VNSVariant.VND); mShake = shake; setRandomStream(rndStream); } /* * (non-Javadoc) * @see vroom.common.heuristics.ILocalSearch#perfomLocalSearch(vroom.common.utilities .optimization.IInstance, * vroom.common.utilities.optimization.ISolution, vroom.common.utilities.optimization.IParameters) */ @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public synchronized S localSearch(IInstance instance, S solution, IParameters params) { if (solution == null) { throw new IllegalArgumentException("Argument mSolution cannot be null"); } setRunning(); getNeighHandler().initialize(null); if (params == null) { params = SimpleParameters.newUnlimitedThoroughDetParameters(); } // Clone the mSolution S sol = (S) solution.clone(); IParameters oldLSParams = mLSParameters; IParameters oldShakeParams = mShakeParameters; IStoppingCriterion stop = null; if (params instanceof VNSParameters) { VNSParameters vnsParams = (VNSParameters) params; stop = vnsParams.getStoppingCriterion(); mLSParameters = vnsParams.getLSParameters(); mShakeParameters = vnsParams.getShakeParameters(); } else { stop = new SimpleStoppingCriterion(params.getMaxTime(), params.getMaxIterations()); } stop.init(); LOGGER.info("VNS started with solution %s \t(params: %s)", solution, params); LOGGER.info("Neighborhoods: %s", getNeighHandler()); LOGGER.debug("Stopping criterion: %s", stop); int iteration = 0; while (!stop.isStopCriterionMet() && !getNeighHandler().isCompletelyExplored()) { getMainTimer().reset(); INeighborhood<S, ?> neigh = getNeighHandler().nextComponent(); if (neigh == null) { break; } S temp = (S) sol.clone(); LOGGER.debug("Current solution: %s", temp); LOGGER.debug("Selecting neighborhood %s", neigh); getMainTimer().start(); // Shake if (mShake) { neigh.pertub(instance, temp, getShakeParameters(neigh, params)); LOGGER.debug("Shake: \t(neigh:%s, time:%s, sol:%s)", neigh, getMainTimer() .readTimeMS(), temp); } // Local search ILocalSearch<S> ls = getLocalSearch() == null ? neigh : getLocalSearch(); Stopwatch lsTimer = new Stopwatch(); lsTimer.start(); temp = ls.localSearch(instance, temp, params); lsTimer.stop(); if (!(ls instanceof Identity)) { LOGGER.debug("Local search finished \t(new sol:%s, time:%s", temp, lsTimer.readTimeMS()); } getMainTimer().stop(); double improvement = getOptimizationSense().getImprovement(sol.getObjectiveValue(), temp.getObjectiveValue()); if (getAcceptanceCriterion().accept(sol, temp)) { LOGGER.debug( "New solution accepted \t(neigh:%s, improv:%s, new sol:%s, acceptanceCriterion:%s)", neigh, improvement, temp, getAcceptanceCriterion()); sol = temp; getNeighHandler().updateStats(neigh, improvement, getMainTimer().readTimeMS(), iteration, IComponentHandler.Outcome.ACCEPTED); } else { LOGGER.debug( "New solution rejected \t(neigh:%s, improv:%s, new sol:%s, acceptanceCriterion:%s)", neigh, improvement, temp, getAcceptanceCriterion()); getNeighHandler().updateStats(neigh, improvement, getMainTimer().readTimeMS(), iteration, IComponentHandler.Outcome.REJECTED); } stop.update(improvement); iteration++; } setStopped(); LOGGER.info("VNS finished, final solution \t(obj change:%s) %s", ((Comparable) sol.getObjective()).compareTo(solution.getObjective()), sol); mLSParameters = oldLSParams; mShakeParameters = oldShakeParams; return sol; } /** * Reset this variable neighborhood search. */ public void reset() { getNeighHandler().reset(); } /** * Return the parameters to be used for the given neighborhood. * * @param neigh * the current neighborhood * @param params * the vns parameters * @return the parameters to be used for the given neighborhood */ protected IParameters getShakeParameters(INeighborhood<S, ?> neigh, IParameters params) { return mShakeParameters; } /** * Return the parameters to be used for the local search procedure. * * @param neigh * the current neighborhood * @param params * the vns parameters * @return the parameters to be used for the local search procedure */ protected IParameters getLSParameters(INeighborhood<S, ?> neigh, IParameters params) { return mLSParameters; } // --------------------------------------- // Factory methods // --------------------------------------- /** * Factory method to create a new VNS. * * @param <S> * the generic type * @param variant * the vns variant to be instantiated * @param sense * the optimization sense * @param ls * the local search component (can be <code>null</code>) * @param rndStream * the random stream that will be used in the created vns * @param neighborhoods * the list of neighborhoods to be used * @return a new variable neighborhood search */ public static <S extends ISolution> VariableNeighborhoodSearch<S> newVNS(VNSVariant variant, OptimizationSense sense, ILocalSearch<S> ls, RandomStream rndStream, List<INeighborhood<S, ?>> neighborhoods) { if (variant.neighLS && ls != null) { throw new IllegalArgumentException(String.format( "Local search operator should be null (vns variant:%s ls:%s)", variant, ls)); } VariableNeighborhoodSearch<S> vns = new VariableNeighborhoodSearch<S>(sense, ls, neighborhoods, !variant.identityShake, rndStream); vns.setDefaultParameters(variant); return vns; } /** * Factory method to create a new VNS. * * @param <S> * the generic type * @param variant * the vns variant to be instantiated * @param sense * the optimization sense * @param ls * the local search component (can be <code>null</code>) * @param rndStream * the random stream that will be used in the created vns * @param neighborhoods * the list of neighborhoods to be used * @return a new variable neighborhood search */ public static <S extends ISolution> VariableNeighborhoodSearch<S> newVNS(VNSVariant variant, OptimizationSense sense, ILocalSearch<S> ls, RandomStream rndStream, INeighborhood<S, ?>... neighborhoods) { return newVNS(variant, sense, ls, rndStream, Arrays.asList(neighborhoods)); } /** * Sets the default shake and ls parameters for the given vns variant. * <p> * Note that these parameters can be overridden when calling * * @param variant * the new default parameters {@link #localSearch(IInstance, ISolution, IParameters)} */ private synchronized void setDefaultParameters(VNSVariant variant) { mLSParameters = SimpleParameters.newUnlimitedThoroughDetParameters(); mShakeParameters = SimpleParameters.PERTURBATION; ((SimpleParameters) mShakeParameters).setRandomStream(getRandomStream()); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { if (this.getLocalSearch() != null) { return String.format("%s \n Shake:%s (%s)\n LS:%s (%s)", this.getClass() .getSimpleName(), getNeighHandler().getComponents(), mShakeParameters, getLocalSearch(), mLSParameters); } else { return String.format("%s \n Shake: Identity \n LS:%s (%s)", this.getClass() .getSimpleName(), getNeighHandler().getComponents(), mLSParameters); } } @Override public void dispose() { mNeighHandler.dispose(); if (mLocalSearch != null) { mLocalSearch.dispose(); mLocalSearch = null; } } }