/** * */ package vroom.common.heuristics; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import umontreal.iro.lecuyer.rng.RandomStream; import vroom.common.utilities.Constants; import vroom.common.utilities.RouletteWheel; import vroom.common.utilities.optimization.IComponentHandler; import vroom.common.utilities.optimization.IInstance; import vroom.common.utilities.optimization.INeighborhood; import vroom.common.utilities.optimization.ISolution; /** * <code>GenericComponentHandler</code> is a general purpose implementation of {@link INeighborhoodHandler} * <p> * Creation date: 2 juil. 2010 - 20:17:03 * * @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 GenericNeighborhoodHandler<S extends ISolution> implements IComponentHandler<INeighborhood<S, ?>> { public static enum Strategy { /** Sequential component exploration (default) */ SEQUENTIAL, /** Random wheel selection based on improvement frequency over all iterations */ FREQUENCY_BASED, /** Random wheel selection based on improvement frequency for a given component */ SELF_FREQUENCY_BASED, /** Random wheel selection based on average efficiency (ratio improvement/time) */ EFFICIENCY_BASED, /** Purely random selection */ RANDOM }; /** The selection strategy used by this handler **/ private Strategy mStrategy; /** * Getter for the selection strategy used by this handler * * @return the value of strategy */ public Strategy getStrategy() { return this.mStrategy; } /** * Setter for the selection strategy used by this handler * * @param strategy * the value to be set for strategy */ public void setStrategy(Strategy strategy) { this.mStrategy = strategy; } /** The list of components */ private final List<INeighborhood<S, ?>> mComponents; @Override public List<INeighborhood<S, ?>> getComponents() { return mComponents; } protected int mCurrentNeighIndex; protected INeighborhood<S, ?> mCurrentComponent; protected final Set<INeighborhood<S, ?>> mExploredComponents; protected final Set<INeighborhood<S, ?>> mUnexploredComponents; /** * Return the set of unexplored components * * @return a set containing the unexplored components * @see #getExploredComponents() */ public Set<INeighborhood<S, ?>> getUnexploredComponents() { return new HashSet<INeighborhood<S, ?>>(mUnexploredComponents); } /** * Return the set of explored components * * @return a set containing the explored components * @see #getUnexploredComponents() */ public Set<INeighborhood<S, ?>> getExploredComponents() { return new HashSet<INeighborhood<S, ?>>(mExploredComponents); } /** Number of times an improving solution was found in the component */ protected final Map<INeighborhood<S, ?>, Integer> mSuccessCount; /** Number of times the component was explored */ protected final Map<INeighborhood<S, ?>, Integer> mExplorationCount; /** Average efficiency metric when exploring the component */ protected final Map<INeighborhood<S, ?>, Double> mEfficiencyMetric; protected int mIterationCount; protected int mInitialFrequency = 1; protected double mInitialPerformance = 1; private final RandomStream mRandom; // Reset strategy protected int mResetIterationThreshold = Integer.MAX_VALUE; protected double mResetProbaThreshold = 0; /** * Set a reset strategy. * <p> * The component memory will be {@linkplain #reset() reset} whenever one of the two following criterion is met: * <ul> * <li>The number of iterations exceeds <code>iterationThreshold</code></li> * <li>The evaluation of selecting a given components is less than <code>probaThreshold*equiProba</code> where * <code>equiProba=1/componentCount</code> is the evaluation of selecting any component when no history is available * </li> * </ul> * </p> * * @param iterationThreshold * a reset threshold on the number of iterations * @param probaThreshold * a reset threshold based on the evaluation of selecting any component (between 0 and 1) */ public void setResetStrategy(int iterationThreshold, double probaThreshold) { if (probaThreshold < 0 || probaThreshold > 1) { throw new IllegalArgumentException( "probaThreshold has to be between 0 and 1 (inclusive) : " + probaThreshold); } if (iterationThreshold < 0) { throw new IllegalArgumentException("iterationThreshold has to be positive : " + iterationThreshold); } mResetIterationThreshold = iterationThreshold; mResetProbaThreshold = probaThreshold; } /** * Creates a default <code>GenericComponentHandler</code> with a {@linkplain Strategy#SEQUENTIAL sequential} * strategy * * @param components * a list of components * @param rndStream * the random stream to be used in this component selection */ public GenericNeighborhoodHandler(List<INeighborhood<S, ?>> components, RandomStream rndStream) { this(Strategy.SEQUENTIAL, components, rndStream); } /** * Creates a new <code>GenericComponentHandler</code> * * @param strategy * an initial component selection strategy * @param components * a list of components * @param rndStream * the random stream to be used in this component selection * @param parentVNS * the parent VNS */ public GenericNeighborhoodHandler(Strategy strategy, List<INeighborhood<S, ?>> components, RandomStream rndStream) { super(); this.mStrategy = strategy; this.mComponents = new LinkedList<INeighborhood<S, ?>>(components); this.mUnexploredComponents = new HashSet<INeighborhood<S, ?>>(); this.mExploredComponents = new HashSet<INeighborhood<S, ?>>(); this.mSuccessCount = new HashMap<INeighborhood<S, ?>, Integer>(); this.mEfficiencyMetric = new HashMap<INeighborhood<S, ?>, Double>(); this.mExplorationCount = new HashMap<INeighborhood<S, ?>, Integer>(); this.mRandom = rndStream; } @Override public void initialize(IInstance instance) { mIterationCount = 1; for (INeighborhood<S, ?> n : getComponents()) { if (!mSuccessCount.containsKey(n)) { mSuccessCount.put(n, mInitialFrequency * mIterationCount); } if (!mEfficiencyMetric.containsKey(n)) { mEfficiencyMetric.put(n, mInitialPerformance); } if (!mExplorationCount.containsKey(n)) { mExplorationCount.put(n, 1); } } mUnexploredComponents.addAll(getComponents()); mExploredComponents.clear(); mCurrentComponent = null; mCurrentNeighIndex = 0; } @Override public boolean updateStats(INeighborhood<S, ?> currentComponent, double improvement, double time, int iteration, Outcome state) { // Prevent zero division time = Math.max(time, 1); // Setup the initial performance metric if (mIterationCount == 1 || mInitialPerformance == 0) { mInitialPerformance = Math.max(improvement / time, 0); } // Initialize success count if (!mSuccessCount.containsKey(currentComponent)) { mSuccessCount.put(currentComponent, mInitialFrequency * mIterationCount); } // Initialize the self frequency if (!mExplorationCount.containsKey(currentComponent)) { mExplorationCount.put(currentComponent, 1); } // Initialize efficiency metric if (!mEfficiencyMetric.containsKey(currentComponent) || mIterationCount == 0) { mEfficiencyMetric.put(currentComponent, mInitialPerformance); } // Update number of exploration int numEx = mExplorationCount.get(currentComponent) + 1; mExplorationCount.put(currentComponent, numEx); // Update efficiency double current = mEfficiencyMetric.get(currentComponent); mEfficiencyMetric.put(currentComponent, (current * (numEx - 1) + improvement / time) / numEx); if (Constants.isStrictlyPositive(improvement)) { // Update success count mSuccessCount.put(currentComponent, mSuccessCount.get(currentComponent) + 1); // Reset current component to the first (used for SEQUENTIAL strategy) mCurrentNeighIndex = 0; // Reset the list of unexplored component mUnexploredComponents.addAll(getComponents()); mExploredComponents.clear(); } else { // Mark the component as explored mUnexploredComponents.remove(currentComponent); mExploredComponents.add(currentComponent); // Move to the next component (used for SEQUENTIAL strategy) mCurrentNeighIndex++; } mIterationCount++; return true; } @Override public void reset() { mSuccessCount.clear(); mExplorationCount.clear(); mEfficiencyMetric.clear(); mUnexploredComponents.addAll(getComponents()); mExploredComponents.clear(); mIterationCount = 1; mCurrentNeighIndex = 0; mCurrentComponent = null; } /** * Reset the component memory */ protected void resetMemory() { for (INeighborhood<S, ?> n : getComponents()) { mSuccessCount.put(n, 1); mExplorationCount.put(n, 1); mEfficiencyMetric.put(n, mInitialPerformance); } } @Override public boolean isCompletelyExplored() { switch (getStrategy()) { case SEQUENTIAL: return mCurrentNeighIndex >= getComponents().size(); case EFFICIENCY_BASED: case SELF_FREQUENCY_BASED: case FREQUENCY_BASED: case RANDOM: return mUnexploredComponents.isEmpty(); default: throw new IllegalStateException("Unsupported strategy :" + getStrategy()); } } @Override public INeighborhood<S, ?> nextComponent() { INeighborhood<S, ?> next = null; if (mIterationCount % mResetIterationThreshold > mResetIterationThreshold) { resetMemory(); } if (getStrategy() == Strategy.SEQUENTIAL) { if (mCurrentNeighIndex < getComponents().size()) { next = getComponents().get(mCurrentNeighIndex); } } else { // Generate the evaluation wheel RouletteWheel<INeighborhood<S, ?>> wheel = new RouletteWheel<INeighborhood<S, ?>>(); double acc = 0; for (INeighborhood<S, ?> n : mUnexploredComponents) { double eval = evalComponent(n); if (eval < mResetProbaThreshold / getComponents().size()) { resetMemory(); eval = 1d / getComponents().size(); } wheel.add(n, eval); acc += eval; } next = wheel.drawObject(mRandom, false); } mCurrentComponent = next; return mCurrentComponent; } /** * Evaluation of a component for the selection wheel * * @param component * @return a value representing the performance of the component */ protected double evalComponent(INeighborhood<S, ?> component) { switch (getStrategy()) { case EFFICIENCY_BASED: return mEfficiencyMetric.get(component); case FREQUENCY_BASED: return mSuccessCount.get(component).doubleValue() / mIterationCount; case SELF_FREQUENCY_BASED: return mSuccessCount.get(component).doubleValue() / mExplorationCount.get(component); case RANDOM: return 1; default: throw new IllegalStateException("Unsupported strategy: " + getStrategy()); } } @Override public String toString() { RouletteWheel<INeighborhood<S, ?>> freq = new RouletteWheel<INeighborhood<S, ?>>(); RouletteWheel<INeighborhood<S, ?>> selfFreq = new RouletteWheel<INeighborhood<S, ?>>(); RouletteWheel<INeighborhood<S, ?>> eff = new RouletteWheel<INeighborhood<S, ?>>(); for (INeighborhood<S, ?> n : getComponents()) { double suc = mSuccessCount.get(n); freq.add(n, suc / mIterationCount); selfFreq.add(n, suc / mExplorationCount.get(n)); eff.add(n, mEfficiencyMetric.get(n)); } return String.format("Unex. Neigh:%s Freq:%s SelfFreq:%s Eff:%s", mUnexploredComponents, freq, selfFreq, eff); } @Override public void dispose() { for (INeighborhood<S, ?> comp : mComponents) { comp.dispose(); } mExploredComponents.clear(); mUnexploredComponents.clear(); mSuccessCount.clear(); mExplorationCount.clear(); mEfficiencyMetric.clear(); mCurrentComponent = null; mComponents.clear(); } @Override public double getWeight(INeighborhood<S, ?> component) { switch (mStrategy) { case SEQUENTIAL: return getComponents().get(mCurrentNeighIndex) == component ? 1 : 0; case FREQUENCY_BASED: case SELF_FREQUENCY_BASED: case EFFICIENCY_BASED: return evalComponent(component); case RANDOM: return mUnexploredComponents.contains(component) ? 1d / mUnexploredComponents.size() : 0; default: return 0; } } }