package vroom.common.heuristics.alns; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map.Entry; import umontreal.iro.lecuyer.rng.RandomStream; import vroom.common.utilities.IDistance; import vroom.common.utilities.Utilities; import vroom.common.utilities.Utilities.Random; import vroom.common.utilities.optimization.IInstance; import vroom.common.utilities.optimization.IParameters; import vroom.common.utilities.optimization.ISolution; import vroom.common.utilities.optimization.OptimizationSense; import vroom.common.utilities.optimization.SAAcceptanceCriterion; /** * <code>DiversifiedPool</code> is an implementation of {@link IPALNSSolutionPool} that maintains a fixed size set of * solutions ordered according to their objective value and their contribution to a diversity metric. * <p> * To work properly this implementation requires that solutions are not modified once added to the pool, and it uses * solutions' {@linkplain #hashCode() hash code} to assert if two solutions are equals. * </p> * <p> * Creation date: Feb 29, 2012 - 10:57:19 AM * * @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 * @param <S> */ public class DiversifiedPool<S extends ISolution> implements IPALNSSolutionPool<S> { public static boolean sAutoAdjustWeights = true; private final OptimizationSense mSense; private final int mMaxSize; private final IDistance<S> mDistance; private final ArrayList<Solution> mWrappedSol; // private final TreeSet<Solution> mDivSol; // private final TreeSet<Solution> mObjSol; private final ArrayList<Solution> mDivSol; private final ArrayList<Solution> mObjSol; private final double[][] mDistances; private final HashMap<Solution, Integer> mIdMapping; private final HashSet<Integer> mSolHashes; private int mFreeId; /** The relative weight of the cost ranking component */ private double mAlpha; /** "cooling rate" for the relative weight */ private double mCoolingRate; @SuppressWarnings("unchecked") public DiversifiedPool(OptimizationSense sense, ALNSGlobalParameters params) { this((IDistance<S>) params.newInstance(ALNSGlobalParameters.DIVERSITY_METRIC), sense, params.get(ALNSGlobalParameters.PALNS_POOL_SIZE)); } public DiversifiedPool(IDistance<S> distance, OptimizationSense sense, int maxSize) { mAlpha = 0.5; mSense = sense; mMaxSize = maxSize; mWrappedSol = new ArrayList<>(maxSize + 1); // mDivSol = new TreeSet<>(mDivComp); // mObjSol = new TreeSet<>(mObjComp); mDivSol = new ArrayList<>(); mObjSol = new ArrayList<>(); mDistance = distance; mDistances = new double[maxSize + 1][maxSize + 1]; mIdMapping = new HashMap<>(maxSize * 2); mSolHashes = new HashSet<>(maxSize * 2); mFreeId = 0; } @Override public void initialize(IInstance instance, S solution, IParameters params) { if (SAAcceptanceCriterion.class .isAssignableFrom(params.getAcceptanceCriterion().getClass())) { // Copy the cooling rate from the acceptance criterion mCoolingRate = ((SAAcceptanceCriterion) params.getAcceptanceCriterion()) .getCoolingRate(); } else { // Ensure that the final alpha is 10 times lower than the initial value mCoolingRate = Math.pow(0.1, 1d / params.getMaxIterations()); } } @Override public boolean add(S solution, boolean mainIteration) { Solution newSol = new Solution(solution); if (!mainIteration) updateAlpha(); if (mSolHashes.contains(newSol.hashCode())) return false; if (!mainIteration && getBest() != null && !mSense.isBetter(getBest(), solution)) return false; evaluateDistancesAdd(newSol); if (size() == getMaxSize()) { // The pool is full // Add the new solution addInternal(newSol); evaluateFitness(); // remove the worst to maintain the pool size Solution worst = removeWorstInternal(); return worst != newSol; } else { // Pool is not full, add the solution to it addInternal(newSol); mFreeId++; return true; } } /** * Update the ratio */ private void updateAlpha() { if (sAutoAdjustWeights) { mAlpha *= mCoolingRate; } } /** * Evaluate the distances when a new solution is added * * @param newSol */ private void evaluateDistancesAdd(Solution newSol) { for (Entry<Solution, Integer> e : mIdMapping.entrySet()) { mDistances[mFreeId][e.getValue()] = mDistance.evaluateDistance(newSol.getSolution(), e .getKey().getSolution()); newSol.mAvgDiversity += mDistances[mFreeId][e.getValue()]; if (mDistance.isSymmetric()) mDistances[e.getValue()][mFreeId] = mDistances[mFreeId][e.getValue()]; else mDistances[mFreeId][e.getValue()] = mDistance.evaluateDistance( newSol.getSolution(), e.getKey().getSolution()); e.getKey().mAvgDiversity = (e.getKey().mAvgDiversity * size() + mDistances[e.getValue()][mFreeId]) / (size() + 1); } newSol.mAvgDiversity /= size() + 1; } /** * Update the stored distances when a solution is removed * * @param remSol */ private void evaluateDistancesRem(Solution remSol) { for (Entry<Solution, Integer> e : mIdMapping.entrySet()) { e.getKey().mAvgDiversity = (e.getKey().mAvgDiversity * (size() + 1) - mDistances[e .getValue()][mFreeId]) / size(); } } private void evaluateFitness() { // Sort the collections Collections.sort(mDivSol, mDivComp); // Update the ranks int i = 0; for (Solution s : mObjSol) s.mObjRank = i++; i = 0; for (Solution s : mDivSol) s.mDivRank = i++; // Update the fitness for (Solution s : mWrappedSol) s.mFitness = s.mObjRank == 0 ? 0 : s.mDivRank == 0 ? 0 : ((1 - mAlpha) * s.mObjRank) / mWrappedSol.size() + (mAlpha * s.mDivRank) / mWrappedSol.size(); // Sort the collection Collections.sort(mWrappedSol); } /** * Add a solution to this pool * * @param s * @return {@code true} if the solution was added, {@code false} if it was already present */ private void addInternal(Solution s) { mWrappedSol.add(s); mObjSol.add(s); Collections.sort(mObjSol, mObjComp); mDivSol.add(s); mIdMapping.put(s, mFreeId); mSolHashes.add(s.hashCode()); } private Solution removeWorstInternal() { Solution worst = mWrappedSol.remove(mWrappedSol.size() - 1); mObjSol.remove(worst); Collections.sort(mObjSol, mObjComp); mDivSol.remove(worst); // if (!mObjSol.remove(worst)) // throw new IllegalStateException("Unable to remove solution " + worst + " from ObjSol"); // if (!mDivSol.remove(worst)) // throw new IllegalStateException("Unable to remove solution " + worst + " from DivSol"); // if (!getSolutionsInternal().remove(worst.getSolution())) // throw new IllegalStateException("Unable to remove solution " + worst // + " from parent solutions"); mFreeId = mIdMapping.get(worst); mIdMapping.remove(worst); mSolHashes.remove(worst.hashCode()); evaluateDistancesRem(worst); return worst; } @Override public void clear() { mWrappedSol.clear(); mObjSol.clear(); mDivSol.clear(); mIdMapping.clear(); mSolHashes.clear(); } @Override public String toString() { StringBuilder b = new StringBuilder(size() * 10); b.append("["); for (Solution sol : mWrappedSol) { b.append(sol.toString()); b.append(","); } if (b.length() > 1) b.setCharAt(b.length() - 1, ']'); else b.append(']'); return String.format("size:%s/%s %s", size(), getMaxSize(), b); } public boolean checkPool() { HashSet<S> wrap = new HashSet<>(); HashSet<S> obj = new HashSet<>(); HashSet<S> div = new HashSet<>(); HashSet<?> sets[] = new HashSet<?>[] { wrap, obj, div }; for (Solution s : mWrappedSol) if (!wrap.add(s.getSolution())) return false; for (Solution s : mObjSol) if (!obj.add(s.getSolution())) return false; for (Solution s : mDivSol) if (!div.add(s.getSolution())) return false; for (int i = 0; i < sets.length; i++) for (int j = i + 1; j < sets.length; j++) if (!Utilities.Math.equals(sets[i], sets[j])) return false; return true; } /** * <code>Solution</code> is a wrapping class that stores diversity measure and overall evaluation of a solution */ private class Solution implements Comparable<Solution> { private final S mSolution; private final int mHash; private double mAvgDiversity; private int mObjRank; private int mDivRank; private double mFitness; private Solution(S solution) { super(); mSolution = solution; mHash = solution.hashSolution(); } @Override public int compareTo(Solution s) { if (this == s) return 0; int comp = Double.compare(this.mFitness, s.mFitness); return comp == 0 ? DiversifiedPool.this.mObjComp.compare(this, s) : comp; } @Override public int hashCode() { return mHash; } @SuppressWarnings("unchecked") @Override public boolean equals(Object obj) { return mHash == ((Solution) obj).mHash; } private S getSolution() { return mSolution; } @Override public String toString() { return String.format("%.1f:%.1f (%s/%s/%.1f)", mSolution.getObjectiveValue(), mAvgDiversity, mObjRank, mDivRank, mFitness); } } private final SolutionObjComparator mObjComp = new SolutionObjComparator(); private class SolutionObjComparator implements Comparator<Solution> { @Override public int compare(Solution o1, Solution o2) { if (o1 == o2) return 0; // [12/07/13] removed specific case when o1==o2 to prevent exception in sorting methods (since 1.7) return -mSense.compare(o1.getSolution(), o2.getSolution()); } } private final SolutionDivComparator mDivComp = new SolutionDivComparator(); private class SolutionDivComparator implements Comparator<Solution> { @Override public int compare(Solution o1, Solution o2) { if (o1 == o2) return 0; // [12/07/13] removed specific case when o1==o2 to prevent exception in sorting methods (since 1.7) return -Double.compare(o1.mAvgDiversity, o2.mAvgDiversity); } } /** * Returns the max size for this pool * * @return the max size for this pool */ public int getMaxSize() { return mMaxSize; } @Override public Iterator<S> iterator() { return getSolutions().iterator(); } @Override public void addAll(Collection<S> solutions, boolean mainIteration) { for (S s : solutions) add(s, mainIteration); } @Override public Collection<S> subset(int size, RandomStream rndStream) { return Random.sample(getSolutions(), size, rndStream); } @Override public Collection<S> getSolutions() { ArrayList<S> sol = new ArrayList<>(size()); for (Solution s : mObjSol) sol.add(s.getSolution()); return sol; } @Override public S getBest() { return size() > 0 ? mObjSol.get(0).getSolution() : null; } @Override public int size() { return mWrappedSol.size(); } @Override public boolean isFull() { return size() >= getMaxSize(); } }