/*
* Copyright (c) 2003, the JUNG Project and the Regents of the University
* of California
* All rights reserved.
*
* This software is open-source under the BSD license; see either
* "license.txt" or
* http://jung.sourceforge.net/license.txt for a description.
*/
package edu.uci.ics.jung.algorithms.importance;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.collections15.Factory;
import edu.uci.ics.jung.graph.DirectedGraph;
/**
* This algorithm measures the importance of nodes based upon both the number and length of disjoint paths that lead
* to a given node from each of the nodes in the root set. Specifically the formula for measuring the importance of a
* node is given by: I(t|R) = sum_i=1_|P(r,t)|_{alpha^|p_i|} where alpha is the path decay coefficient, p_i is path i
* and P(r,t) is a set of maximum-sized node-disjoint paths from r to t.
* <p>
* This algorithm uses heuristic breadth-first search to try and find the maximum-sized set of node-disjoint paths
* between two nodes. As such, it is not guaranteed to give exact answers.
* <p>
* A simple example of usage is:
* <pre>
* WeightedNIPaths ranker = new WeightedNIPaths(someGraph,2.0,6,rootSet);
* ranker.evaluate();
* ranker.printRankings();
* </pre>
*
* @author Scott White
* @see "Algorithms for Estimating Relative Importance in Graphs by Scott White and Padhraic Smyth, 2003"
*/
public class WeightedNIPaths<V,E> extends AbstractRanker<V,E> {
public final static String WEIGHTED_NIPATHS_KEY = "jung.algorithms.importance.WEIGHTED_NIPATHS_KEY";
private double mAlpha;
private int mMaxDepth;
private Set<V> mPriors;
private Map<E,Number> pathIndices = new HashMap<E,Number>();
private Map<Object,V> roots = new HashMap<Object,V>();
private Map<V,Set<Number>> pathsSeenMap = new HashMap<V,Set<Number>>();
private Factory<V> vertexFactory;
private Factory<E> edgeFactory;
/**
* Constructs and initializes the algorithm.
* @param graph the graph whose nodes are being measured for their importance
* @param alpha the path decay coefficient (>= 1); 2 is recommended
* @param maxDepth the maximal depth to search out from the root set
* @param priors the root set (starting vertices)
*/
public WeightedNIPaths(DirectedGraph<V,E> graph, Factory<V> vertexFactory,
Factory<E> edgeFactory, double alpha, int maxDepth, Set<V> priors) {
super.initialize(graph, true,false);
this.vertexFactory = vertexFactory;
this.edgeFactory = edgeFactory;
mAlpha = alpha;
mMaxDepth = maxDepth;
mPriors = priors;
for (V v : graph.getVertices()) {
super.setVertexRankScore(v, 0.0);
}
}
protected void incrementRankScore(V v, double rankValue) {
setVertexRankScore(v, getVertexRankScore(v) + rankValue);
}
protected void computeWeightedPathsFromSource(V root, int depth) {
int pathIdx = 1;
for (E e : getGraph().getOutEdges(root)) {
this.pathIndices.put(e, pathIdx);
this.roots.put(e, root);
newVertexEncountered(pathIdx, getGraph().getEndpoints(e).getSecond(), root);
pathIdx++;
}
List<E> edges = new ArrayList<E>();
V virtualNode = vertexFactory.create();
getGraph().addVertex(virtualNode);
E virtualSinkEdge = edgeFactory.create();
getGraph().addEdge(virtualSinkEdge, virtualNode, root);
edges.add(virtualSinkEdge);
int currentDepth = 0;
while (currentDepth <= depth) {
double currentWeight = Math.pow(mAlpha, -1.0 * currentDepth);
for (E currentEdge : edges) {
incrementRankScore(getGraph().getEndpoints(currentEdge).getSecond(),//
currentWeight);
}
if ((currentDepth == depth) || (edges.size() == 0)) break;
List<E> newEdges = new ArrayList<E>();
for (E currentSourceEdge : edges) { //Iterator sourceEdgeIt = edges.iterator(); sourceEdgeIt.hasNext();) {
Number sourcePathIndex = this.pathIndices.get(currentSourceEdge);
// from the currentSourceEdge, get its opposite end
// then iterate over the out edges of that opposite end
V newDestVertex = getGraph().getEndpoints(currentSourceEdge).getSecond();
Collection<E> outs = getGraph().getOutEdges(newDestVertex);
for (E currentDestEdge : outs) {
V destEdgeRoot = this.roots.get(currentDestEdge);
V destEdgeDest = getGraph().getEndpoints(currentDestEdge).getSecond();
if (currentSourceEdge == virtualSinkEdge) {
newEdges.add(currentDestEdge);
continue;
}
if (destEdgeRoot == root) {
continue;
}
if (destEdgeDest == getGraph().getEndpoints(currentSourceEdge).getFirst()) {//currentSourceEdge.getSource()) {
continue;
}
Set<Number> pathsSeen = this.pathsSeenMap.get(destEdgeDest);
if (pathsSeen == null) {
newVertexEncountered(sourcePathIndex.intValue(), destEdgeDest, root);
} else if (roots.get(destEdgeDest) != root) {
roots.put(destEdgeDest,root);
pathsSeen.clear();
pathsSeen.add(sourcePathIndex);
} else if (!pathsSeen.contains(sourcePathIndex)) {
pathsSeen.add(sourcePathIndex);
} else {
continue;
}
this.pathIndices.put(currentDestEdge, sourcePathIndex);
this.roots.put(currentDestEdge, root);
newEdges.add(currentDestEdge);
}
}
edges = newEdges;
currentDepth++;
}
getGraph().removeVertex(virtualNode);
}
private void newVertexEncountered(int sourcePathIndex, V dest, V root) {
Set<Number> pathsSeen = new HashSet<Number>();
pathsSeen.add(sourcePathIndex);
this.pathsSeenMap.put(dest, pathsSeen);
roots.put(dest, root);
}
@Override
public void step() {
for (V v : mPriors) {
computeWeightedPathsFromSource(v, mMaxDepth);
}
normalizeRankings();
// return 0;
}
/**
* Given a node, returns the corresponding rank score. This implementation of <code>getRankScore</code> assumes
* the decoration representing the rank score is of type <code>MutableDouble</code>.
* @return the rank score for this node
*/
@Override
public String getRankScoreKey() {
return WEIGHTED_NIPATHS_KEY;
}
@Override
protected void onFinalize(Object udc) {
pathIndices.remove(udc);
roots.remove(udc);
pathsSeenMap.remove(udc);
}
}