/*******************************************************************************
* Copyright 2006 - 2012 Vienna University of Technology,
* Department of Software Technology and Interactive Systems, IFS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This work originates from the Planets project, co-funded by the European Union under the Sixth Framework Programme.
******************************************************************************/
package eu.scape_project.planning.model.sensitivity;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import eu.scape_project.planning.model.Alternative;
import eu.scape_project.planning.model.aggregators.IAggregator;
import eu.scape_project.planning.model.beans.ResultNode;
import eu.scape_project.planning.model.tree.TreeNode;
/**
* This sensitivity analysis test is similar to the OrderChangeCountTest.
*
* It first determines the "normal" ordering of alternatives (which is best, which is worst etc.). After each iteration
* (after each modification to the importance factors) the order of alternatives is recalculated. The new ordering is
* than compared with the normal ordering using the Spearman's rank correlation coefficient.
*
* The correlation coefficient is 0 if the ordering hasn't changed.
*
* The coefficients of all iterations for each node are added together and then devided by the number of iteration
* (= compute the average correlation coefficient). If this value exceeds a certain threshold the node is considered
* sensitive (the weight modification produces "too strong changes" in the ordering of the alternatives).
*
* @author Jan Zarnikov
*
*/
public class RangCorrelationTest extends OrderChangeTest {
private double rangCorrelationSum = 0;
private int roundCount = 0;
public RangCorrelationTest(TreeNode root, IAggregator aggregator, List<Alternative> alternatives) {
super(root, aggregator, alternatives);
}
public void afterIteration(ResultNode node) {
roundCount++;
SortedSet<ComparableAlternative> order = getOrder();
if(order.size() != normalOrder.size()) {
// huh?! how did this happen? someone has modified the alternatives list
return;
}
rangCorrelationSum += Math.abs(getRankCorrelation(order, normalOrder));
}
/**
* Compute Spearman's rank correlation coefficient
* @param ranks1 Sorted non-empty set of ComparableAlternatives, it doesn't matter whether they are sorted
* in ascending or descending order.
* @param ranks2 Must be a set of the same Alternatives sorted using the same Comparator as ranks1.
* Of course the sorting might be different (because of the different results).
*
* @return 0 if the ordering of rangs1 and rangs2 is the same. Otherwise the bigger the difference in the ordering
* of the alternatives the bigger the returned value.
*/
private double getRankCorrelation(SortedSet<ComparableAlternative> ranks1, SortedSet<ComparableAlternative> ranks2) {
double n = ranks1.size();
// avoid devision by 0 further bellow
if(n == 0) {
return 0;
}
// the following two maps should have the same key set.
Map<ComparableAlternative, Double> rankedAlternatives1 = getRankings(ranks1);
Map<ComparableAlternative, Double> rankedAlternatives2 = getRankings(ranks2);
double rankDifferenceSum = 0;
for(ComparableAlternative ca : rankedAlternatives1.keySet()) {
double difference = rankedAlternatives1.get(ca) - rankedAlternatives2.get(ca);
rankDifferenceSum += difference*difference;
}
double result = ((6 * rankDifferenceSum) / (n * (n * n - 1)));
return result;
}
/**
* Computes the rankings of a sorted set of Alternatives.
*
* Example:
*
* Alternative_A = 150
* Alternative_B = 110
* Alternative_C = 110
* Alternative_D = 70
*
* will result in:
*
* Alternative_A -> 1
* Alternative_B -> 2.5
* Alternative_C -> 2.5
* Alternative_D -> 4
*
* Note that B and C share third and fourth place.
* Works just like in sport events on TV ;-)
*
* @param alternatives
* @return
*/
private Map<ComparableAlternative, Double> getRankings(SortedSet<ComparableAlternative> alternatives) {
List<ComparableAlternative> buffer = new LinkedList<ComparableAlternative>();
Map<ComparableAlternative, Double> result = new HashMap<ComparableAlternative, Double>();
// the set is already sorted so this should iterate in ascending order.
for(ComparableAlternative ca : alternatives) {
if(buffer.isEmpty()) {
buffer.add(ca);
} else {
// if we have the same value as the previous alternative then add it to the buffer
if(ca.getValue() == buffer.get(0).getValue()) {
buffer.add(ca);
} else {
// flush the buffer
for(ComparableAlternative bufferedAlternative : buffer) {
double rank = alternatives.size() - result.size() - (buffer.size() - 1)/2;
result.put(bufferedAlternative, rank);
}
// and add this ca to the newly created buffer
buffer = new LinkedList<ComparableAlternative>();
buffer.add(ca);
}
}
}
// flush the last values from the buffer.
for(ComparableAlternative bufferedAlternative : buffer) {
double rank = alternatives.size() - result.size() - (buffer.size() - 1)/2;
result.put(bufferedAlternative, rank);
}
return result;
}
public void afterNode(ResultNode node) {
node.setSensitivityAnalysisResult(new RangCorrelationResult(rangCorrelationSum, roundCount));
}
public void beforeNode(ResultNode node) {
rangCorrelationSum = 0;
roundCount = 0;
}
/**
* A simple container for storing the results of the sensitivity analysis.
*
* It stores the sum of all correlation coefficients (all iterations over a node) and the number of nodes.
* The quotient of these both is used as a result (= the average correlation coefficient).
*
* @author Jan Zarnikov
*
*/
private static class RangCorrelationResult implements ISensitivityAnalysisResult {
private double correlationSum = 0;
private int rounds = 0;
private static final double THRESHOLD = 0.049;
public RangCorrelationResult(double correlationSum, int rounds) {
super();
this.correlationSum = correlationSum;
this.rounds = rounds;
}
public double getSensitivityCoefficient() {
return correlationSum / rounds;
}
public double getSensitivityThreashold() {
return THRESHOLD;
}
public boolean isSensitive() {
return getSensitivityCoefficient() > getSensitivityThreashold();
}
}
}