View Javadoc
1   /**
2    * Copyright (c) 2008, The JUNG Authors 
3    *
4    * All rights reserved.
5    *
6    * This software is open-source under the BSD license; see either
7    * "license.txt" or
8    * https://github.com/jrtom/jung/blob/master/LICENSE for a description.
9    * Created on Sep 16, 2008
10   * 
11   */
12  package edu.uci.ics.jung.algorithms.scoring;
13  
14  import java.util.ArrayList;
15  import java.util.Comparator;
16  import java.util.HashMap;
17  import java.util.LinkedList;
18  import java.util.List;
19  import java.util.Map;
20  import java.util.Queue;
21  import java.util.Stack;
22  
23  import com.google.common.base.Function;
24  import com.google.common.base.Functions;
25  
26  import edu.uci.ics.jung.algorithms.util.MapBinaryHeap;
27  import edu.uci.ics.jung.graph.Graph;
28  import edu.uci.ics.jung.graph.UndirectedGraph;
29  
30  /**
31   * Computes betweenness centrality for each vertex and edge in the graph.
32   * 
33   * @see "Ulrik Brandes: A Faster Algorithm for Betweenness Centrality. Journal of Mathematical Sociology 25(2):163-177, 2001."
34   */
35  public class BetweennessCentrality<V, E> 
36  	implements VertexScorer<V, Double>, EdgeScorer<E, Double> 
37  {
38  	protected Graph<V,E> graph;
39  	protected Map<V, Double> vertex_scores;
40  	protected Map<E, Double> edge_scores;
41  	protected Map<V, BetweennessData> vertex_data;
42  		
43  	/**
44  	 * Calculates betweenness scores based on the all-pairs unweighted shortest paths
45  	 * in the graph.
46  	 * @param graph the graph for which the scores are to be calculated
47  	 */
48  	public BetweennessCentrality(Graph<V, E> graph) 
49  	{
50  		initialize(graph);
51  		computeBetweenness(new LinkedList<V>(), Functions.<Integer>constant(1));
52  	}
53  
54  	/**
55  	 * Calculates betweenness scores based on the all-pairs weighted shortest paths in the
56  	 * graph.
57  	 * 
58  	 * <p>NOTE: This version of the algorithm may not work correctly on all graphs; we're still
59  	 * working out the bugs.  Use at your own risk.
60  	 * @param graph the graph for which the scores are to be calculated
61  	 * @param edge_weights the edge weights to be used in the path length calculations
62  	 */
63  	public BetweennessCentrality(Graph<V, E> graph, 
64  			Function<? super E, ? extends Number> edge_weights) 
65  	{
66  		// reject negative-weight edges up front
67  		for (E e : graph.getEdges())
68  		{
69  			double e_weight = edge_weights.apply(e).doubleValue();
70          	if (e_weight < 0)
71          		throw new IllegalArgumentException(String.format(
72          				"Weight for edge '%s' is < 0: %d", e, e_weight)); 
73  		}
74  			
75  		initialize(graph);
76  		computeBetweenness(new MapBinaryHeap<V>(new BetweennessComparator()), 
77  			edge_weights);
78  	}
79  
80  	protected void initialize(Graph<V,E> graph)
81  	{
82  		this.graph = graph;
83  		this.vertex_scores = new HashMap<V, Double>();
84  		this.edge_scores = new HashMap<E, Double>();
85  		this.vertex_data = new HashMap<V, BetweennessData>();
86  		
87  		for (V v : graph.getVertices())
88  			this.vertex_scores.put(v, 0.0);
89  		
90  		for (E e : graph.getEdges())
91  			this.edge_scores.put(e, 0.0);
92  	}
93  	
94  	protected void computeBetweenness(Queue<V> queue, 
95  			Function<? super E, ? extends Number> edge_weights)
96  	{
97  		for (V v : graph.getVertices())
98  		{
99  			// initialize the betweenness data for this new vertex
100 			for (V s : graph.getVertices()) 
101 				this.vertex_data.put(s, new BetweennessData());
102 
103 //			if (v.equals(new Integer(0)))
104 //				System.out.println("pause");
105 			
106             vertex_data.get(v).numSPs = 1;
107             vertex_data.get(v).distance = 0;
108 
109             Stack<V> stack = new Stack<V>();
110 //            Buffer<V> queue = new UnboundedFifoBuffer<V>();
111 //            queue.add(v);
112             queue.offer(v);
113 
114             while (!queue.isEmpty()) 
115             {
116 //                V w = queue.remove();
117             	V w = queue.poll();
118                 stack.push(w);
119             	BetweennessData w_data = vertex_data.get(w);
120                 
121                 for (E e : graph.getOutEdges(w))
122                 {
123                 	// TODO (jrtom): change this to getOtherVertices(w, e)
124                 	V x = graph.getOpposite(w, e);
125                 	if (x.equals(w))
126                 		continue;
127                 	double wx_weight = edge_weights.apply(e).doubleValue();
128                 	
129                 	
130 //                for(V x : graph.getSuccessors(w)) 
131 //                {
132 //                	if (x.equals(w))
133 //                		continue;
134                 	
135                 	// FIXME: the other problem is that I need to 
136                 	// keep putting the neighbors of things we've just 
137                 	// discovered in the queue, if they're undiscovered or
138                 	// at greater distance.
139                 	
140                 	// FIXME: this is the problem, right here, I think: 
141                 	// need to update position in queue if distance changes
142                 	// (which can only happen with weighted edges).
143                 	// for each outgoing edge e from w, get other end x
144                 	// if x not already visited (dist x < 0)
145                 	//   set x's distance to w's dist + edge weight
146                 	//   add x to queue; pri in queue is x's dist
147                 	// if w's dist + edge weight < x's dist 
148                 	//   update x's dist
149                 	//   update x in queue (MapBinaryHeap)
150                 	//   clear x's incoming edge list
151                 	// if w's dist + edge weight = x's dist
152                 	//   add e to x's incoming edge list
153                 	
154                 	BetweennessData x_data = vertex_data.get(x);
155                 	double x_potential_dist = w_data.distance + wx_weight;
156                 	
157                     if (x_data.distance < 0) 
158                     {
159 //                        queue.add(x);
160 //                        vertex_data.get(x).distance = vertex_data.get(w).distance + 1;
161                     	x_data.distance = x_potential_dist;
162                       	queue.offer(x);
163                     }
164                     
165                     // note:
166                     // (1) this can only happen with weighted edges
167                     // (2) x's SP count and incoming edges are updated below 
168                     if (x_data.distance > x_potential_dist)
169                     {
170                     	x_data.distance = x_potential_dist;
171                     	// invalidate previously identified incoming edges
172                     	// (we have a new shortest path distance to x)
173                     	x_data.incomingEdges.clear(); 
174                         // update x's position in queue
175                     	((MapBinaryHeap<V>)queue).update(x);
176                     }
177 //                  if (vertex_data.get(x).distance == vertex_data.get(w).distance + 1) 
178                     // 
179 //                    if (x_data.distance == x_potential_dist) 
180 //                    {
181 //                        x_data.numSPs += w_data.numSPs;
182 ////                        vertex_data.get(x).predecessors.add(w);
183 //                        x_data.incomingEdges.add(e);
184 //                    }
185                 }
186                 for (E e: graph.getOutEdges(w))
187                 {
188                 	V x = graph.getOpposite(w, e);
189                 	if (x.equals(w))
190                 		continue;
191                 	double e_weight = edge_weights.apply(e).doubleValue();
192                 	BetweennessData x_data = vertex_data.get(x);
193                 	double x_potential_dist = w_data.distance + e_weight;
194                     if (x_data.distance == x_potential_dist) 
195                     {
196                         x_data.numSPs += w_data.numSPs;
197 //                        vertex_data.get(x).predecessors.add(w);
198                         x_data.incomingEdges.add(e);
199                     }
200                 }
201             }
202     		while (!stack.isEmpty()) 
203     		{
204     		    V x = stack.pop();
205 
206 //    		    for (V w : vertex_data.get(x).predecessors) 
207     		    for (E e : vertex_data.get(x).incomingEdges)
208     		    {
209     		    	V w = graph.getOpposite(x, e);
210     		        double partialDependency = 
211     		        	vertex_data.get(w).numSPs / vertex_data.get(x).numSPs *
212     		        	(1.0 + vertex_data.get(x).dependency);
213     		        vertex_data.get(w).dependency +=  partialDependency;
214 //    		        E w_x = graph.findEdge(w, x);
215 //    		        double w_x_score = edge_scores.get(w_x).doubleValue();
216 //    		        w_x_score += partialDependency;
217 //    		        edge_scores.put(w_x, w_x_score);
218     		        double e_score = edge_scores.get(e).doubleValue();
219     		        edge_scores.put(e, e_score + partialDependency);
220     		    }
221     		    if (!x.equals(v)) 
222     		    {
223     		    	double x_score = vertex_scores.get(x).doubleValue();
224     		    	x_score += vertex_data.get(x).dependency;
225     		    	vertex_scores.put(x, x_score);
226     		    }
227     		}
228         }
229 
230         if(graph instanceof UndirectedGraph) 
231         {
232     		for (V v : graph.getVertices()) { 
233     			double v_score = vertex_scores.get(v).doubleValue();
234     			v_score /= 2.0;
235     			vertex_scores.put(v, v_score);
236     		}
237     		for (E e : graph.getEdges()) {
238     			double e_score = edge_scores.get(e).doubleValue();
239     			e_score /= 2.0;
240     			edge_scores.put(e, e_score);
241     		}
242         }
243 
244         vertex_data.clear();
245 	}
246 
247 //	protected void computeWeightedBetweenness(Function<E, ? extends Number> edge_weights)
248 //	{
249 //		for (V v : graph.getVertices())
250 //		{
251 //			// initialize the betweenness data for this new vertex
252 //			for (V s : graph.getVertices()) 
253 //				this.vertex_data.put(s, new BetweennessData());
254 //            vertex_data.get(v).numSPs = 1;
255 //            vertex_data.get(v).distance = 0;
256 //
257 //            Stack<V> stack = new Stack<V>();
258 ////            Buffer<V> queue = new UnboundedFifoBuffer<V>();
259 //            SortedSet<V> pqueue = new TreeSet<V>(new BetweennessComparator());
260 ////          queue.add(v);
261 //            pqueue.add(v);
262 //
263 ////            while (!queue.isEmpty()) 
264 //            while (!pqueue.isEmpty()) 
265 //            {
266 ////              V w = queue.remove();
267 //            	V w = pqueue.first();
268 //            	pqueue.remove(w);
269 //                stack.push(w);
270 //
271 ////                for(V x : graph.getSuccessors(w)) 
272 //                for (E e : graph.getOutEdges(w))
273 //                {
274 //                	// TODO (jrtom): change this to getOtherVertices(w, e)
275 //                	V x = graph.getOpposite(w, e);
276 //                	if (x.equals(w))
277 //                		continue;
278 //                	double e_weight = edge_weights.transform(e).doubleValue();
279 //                	
280 //                    if (vertex_data.get(x).distance < 0) 
281 //                    {
282 ////                        queue.add(x);
283 //                    	pqueue.add(v);
284 ////                        vertex_data.get(x).distance = vertex_data.get(w).distance + 1;
285 //                        vertex_data.get(x).distance = 
286 //                        	vertex_data.get(w).distance + e_weight;
287 //                    }
288 //
289 ////                    if (vertex_data.get(x).distance == vertex_data.get(w).distance + 1) 
290 //                    if (vertex_data.get(x).distance == 
291 //                    	vertex_data.get(w).distance + e_weight)
292 //                    {
293 //                        vertex_data.get(x).numSPs += vertex_data.get(w).numSPs;
294 //                        vertex_data.get(x).predecessors.add(w);
295 //                    }
296 //                }
297 //            }
298 //            updateScores(v, stack);
299 //        }
300 //
301 //        if(graph instanceof UndirectedGraph) 
302 //            adjustUndirectedScores();
303 //
304 //        vertex_data.clear();
305 //	}
306 	
307 	public Double getVertexScore(V v) 
308 	{
309 		return vertex_scores.get(v);
310 	}
311 
312 	public Double getEdgeScore(E e) 
313 	{
314 		return edge_scores.get(e);
315 	}
316 
317     private class BetweennessData 
318     {
319         double distance;
320         double numSPs;
321 //        List<V> predecessors;
322         List<E> incomingEdges;
323         double dependency;
324 
325         BetweennessData() 
326         {
327             distance = -1;
328             numSPs = 0;
329 //            predecessors = new ArrayList<V>();
330             incomingEdges = new ArrayList<E>();
331             dependency = 0;
332         }
333         
334         @Override
335         public String toString()
336         {
337         	return "[d:" + distance + ", sp:" + numSPs + 
338         		", p:" + incomingEdges + ", d:" + dependency + "]\n";
339 //        		", p:" + predecessors + ", d:" + dependency + "]\n";
340         }
341     }
342     
343     private class BetweennessComparator implements Comparator<V>
344     {
345 		public int compare(V v1, V v2) 
346 		{
347 			return vertex_data.get(v1).distance > vertex_data.get(v2).distance ? 1 : -1;
348 		}
349     }
350 }