/** * */ package vroom.trsp.optimization.biobj; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; import umontreal.iro.lecuyer.rng.RandomStream; import vroom.common.heuristics.alns.IPALNSSolutionPool; import vroom.common.utilities.ILockable; 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.OptimizationSense; import vroom.trsp.datamodel.TRSPSolution; import vroom.trsp.datamodel.costDelegates.TRSPCostDelegate; /** * <code>ParetoFront</code> is an implementation of {@link IPALNSSolutionPool} that maintain a Pareto front for * {@link TRSPSolution solutions} according to 2 objectives. * <p> * Creation date: Nov 23, 2011 - 1:55:52 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 ParetoFront implements IPALNSSolutionPool<TRSPSolution>, vroom.common.utilities.ICloneable<ParetoFront>, Cloneable, ILockable { private final HashSet<ParetoSolution> mAllSolutions; private final TreeSet<ParetoSolution> mFirstObjSol; private final OptimizationSense mFirstObjSense; private final OptimizationSense mSecondObjSense; private final TRSPCostDelegate mFirstCostDelegate; private final TRSPCostDelegate mSecondCostDelegate; /** * Getter for <code>allSolutions</code> * * @return the allSolutions */ HashSet<ParetoSolution> getAllSolutions() { return mAllSolutions; } /** * Getter for <code>firstObjSol</code> * * @return the firstObjSol */ TreeSet<ParetoSolution> getFirstObjSol() { return mFirstObjSol; } /** * Getter for <code>firstObjSense</code> * * @return the firstObjSense */ OptimizationSense getFirstObjSense() { return mFirstObjSense; } /** * Getter for <code>secondObjSense</code> * * @return the secondObjSense */ OptimizationSense getSecondObjSense() { return mSecondObjSense; } /** * Getter for <code>firstCostDelegate</code> * * @return the firstCostDelegate */ TRSPCostDelegate getFirstCostDelegate() { return mFirstCostDelegate; } /** * Getter for <code>secondCostDelegate</code> * * @return the secondCostDelegate */ TRSPCostDelegate getSecondCostDelegate() { return mSecondCostDelegate; } /** * Creates a new <code>ParetoFront</code> * * @param firstCostDelegate * the cost delegate for the first objective function * @param firstObjSense * the sense of optimization for the first objective function * @param secondCostDelegate * the cost delegate for the second objective function * @param secondObjSense * the sense of optimization for the second objective function */ public ParetoFront(TRSPCostDelegate firstCostDelegate, OptimizationSense firstObjSense, TRSPCostDelegate secondCostDelegate, OptimizationSense secondObjSense) { mFirstCostDelegate = firstCostDelegate; mFirstObjSense = firstObjSense; mSecondCostDelegate = secondCostDelegate; mSecondObjSense = secondObjSense; mFirstObjSol = new TreeSet<ParetoSolution>(new ParetoSolutionComparator(true, mFirstObjSense)); mSecondObjSol = Collections.unmodifiableSortedSet(mFirstObjSol.descendingSet()); mAllSolutions = new HashSet<ParetoFront.ParetoSolution>(); mLock = new ReentrantLock(); } /** * Creates a new <code>ParetoFront</code> by cloning {@code original} * * @param original */ @SuppressWarnings("unchecked") private ParetoFront(ParetoFront original) { mFirstCostDelegate = original.mFirstCostDelegate; mFirstObjSense = original.mFirstObjSense; mSecondCostDelegate = original.mSecondCostDelegate; mSecondObjSense = original.mSecondObjSense; mFirstObjSol = (TreeSet<ParetoSolution>) original.mFirstObjSol.clone(); mSecondObjSol = Collections.unmodifiableSortedSet(mFirstObjSol.descendingSet()); mAllSolutions = (HashSet<ParetoSolution>) original.mAllSolutions.clone(); mLock = new ReentrantLock(); } /** * Wraps a {@link TRSPSolution solution} in a {@link ParetoSolution pareto solution} and store its cost depending on * both objectives * * @param sol * the solution wrap * @return the wrapped solution */ public ParetoSolution wrapSolution(TRSPSolution sol) { return new ParetoSolution(sol, mFirstCostDelegate.evaluateSolution(sol, true, false), mSecondCostDelegate.evaluateSolution(sol, true, false)); } /** * Creates a point in the objective function space wrapped in a {@link ParetoSolution} with no associated * {@link TRSPSolution} * * @param firstObj * the first objective value * @param secondObj * the second objective value * @return a point in the objective function space */ public ParetoSolution newParetoPoint(double firstObj, double secondObj) { return new ParetoSolution(null, firstObj, secondObj); } @Override public void initialize(IInstance instance, TRSPSolution solution, IParameters params) { } @Override public boolean add(TRSPSolution solution, boolean mainIteration) { ParetoSolution sol = wrapSolution(solution); if (mAllSolutions.contains(sol)) return false; if (size() == 0) { // Add the solution to the front addInternal(sol); return true; } SortedSet<ParetoSolution> bestFirstObj = mFirstObjSol.tailSet(sol, true); boolean nonDominated = true; if (bestFirstObj.isEmpty()) { nonDominated = true; } else { // Look for a dominating solution Iterator<ParetoSolution> bfit = bestFirstObj.iterator(); while (bfit.hasNext() && nonDominated) { ParetoSolution bettersolfo = bfit.next(); if (bettersolfo.dominates(sol)) nonDominated = false; // if (bettersolfo.getSolution().getUnservedCount() == 0) // break; } } if (nonDominated) { // && !bestSecondObj.first().dominates(sol))) { // The solution is the best the first objective objectives // Or is better than the closest solutions on the second objective // Check for strictly dominated solutions Iterator<ParetoSolution> it = mFirstObjSol.headSet(sol, true).descendingIterator(); ParetoSolution s; while (it.hasNext() && sol.dominates(s = it.next())) { it.remove(); mAllSolutions.remove(s); } // Add the solution internally addInternal(sol); return true; } else { // The solution is dominated return false; } } /** * Return {@code true} if no solution of the front is dominated by another, {@code false} otherwise * * @return {@code true} if no solution of the front is dominated by another, {@code false} otherwise */ public boolean checkPareto() { for (ParetoSolution s : mAllSolutions) for (ParetoSolution t : mAllSolutions) if (s != t && s.dominates(t)) return false; return true; } void addInternal(ParetoSolution sol) { mFirstObjSol.add(sol); mAllSolutions.add(sol); } @Override public void addAll(Collection<TRSPSolution> solutions, boolean mainIteration) { for (TRSPSolution sol : solutions) { add(sol, false); } } @Override public Collection<TRSPSolution> subset(int size, RandomStream rndStream) { ArrayList<ParetoSolution> wrappedSubset = Random.sample(mFirstObjSol, size, rndStream); ArrayList<TRSPSolution> subset = new ArrayList<TRSPSolution>(wrappedSubset.size()); for (ParetoSolution sol : wrappedSubset) { subset.add(sol.getSolution()); } return subset; } @Override public TRSPSolution getBest() { return mFirstObjSol.last().getSolution(); } @Override public int size() { return mFirstObjSol.size(); } @Override public boolean isFull() { return false; } @Override public void clear() { mFirstObjSol.clear(); mAllSolutions.clear(); // mSecondObjSol.clear(); } /** * Return the solutions in this pareto front in the form of a CSV string * * @return the solutions in this pareto front in the form of a CSV string */ public String toCSVString() { StringBuilder sb = new StringBuilder(size() * 50); sb.append("hash;first_obj;second_obj\n"); for (ParetoSolution s : mFirstObjSol) { sb.append(s.hashCode()); sb.append(";"); sb.append(s.getFirstObjValue()); sb.append(";"); sb.append(s.getSecondObjValue()); sb.append("\n"); } return sb.toString(); } @Override public String toString() { return Utilities.toShortString(mFirstObjSol); } /** * Returns the solutions in this Pareto front sorted according to the first objective * <p> * The {@linkplain SortedSet#last() last solution} is the best * <p> * * @return the solutions in this Pareto front sorted according to the first objective */ public SortedSet<ParetoSolution> getSolutionsFirstObj() { return Collections.unmodifiableSortedSet(mFirstObjSol); } /** * Returns the best solution according to the first objective * * @return the best solution according to the first objective */ public ParetoSolution getBestFirstObj() { return mFirstObjSol.last(); } private final SortedSet<ParetoSolution> mSecondObjSol; /** * Returns the solutions in this Pareto front sorted according to the second objective * <p> * The {@linkplain SortedSet#last() last solution} is the best * <p> * * @return the solutions in this Pareto front sorted according to the second objective */ public SortedSet<ParetoSolution> getSolutionsSecondObj() { return mSecondObjSol; } /** * Returns the best solution according to the second objective * * @return the best solution according to the second objective */ public ParetoSolution getBestSecondObj() { return mFirstObjSol.first(); } @Override public Iterator<TRSPSolution> iterator() { return getSolutions().iterator(); } @Override public List<TRSPSolution> getSolutions() { ArrayList<TRSPSolution> sol = new ArrayList<TRSPSolution>(size()); for (ParetoSolution s : mFirstObjSol) sol.add(s.getSolution()); return sol; } /** * <code>ParetoSolution</code> is used to attach two objective values to a solution. It assumes that the solution * objective values will not change after the object creation. * <p> * Creation date: Nov 23, 2011 - 3:02:39 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 ParetoSolution { private final double mFirstObjValue; private final double mSecondObjValue; private final TRSPSolution mSolution; /** * Creates a new <code>ParetoSolution</code> * * @param solution * @param firstObjValue * @param secondObjValue */ public ParetoSolution(TRSPSolution solution, double firstObjValue, double secondObjValue) { mSolution = solution; mFirstObjValue = firstObjValue; mSecondObjValue = secondObjValue; } /** * Getter for <code>firstObjValue</code> * * @return the firstObjValue */ public double getFirstObjValue() { return mFirstObjValue; } /** * Getter for <code>secondObjValue</code> * * @return the secondObjValue */ public double getSecondObjValue() { return mSecondObjValue; } /** * Check if this solution strictly dominates {@code sol} * * @param sol * the solution to be tested * @return true if this solution strictly dominates {@code sol} */ public boolean dominates(ParetoSolution sol) { OptimizationSense fs = ParetoFront.this.mFirstObjSense; OptimizationSense ss = ParetoFront.this.mFirstObjSense; return ((fs.isBetter(sol.getFirstObjValue(), getFirstObjValue(), false) && ss.isBetter( sol.getSecondObjValue(), getSecondObjValue(), true)) || (fs.isBetter( sol.getFirstObjValue(), getFirstObjValue(), true) && ss.isBetter( sol.getSecondObjValue(), getSecondObjValue(), false))); } /** * Getter for <code>solution</code> * * @return the solution */ public TRSPSolution getSolution() { return mSolution; } /* * (non-Javadoc) * @see java.lang.Object#hashCode() */ @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(mFirstObjValue); result = prime * result + (int) (temp ^ (temp >>> 32)); temp = Double.doubleToLongBits(mSecondObjValue); result = prime * result + (int) (temp ^ (temp >>> 32)); return result; } /* * (non-Javadoc) * @see java.lang.Object#equals(java.lang.Object) */ @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; ParetoSolution other = (ParetoSolution) obj; if (Double.doubleToLongBits(mFirstObjValue) != Double .doubleToLongBits(other.mFirstObjValue)) return false; if (Double.doubleToLongBits(mSecondObjValue) != Double .doubleToLongBits(other.mSecondObjValue)) return false; return true; } @Override public String toString() { return String.format("%s [%s,%s (%s)]", mSolution != null ? mSolution.hashCode() : null, Utilities.format(mFirstObjValue), Utilities.format(mSecondObjValue), mSolution != null ? mSolution.getUnservedCount() : ""); // return String.format("%s [%.3f,%.3f (%s)]", mSolution != null ? mSolution.hashCode() : null, // mFirstObjValue, mSecondObjValue, mSolution != null ? mSolution.getUnservedCount() : ""); } } /** * <code>ParetoSolutionComparator</code> is a comparator for {@link ParetoSolution} that is either based on the * first or second objective. * <p> * Creation date: Nov 23, 2011 - 3:03:14 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 static class ParetoSolutionComparator implements Comparator<ParetoSolution> { private final boolean mFirstObj; private final OptimizationSense mSense; /** * Creates a new <code>ParetoSolutionComparator</code> * * @param firstObj * @param sense */ public ParetoSolutionComparator(boolean firstObj, OptimizationSense sense) { mFirstObj = firstObj; mSense = sense; } @Override public int compare(ParetoSolution o1, ParetoSolution o2) { return mFirstObj ? mSense.compare(o1.getFirstObjValue(), o2.getFirstObjValue()) : mSense.compare(o1.getSecondObjValue(), o2.getSecondObjValue()); } } @Override public ParetoFront clone() { return new ParetoFront(this); } // ------------------------------------ // ILockable interface implementation // ------------------------------------ /** A lock to be used by this instance */ private final ReentrantLock mLock; @Override public boolean tryLock(long timeout) { try { return getLockInstance().tryLock(timeout, TimeUnit.MILLISECONDS); } catch (InterruptedException e) { return false; } } @Override public void acquireLock() { try { if (!getLockInstance().tryLock(TRY_LOCK_TIMOUT, TRY_LOCK_TIMOUT_UNIT)) { throw new IllegalStateException(String.format( "Unable to acquire lock on this instance of %s (%s) after %s %s", this .getClass().getSimpleName(), hashCode(), TRY_LOCK_TIMOUT, TRY_LOCK_TIMOUT_UNIT)); } } catch (InterruptedException e) { throw new IllegalStateException(String.format( "Unable to acquire lock on this instance of %s (%s)", this.getClass() .getSimpleName(), hashCode()), e); } } @Override public void releaseLock() { if (mLock.isLocked()) { this.mLock.unlock(); } } @Override public boolean isLockOwnedByCurrentThread() { return this.mLock.isHeldByCurrentThread(); } @Override public ReentrantLock getLockInstance() { return this.mLock; } // ------------------------------------ }