/**
*
*/
package vroom.common.heuristics.alns;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import umontreal.iro.lecuyer.rng.RandomStream;
import vroom.common.utilities.RouletteWheel;
import vroom.common.utilities.optimization.IComponentHandler;
import vroom.common.utilities.optimization.IInstance;
/**
* <code>ALNSComponentHandler</code> is a generic implementation of the ALNS destroy/repair selection process presented
* in
* <p>
* Ropke, S. & Pisinger, D.<br/>
* An adaptive large neighborhood search heuristic for the pickup and delivery problem with time windows<br/>
* Transportation Science, 2006, 40, 455-472
* </p>
* <p>
* Creation date: May 12, 2011 - 1:49:12 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 ALNSComponentHandler<M extends IALNSComponent<?>> implements IComponentHandler<M> {
public static boolean sNewWeightUpdate = true;
/** the increment applied for components that lead to a best solution **/
private final double mSigmaOne;
/**
* Getter for the increment applied for components that lead to a best solution
*
* @return the value of sigmaOne
*/
public double getSigmaOne() {
return this.mSigmaOne;
}
/** the increment applied for components that lead to a different and better current solution **/
private final double mSigmaTwo;
/**
* Getter for the increment applied for components that lead to a different and better current solution
*
* @return the value of sigmaTwo
*/
public double getSigmaTwo() {
return this.mSigmaTwo;
}
/** the increment applied for components that lead to a different non-improving current solution **/
private final double mSigmaThree;
/**
* Getter for the increment applied for components that lead to a different non-improving current solution
*
* @return the value of sigmaThree
*/
public double getSigmaThree() {
return this.mSigmaThree;
}
/** the reaction factor **/
private final double mReactionFactor;
/**
* Getter for the reaction factor
*
* @return the value of reactionFactor
*/
public double getReactionFactor() {
return this.mReactionFactor;
}
/** the length of the time segments in number of iterations **/
private final int mTimeSegmentLength;
/**
* Getter for the length of the time segments in number of iterations
*
* @return the value of timeSegmentLength
*/
public int getTimeSegmentLength() {
return this.mTimeSegmentLength;
}
/** the score of each component */
private final Map<M, Evaluation> mEvaluations;
/** the current selection wheel, replaced at the beginning of each segment (to prevent unecessary synchronization) */
private RouletteWheel<M> mWheel;
/** a random source */
private final RandomStream mRndStream;
/**
* Creates a new <code>ALNSComponentHandler</code>.
*
* @param rndStream
* a random stream
* @param sigma1
* the sigma1
* @param sigma2
* the sigma2
* @param sigma3
* the sigma3
* @param reactionFactor
* the reaction factor
* @param timeSegLength
* the length of each time segment
*/
public ALNSComponentHandler(RandomStream rndStream, Collection<M> components, double sigma1, double sigma2,
double sigma3, double reactionFactor, int timeSegLength) {
mRndStream = rndStream;
mEvaluations = new HashMap<M, Evaluation>();
mWheel = new RouletteWheel<M>();
mSigmaOne = sigma1;
mSigmaTwo = sigma2;
mSigmaThree = sigma3;
mReactionFactor = reactionFactor;
mTimeSegmentLength = timeSegLength;
for (M c : components) {
mEvaluations.put(c, new Evaluation(0, 0, 1d / components.size()));
}
reset();
}
@Override
public List<M> getComponents() {
return new ArrayList<M>(mEvaluations.keySet());
}
/**
* Returns a copy of the evaluation of a component
*
* @return a copy of the evaluation of a component
*/
public Evaluation getEvaluation(M component) {
Evaluation e = mEvaluations.get(component);
return e != null ? e.clone() : null;
}
@Override
public double getWeight(M component) {
return getEvaluation(component).getWeight();
};
@Override
public M nextComponent() {
return mWheel.drawObject(mRndStream, false);
}
@Override
public boolean isCompletelyExplored() {
return false;
}
@Override
public boolean updateStats(M currentComponent, double improvement, double time, int iteration, Outcome state) {
boolean changed = false;
if (iteration % mTimeSegmentLength == 0) {
// Update weights and reset scores and counts
updateWeights();
for (Entry<M, Evaluation> eval : mEvaluations.entrySet()) {
eval.getValue().reset();
}
changed = true;
}
// Update score
switch (state) {
case NEW_BEST:
mEvaluations.get(currentComponent).updateScore(getSigmaOne());
break;
case ACCEPTED:
if (improvement > 0)
mEvaluations.get(currentComponent).updateScore(getSigmaTwo());
else
mEvaluations.get(currentComponent).updateScore(getSigmaThree());
break;
case REJECTED:
// Do nothing
break;
default:
throw new IllegalArgumentException("Unsupported state: " + state);
}
// Update count
mEvaluations.get(currentComponent).increaseCount();
return changed;
}
/**
* Update the weights and the selection wheel
*/
void updateWeights() {
RouletteWheel<M> wheel = new RouletteWheel<>();
if (sNewWeightUpdate) {
double overallScore = 0;
for (Entry<M, Evaluation> eval : mEvaluations.entrySet()) {
overallScore += eval.getValue().getScore();
}
if (overallScore == 0)
overallScore = 1;
for (Entry<M, Evaluation> eval : mEvaluations.entrySet()) {
eval.getValue().updateWeight(getReactionFactor(), eval.getValue().getScore() / overallScore);
wheel.add(eval.getKey(), eval.getValue().getWeight());
}
} else {
for (Entry<M, Evaluation> eval : mEvaluations.entrySet()) {
Evaluation e = eval.getValue();
if (e.getCount() != 0)
eval.getValue().updateWeight(getReactionFactor());
wheel.add(eval.getKey(), eval.getValue().getWeight());
}
}
// Replace previous wheel
mWheel = wheel;
}
@Override
public void initialize(IInstance instance) {
reset();
}
@Override
public void reset() {
RouletteWheel<M> wheel = new RouletteWheel<>();
for (M comp : mEvaluations.keySet()) {
Evaluation e = new Evaluation(0, 0, 1d / mEvaluations.size());
mEvaluations.put(comp, e);
wheel.add(comp, e.getWeight());
}
// Replace previous wheel
mWheel = wheel;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(String.format("s1=%s,s2=%s,s3=%s,r=%s,T=%s", getSigmaOne(), getSigmaTwo(), getSigmaThree(),
getReactionFactor(), getTimeSegmentLength()));
sb.append("[");
Iterator<Entry<M, Evaluation>> it = mEvaluations.entrySet().iterator();
while (it.hasNext()) {
Entry<M, Evaluation> e = it.next();
sb.append(String.format("%s (%s)", e.getKey(), e.getValue()));
if (it.hasNext())
sb.append(",");
}
sb.append("]");
return sb.toString();
}
/**
* <code>Evaluation</code> is a simple wrapper for the different aspect of a component evaluation
* <p>
* Creation date: May 12, 2011 - 3:35:58 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
*/
protected static class Evaluation implements Cloneable {
private int mCount;
private double mScore;
private double mWeight;
/**
* Reset the score and count of this evaluation
*/
private void reset() {
this.mScore = 0;
this.mCount = 0;
}
/**
* Update the score by adding the given <code>delta</code> value
*
* @param delta
*/
private void updateScore(double delta) {
this.mScore += delta;
}
/**
* Getter for <code>score</code>
*
* @return the score
*/
protected double getScore() {
return mScore;
}
/**
* Increase the counter
*/
private void increaseCount() {
this.mCount++;
}
/**
* Getter for <code>count</code>
*
* @return the count
*/
protected int getCount() {
return mCount;
}
/**
* Getter for <code>weight</code>
*
* @return the weight
*/
protected double getWeight() {
return mWeight;
}
/**
* Update this evaluation weight
*
* @param reactionFactor
*/
private void updateWeight(double reactionFactor) {
if (getCount() > 0)
this.mWeight = getWeight() * (1 - reactionFactor) + reactionFactor * getScore() / getCount();
}
/**
* Update this evaluation weight
*
* @param reactionFactor
* the reaction factor to be used
* @param newWeight
* the weight that will be assigned if <code>reactionFactor=1</code>
*/
public void updateWeight(double reactionFactor, double newWeight) {
this.mWeight = getWeight() * (1 - reactionFactor) + reactionFactor * newWeight;
}
/**
* Creates a new <code>Evaluation</code>
*
* @param count
* @param score
* @param weight
*/
private Evaluation(int count, double score, double weight) {
this.mCount = count;
this.mScore = score;
this.mWeight = weight;
}
@Override
public String toString() {
return String.format("c:%s,s:%.2f,w:%.2f", getCount(), getScore(), getWeight());
}
@Override
protected Evaluation clone() {
return new Evaluation(getCount(), getScore(), getWeight());
}
}
@Override
public void dispose() {
for (M comp : mEvaluations.keySet()) {
comp.dispose();
}
mEvaluations.clear();
mWheel.clear();
}
}