package edu.nd.nina.alg;
import java.util.Collections;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Vector;
import java.util.logging.Logger;
import edu.nd.nina.Graph;
import edu.nd.nina.structs.Pair;
import edu.nd.nina.traverse.BreadthFirstIterator;
import edu.nd.nina.util.GraphUtil;
/**
* Executes breadth first search operations (based on some SNAP functions)
*
* @author Tim Weninger
*
* @param <V>
* Node type, must extend Comparable<V>
* @param <E>
* Edge type
*/
public class BreadthFirstSearch<V extends Comparable<V>, E> {
private static Logger logger = Logger.getLogger(BreadthFirstSearch.class.getName());
private Map<V, Integer> hopMap = null;
/**
* Constructor runs the breadth first search. Warning - this procedure can
* take a long time to compute even for moderately sized graphs
*
* @param graph
* Graph snapshot
* @param startVertex
* Node to begin searching from
*/
public BreadthFirstSearch(Graph<V, E> graph, V startVertex) {
if (!graph.containsVertex(startVertex)) {
throw new IllegalArgumentException(
"graph must contain the start vertex");
}
BreadthFirstIterator<V, E> iter = new BreadthFirstIterator<V, E>(graph,
startVertex);
while (iter.hasNext()) {
iter.next();
}
hopMap = iter.getHopMap();
}
/**
* Get the number of nodes at a given distance from startVertex. Warning -
* this procedure can take a long time to compute even for moderately sized
* graphs
*
* @param graph
* Graph snapshot
* @param Node
* to begin searching from
* @return Vector<Pair<Distance, Count>>
*/
public static <V extends Comparable<V>, E> Vector<Pair<Integer, Integer>> getNodesAtHops(
final Graph<V, E> graph, final V startVertex) {
Vector<Pair<Integer, Integer>> HopCntV = new Vector<Pair<Integer, Integer>>();
BreadthFirstSearch<V, E> bfs = new BreadthFirstSearch<V, E>(graph,
startVertex);
Hashtable<Integer, Integer> HopCntH = new Hashtable<Integer, Integer>();
for (Integer val : bfs.hopMap.values()) {
if (!HopCntH.containsKey(val)) {
HopCntH.put(val, 1);
} else {
HopCntH.put(val, HopCntH.get(val) + 1);
}
}
for (Entry<Integer, Integer> e : HopCntH.entrySet()) {
HopCntV.add(new Pair<Integer, Integer>(e.getKey(), e.getValue()));
}
Collections.sort(HopCntV);
return HopCntV;
}
/**
* Convenience method returns approximation of full diameter. Warning - this
* procedure can take a long time to compute even for moderately sized
* graphs
*
* @param graph
* Graph snapshot
* @param numberTestNodes
* number of nodes to test in diameter, -1 tests the whole graph
* (not approximate)
* @return diameter
*/
public static <V extends Comparable<V>, E> int getBfsFullDiam(
final Graph<V, E> graph, final int numberTestNodes) {
int[] fullDiameter = { 0 };
double[] effectiveDiameter = { 0d };
getBfsEffDiam(graph, numberTestNodes, effectiveDiameter, fullDiameter);
return fullDiameter[0];
}
/**
* Convenience method returns approximation of 90% effective approximate
* diameter. Warning - this procedure can take a long time to compute even
* for moderately sized graphs
*
* @param graph
* Graph snapshot
* @param numberTestNodes
* number of nodes to test in diameter, -1 tests the whole graph
* (not approximate)
* @param effectiveDiameter
* reference to return effective diameter
* @param fullDiameter
* reference to return full diameter
* @return effective Diameter
*/
public static <V extends Comparable<V>, E> Map<V, Integer> getBfsEffDiam(
final Graph<V, E> graph, final int numberTestNodes,
double[] effectiveDiameter, int[] fullDiameter) {
double[] averageDiameter = { 0d };
effectiveDiameter[0] = -1;
fullDiameter[0] = -1;
return getBfsEffDiam(graph, numberTestNodes, effectiveDiameter,
fullDiameter, averageDiameter);
}
/**
* Convenience method returns approximation of 90% effective approximate
* diameter. Warning - this procedure can take a long time to compute even
* for moderately sized graphs.
*
* @param graph
* Graph snapshot
* @param numberTestNodes
* number of nodes to test in diameter, -1 tests the whole graph
* (not approximate)
* @param effectiveDiameter
* reference to return effective diameter
* @param fullDiameter
* reference to return full diameter
* @param averageShortestPathLength
* reference to return average shortest path length
* @return effective Diameter
*/
public static <V extends Comparable<V>, E> Map<V, Integer> getBfsEffDiam(
final Graph<V, E> graph, int numberTestNodes,
double[] effectiveDiameter, int[] fullDiameter,
double[] averageShortestPathLength) {
effectiveDiameter[0] = -1;
fullDiameter[0] = -1;
averageShortestPathLength[0] = -1;
Hashtable<Integer, Float> distanceToCountH = new Hashtable<Integer, Float>();
// shortest paths
Vector<V> nodeV = new Vector<V>();
nodeV.addAll(graph.vertexSet());
Collections.shuffle(nodeV);
if (numberTestNodes == -1) {
numberTestNodes = graph.vertexSet().size();
}
BreadthFirstSearch<V, E> bfs = null;
numberTestNodes = Math.min(numberTestNodes, graph.vertexSet()
.size());
int perc = -1;
for (int tries = 0; tries < numberTestNodes; tries++) {
if(perc < (tries/(float)numberTestNodes)*100){
logger.info(++perc + "%");
}
final V v = nodeV.get(tries);
bfs = new BreadthFirstSearch<V, E>(graph, v);
for (Integer dist : bfs.hopMap.values()) {
if (distanceToCountH.containsKey(dist)) {
distanceToCountH.put(dist,
(float) distanceToCountH.get(dist) + 1);
} else {
distanceToCountH.put(dist, 1f);
}
}
}
Vector<Pair<Integer, Float>> distanceToNeighborsPDF = new Vector<Pair<Integer, Float>>();
double sumPathLength = 0, pathCount = 0;
for (Entry<Integer, Float> e : distanceToCountH.entrySet()) {
distanceToNeighborsPDF.add(new Pair<Integer, Float>(e.getKey(), e
.getValue()));
sumPathLength += e.getKey() * e.getValue();
pathCount += e.getValue();
}
Collections.sort(distanceToNeighborsPDF);
// effective diameter (90-th percentile)
effectiveDiameter[0] = calcEffectiveDiameterPDF(distanceToNeighborsPDF,
0.9);
// approximate full diameter (max shortest path length over the sampled
// nodes)
fullDiameter[0] = distanceToNeighborsPDF.lastElement().p1;
// average shortest path length
averageShortestPathLength[0] = sumPathLength / pathCount;
return bfs.hopMap;
}
/**
* Calculate the effective diameter from probability density function
*
* @param distanceToNeighborsPDF
* @param percentile
* @return effective diameter
*/
public static double calcEffectiveDiameterPDF(
Vector<Pair<Integer, Float>> distanceToNeighborsPDF,
double percentile) {
Vector<Pair<Integer, Float>> cdfV = GraphUtil
.GetCdf(distanceToNeighborsPDF);
return calcEffectiveDiameter(cdfV, percentile);
}
/**
* Calculate the effective diameter from cumulative density function
*
* @param distanceToNeighborsCDFV
* @param percentile
* @return effective diameter
*/
public static double calcEffectiveDiameter(
Vector<Pair<Integer, Float>> distanceToNeighborsCDFV,
double percentile) {
final double effectivePairs = percentile
* distanceToNeighborsCDFV.lastElement().p2;
int valN;
for (valN = 0; valN < distanceToNeighborsCDFV.size(); valN++) {
if (distanceToNeighborsCDFV.get(valN).p2 > effectivePairs) {
break;
}
}
if (valN >= distanceToNeighborsCDFV.size()) {
return distanceToNeighborsCDFV.lastElement().p1;
}
if (valN == 0) {
return 1;
}
// interpolate
final double deltaNeighbors = distanceToNeighborsCDFV.get(valN).p2
- distanceToNeighborsCDFV.get(valN - 1).p2;
if (deltaNeighbors == 0) {
return distanceToNeighborsCDFV.get(valN).p1;
}
return distanceToNeighborsCDFV.get(valN - 1).p1
+ (effectivePairs - distanceToNeighborsCDFV.get(valN - 1).p2)
/ deltaNeighbors;
}
}