/**
*
*/
package vroom..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.OptimizationSense;
import vroom..datamodel.Solution;
import vroom..datamodel.costDelegates.CostDelegate;
/**
* <code>ParetoFront</code> is an implementation of {@link IPALNSSolutionPool} that maintain a Pareto front for
* {@link Solution 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 ParetoFrontOld implements IPALNSSolutionPool<Solution>, vroom.common.utilities.Cloneable<ParetoFrontOld>,
Cloneable, ILockable {
private final HashSet<ParetoSolution> mAllSolutions;
private final TreeSet<ParetoSolution> mFirstObjSol;
private final TreeSet<ParetoSolution> mSecondObjSol;
private final OptimizationSense mFirstObjSense;
private final OptimizationSense mSecondObjSense;
private final CostDelegate mFirstCostDelegate;
private final CostDelegate 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>secondObjSol</code>
*
* @return the secondObjSol
*/
TreeSet<ParetoSolution> getSecondObjSol() {
return mSecondObjSol;
}
/**
* 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
*/
CostDelegate getFirstCostDelegate() {
return mFirstCostDelegate;
}
/**
* Getter for <code>secondCostDelegate</code>
*
* @return the secondCostDelegate
*/
CostDelegate 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 ParetoFrontOld(CostDelegate firstCostDelegate, OptimizationSense firstObjSense,
CostDelegate secondCostDelegate, OptimizationSense secondObjSense) {
mFirstCostDelegate = firstCostDelegate;
mFirstObjSense = firstObjSense;
mSecondCostDelegate = secondCostDelegate;
mSecondObjSense = secondObjSense;
mFirstObjSol = new TreeSet<ParetoSolution>(new ParetoSolutionComparator(true, mFirstObjSense));
mSecondObjSol = new TreeSet<ParetoSolution>(new ParetoSolutionComparator(false, mSecondObjSense));
mAllSolutions = new HashSet<ParetoFrontOld.ParetoSolution>();
mLock = new ReentrantLock();
}
/**
* Creates a new <code>ParetoFront</code> by cloning {@code original}
*
* @param original
*/
@SuppressWarnings("unchecked")
private ParetoFrontOld(ParetoFrontOld original) {
mFirstCostDelegate = original.mFirstCostDelegate;
mFirstObjSense = original.mFirstObjSense;
mSecondCostDelegate = original.mSecondCostDelegate;
mSecondObjSense = original.mSecondObjSense;
mFirstObjSol = (TreeSet<ParetoSolution>) original.mFirstObjSol.clone();
mSecondObjSol = (TreeSet<ParetoSolution>) original.mSecondObjSol.clone();
mAllSolutions = (HashSet<ParetoSolution>) original.mAllSolutions.clone();
mLock = new ReentrantLock();
}
/**
* Wraps a {@link Solution 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(Solution 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 Solution}
*
* @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 boolean add(Solution solution) {
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);
SortedSet<ParetoSolution> bestSecondObj = mSecondObjSol.tailSet(sol, true);
if (bestFirstObj.isEmpty() || bestSecondObj.isEmpty()
|| (!bestFirstObj.first().dominates(sol) && !bestSecondObj.first().dominates(sol))) {
// The solution is the best in one of the two objectives
// Or the closest solutions better on the first/second objective
// do no strictly dominate the solution
// Check for strictly dominated solutions
HashSet<ParetoSolution> dominatedSol = new HashSet<ParetoSolution>(mFirstObjSol.headSet(sol, true));
dominatedSol.retainAll(mSecondObjSol.headSet(sol, true));
mFirstObjSol.removeAll(dominatedSol);
mSecondObjSol.removeAll(dominatedSol);
mAllSolutions.removeAll(dominatedSol);
addInternal(sol);
return true;
} else {
// The solution is dominated
return false;
}
}
void addInternal(ParetoSolution sol) {
mFirstObjSol.add(sol);
mSecondObjSol.add(sol);
mAllSolutions.add(sol);
}
@Override
public void addAll(Collection<Solution> solutions) {
for (Solution sol : solutions) {
add(sol);
}
}
@Override
public Collection<Solution> subset(int size, RandomStream rndStream) {
ArrayList<ParetoSolution> wrappedSubset = Random.sample(mFirstObjSol, size, rndStream);
ArrayList<Solution> subset = new ArrayList<Solution>(wrappedSubset.size());
for (ParetoSolution sol : wrappedSubset) {
subset.add(sol.getSolution());
}
return subset;
}
@Override
public Solution getBest() {
return mFirstObjSol.last().getSolution();
}
@Override
public int size() {
return mFirstObjSol.size();
}
@Override
public void clear() {
mFirstObjSol.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 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 Collections.unmodifiableSortedSet(mSecondObjSol);
}
/**
* Returns the best solution according to the first objective
*
* @return the best solution according to the first objective
*/
public ParetoSolution getBestFirstObj() {
return mFirstObjSol.last();
}
/**
* Returns the best solution according to the second objective
*
* @return the best solution according to the second objective
*/
public ParetoSolution getBestSecondObj() {
return mSecondObjSol.last();
}
@Override
public Iterator<Solution> iterator() {
return getSolutions().iterator();
}
@Override
public List<Solution> getSolutions() {
ArrayList<Solution> sol = new ArrayList<Solution>(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 Solution mSolution;
/**
* Creates a new <code>ParetoSolution</code>
*
* @param solution
* @param firstObjValue
* @param secondObjValue
*/
public ParetoSolution(Solution 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) {
return ParetoFrontOld.this.mFirstObjSense.isBetter(sol.getFirstObjValue(), getFirstObjValue(), true)
&& ParetoFrontOld.this.mSecondObjSense.isBetter(sol.getSecondObjValue(), getSecondObjValue(), true);
}
/**
* Getter for <code>solution</code>
*
* @return the solution
*/
public Solution 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 [%.3f,%.3f]", mSolution != null ? mSolution.hashCode() : null, mFirstObjValue,
mSecondObjValue);
}
}
/**
* <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 ParetoFrontOld clone() {
return new ParetoFrontOld(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;
}
// ------------------------------------
}