All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graphstream.algorithm.BetweennessCentrality Maven / Gradle / Ivy

Go to download

The GraphStream library. With GraphStream you deal with graphs. Static and Dynamic. You create them from scratch, from a file or any source. You display and render them. This package contains algorithms and generators.

There is a newer version: 2.0
Show newest version
/*
 * Copyright 2006 - 2015
 *     Stefan Balev     
 *     Julien Baudry    
 *     Antoine Dutot    
 *     Yoann Pigné      
 *     Guilhelm Savin   
 * 
 * This file is part of GraphStream .
 * 
 * GraphStream is a library whose purpose is to handle static or dynamic
 * graph, create them from scratch, file or any source and display them.
 * 
 * This program is free software distributed under the terms of two licenses, the
 * CeCILL-C license that fits European law, and the GNU Lesser General Public
 * License. You can  use, modify and/ or redistribute the software under the terms
 * of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
 * URL  or under the terms of the GNU LGPL as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see .
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
 */
package org.graphstream.algorithm;

import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.PriorityQueue;
import java.util.Set;

import org.graphstream.graph.Edge;
import org.graphstream.graph.Element;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;

/**
 * Compute the "betweenness" centrality of each vertex of a given graph.
 * 
 * 

* The betweenness centrality counts how many shortest paths between each * pair of nodes of the graph pass by a node. It does it for all nodes of * the graph. *

* *

Usage

* *

* This algorithm, by default, stores the centrality values for each node inside * the "Cb" attribute. You can change this attribute name at construction time. *

* *

* This algorithm does not accept multi-graphs (p-graphs with p>1) yet. *

* *

* This algorithm does not take into account edge direction yet. *

* *

* By default the * weight attribute name is "weight", you can activate the weights using * {@link #setWeighted()}. You can change the weight attribute name using the * dedicated constructor or the {@link #setWeightAttributeName(String)} method. * This method implicitly enable weights in the computation. Use * {@link #setUnweighted()} to disable weights. *

* *

* The result of the computation is stored on each node inside the "Cb" * attribute. You can change the name of this attribute using the dedicated * constructor or the {@link #setCentralityAttributeName(String)} method. *

* *

* As the computing of centrality can take a lot of time, you can provide a * progress 'callback' to get notified each time the algorithm finished * processing a node (however the centrality values are usable only when the * algorithm finished). See the {@link #registerProgressIndicator(Progress)} * method. *

* *

Complexity

* *

* By default the algorithm performs on a graph considered as not weighted with * complexity O(nm). You can specify that the graph edges contain weights in * which case the algorithm complexity is O(nm + n^2 log n). *

* *

Example

* *
 * 		Graph graph = new SingleGraph("Betweenness Test");
 * 		
 * 		//    E----D  AB=1, BC=5, CD=3, DE=2, BE=6, EA=4  
 *		//   /|    |  Cb(A)=4
 *		//  / |    |  Cb(B)=2
 *		// A  |    |  Cb(C)=0
 *		//  \ |    |  Cb(D)=2
 *		//   \|    |  Cb(E)=4
 *		//    B----C
 *		
 *		Node A = graph.addNode("A");
 *		Node B = graph.addNode("B");
 *		Node E = graph.addNode("E");
 *		Node C = graph.addNode("C");
 *		Node D = graph.addNode("D");
 *
 *		graph.addEdge("AB", "A", "B");
 *		graph.addEdge("BE", "B", "E");
 *		graph.addEdge("BC", "B", "C");
 *		graph.addEdge("ED", "E", "D");
 *		graph.addEdge("CD", "C", "D");
 *		graph.addEdge("AE", "A", "E");
 *		
 *		bcb.setWeight(A, B, 1);
 *		bcb.setWeight(B, E, 6);
 *		bcb.setWeight(B, C, 5);
 *		bcb.setWeight(E, D, 2);
 *		bcb.setWeight(C, D, 3);
 *		bcb.setWeight(A, E, 4);
 *
 *		BetweennessCentrality bcb = new BetweennessCentrality();
 *		bcb.setWeightAttributeName("weight");
 *		bcb.init(graph);
 *		bcb.compute();
 *		
 *		System.out.println("A="+ graph.getNode("A").getAttribute("Cb"));
 *		System.out.println("B="+ graph.getNode("B").getAttribute("Cb"));
 *		System.out.println("C="+ graph.getNode("C").getAttribute("Cb"));
 *		System.out.println("D="+ graph.getNode("D").getAttribute("Cb"));
 *		System.out.println("E="+ graph.getNode("E").getAttribute("Cb"));
 * 
* *

Reference

* *

* This is based on the algorithm described in "A Faster Algorithm for * Betweenness Centrality", Ulrik Brandes, Journal of Mathematical Sociology, * 2001, and in * "On variants of shortest-path betweenness centrality and their generic computation", * of the same author, 2008. *

* * @reference A Faster Algorithm for Betweenness Centrality, Ulrik Brandes, * Journal of Mathematical Sociology, 2001, 25:2, pp. 163 - 177", * "DOI: 10.1080/0022250X.2001.9990249" * * @reference On variants of shortest-path betweenness centrality and their generic computation, * Ulrik Brandes, Social Networks, vol 30:2", pp. 136 - 145, 2008, * issn 0378-8733, "DOI: 10.1016/j.socnet.2007.11.001". */ public class BetweennessCentrality implements Algorithm { protected static double INFINITY = 1000000.0; /** Store the centrality value in this attribute on nodes and edges. */ protected String centralityAttributeName = "Cb"; /** The predecessors. */ protected String predAttributeName = "brandes.P"; /** The sigma value. */ protected String sigmaAttributeName = "brandes.sigma"; /** The distance value. */ protected String distAttributeName = "brandes.d"; /** The delta value. */ protected String deltaAttributeName = "brandes.delta"; /** Name of the attribute used to retrieve weights on edges. */ protected String weightAttributeName = "weight"; /** Do not use weights ? */ protected boolean unweighted = true; /** The graph to modify. */ protected Graph graph; /** The progress call-back method. */ protected Progress progress = null; /** Compute the centrality of edges. */ protected boolean doEdges = true; /** * New centrality algorithm that will perform as if the graph was * unweighted. By default the centrality will be stored in a "Cb" attribute * on each node. */ public BetweennessCentrality() { unweighted = true; } /** * New centrality algorithm that will perform as if the graph was * unweighted. The centrality for each node will be stored in an attribute * whose name is specified by the centralityAttributeName * argument. * * @param centralityAttributeName * The name of the attribute used to store the result of the * algorithm on each node. */ public BetweennessCentrality(String centralityAttributeName) { this.centralityAttributeName = centralityAttributeName; this.unweighted = true; } /** * New centrality algorithm that will perform on a weighted graph, taking * the weight of each edge in the given weightAttributeName. * The result of the algorithm is stored for each node using the given * centralityAttributeName. If an edge has no weight attribute, * it is considered as having a weight of one. * * @param centralityAttributeName * Name to use to store the centrality results on each node. * @param weightAttributeName * Name to use to retrieve the edge weights. */ public BetweennessCentrality(String centralityAttributeName, String weightAttributeName) { this.centralityAttributeName = centralityAttributeName; this.weightAttributeName = weightAttributeName; this.unweighted = false; } /** * Name of the attribute used to retrieve weights on edges. */ public String getWeightAttributeName() { return weightAttributeName; } /** * Name of the attribute used to store centrality values on nodes. */ public String getCentralityAttributeName() { return centralityAttributeName; } /** * Specify the name of the weight attribute to retrieve edge attributes. * This automatically set the algorithm to perform on the graph as if it was * weighted. */ public void setWeightAttributeName(String weightAttributeName) { unweighted = false; this.weightAttributeName = weightAttributeName; } /** * Consider the edges to have a weight. By default the weight is stored in a * "weight" attribute. You can change this attribute using * {@link #setWeightAttributeName(String)}. If an edge has no weight the * value 1.0 is used. */ public void setWeighted() { unweighted = false; } /** * Consider all the edges to have the weight. */ public void setUnweighted() { unweighted = true; } /** * Activate or deactivate the centrality computation on edges. By default it is * activated. Notice that this does not change the complexity of the algorithm. * Only one more access on the edges is done to store the centrality in addition * to the node access. * @param on If true, the edges centrality is also computed. */ public void computeEdgeCentrality(boolean on) { doEdges = on; } /** * Specify the name of the attribute used to store the computed centrality * values for each node. */ public void setCentralityAttributeName(String centralityAttributeName) { this.centralityAttributeName = centralityAttributeName; } /** * Specify an interface to call in order to indicate the algorithm progress. * Pass null to remove the progress indicator. The progress indicator will * be called regularly to indicate the computation progress. */ public void registerProgressIndicator(Progress progress) { this.progress = progress; } /** * Setup the algorithm to work on the given graph. */ public void init(Graph graph) { this.graph = graph; } /** * Compute the betweenness centrality on the given graph for each node. The * result is by default stored in the "Cb" attribute on each node. */ public void compute() { if (graph != null) { betweennessCentrality(graph); } } /** * Compute the betweenness centrality on the given graph for each node and * eventually edges. This method is equivalent to a call in sequence to the * two methods {@link #init(Graph)} then {@link #compute()}. */ public void betweennessCentrality(Graph graph) { init(graph); initAllNodes(graph); initAllEdges(graph); float n = graph.getNodeCount(); float i = 0; for (Node s : graph) { PriorityQueue S = null; if (unweighted) S = simpleExplore(s, graph); else S = dijkstraExplore2(s, graph); // The really new things in the Brandes algorithm are here: // Accumulation phase: while (!S.isEmpty()) { Node w = S.poll(); for (Node v : predecessorsOf(w)) { double c = ((sigma(v) / sigma(w)) * (1.0 + delta(w))); if(doEdges) { Edge e = w.getEdgeBetween(v); setCentrality(e, centrality(e) + c); } setDelta(v, delta(v) + c); } if (w != s) { setCentrality(w, centrality(w) + delta(w)); } } if (progress != null) progress.progress(i / n); i++; } } /** * Compute single-source multiple-targets shortest paths on an unweighted * graph. * * @param source * The source node. * @param graph * The graph. * @return A priority queue of explored nodes with sigma values usable to * compute the centrality. */ protected PriorityQueue simpleExplore(Node source, Graph graph) { LinkedList Q = new LinkedList(); PriorityQueue S = new PriorityQueue(graph.getNodeCount(), new BrandesNodeComparatorLargerFirst()); setupAllNodes(graph); Q.add(source); setSigma(source, 1.0); setDistance(source, 0.0); while (!Q.isEmpty()) { Node v = Q.removeFirst(); S.add(v); Iterator ww = v.getLeavingEdgeIterator(); while (ww.hasNext()) { Edge l = ww.next(); Node w = l.getOpposite(v);//ww.next(); if (distance(w) == INFINITY) { setDistance(w, distance(v) + 1); Q.add(w); } if (distance(w) == (distance(v) + 1.0)) { setSigma(w, sigma(w) + sigma(v)); addToPredecessorsOf(w, v); } } } return S; } /** * Compute single-source multiple-targets paths on a weighted graph. * * @param source * The source node. * @param graph * The graph. * @return A priority queue of explored nodes with sigma values usable to * compute the centrality. */ protected PriorityQueue dijkstraExplore(Node source, Graph graph) { PriorityQueue S = new PriorityQueue(graph.getNodeCount(), new BrandesNodeComparatorLargerFirst()); PriorityQueue Q = new PriorityQueue(graph.getNodeCount(), new BrandesNodeComparatorSmallerFirst()); setupAllNodes(graph); setDistance(source, 0.0); setSigma(source, 1.0); Q.add(source); while (!Q.isEmpty()) { Node u = Q.poll(); if (distance(u) < 0.0) { // XXX Can happen ??? XXX Q.clear(); throw new RuntimeException("negative distance ??"); } else { S.add(u); // Iterator k = u.getNeighborNodeIterator(); Iterator k = u.getLeavingEdgeIterator(); while (k.hasNext()) { // Node v = k.next(); Edge l = k.next(); Node v = l.getOpposite(u); double alt = distance(u) + weight(u, v); if (alt < distance(v)) { if (distance(v) == INFINITY) { setDistance(v, alt); updatePriority(S, v); updatePriority(Q, v); Q.add(v); setSigma(v, sigma(v) + sigma(u)); // XXX // sigma(v)==0, // always ?? XXX } else { setDistance(v, alt); updatePriority(S, v); updatePriority(Q, v); setSigma(v, sigma(u)); } replacePredecessorsOf(v, u); } else if (alt == distance(v)) { setSigma(v, sigma(v) + sigma(u)); addToPredecessorsOf(v, u); } } } } return S; } /** * The implementation of the Brandes paper. * *
    *
  • title = * "On variants of shortest-path betweenness centrality and their generic computation" * ,
  • *
  • author = "Ulrik Brandes",
  • *
  • journal = "Social Networks",
  • *
  • volume = "30",
  • *
  • number = "2",
  • *
  • pages = "136 - 145",
  • *
  • year = "2008",
  • *
  • note = "",
  • *
  • issn = "0378-8733",
  • *
  • doi = "DOI: 10.1016/j.socnet.2007.11.001",
  • */ protected PriorityQueue dijkstraExplore2(Node source, Graph graph) { PriorityQueue S = new PriorityQueue(graph.getNodeCount(), new BrandesNodeComparatorLargerFirst()); PriorityQueue Q = new PriorityQueue(graph.getNodeCount(), new BrandesNodeComparatorSmallerFirst()); setupAllNodes(graph); setDistance(source, 0.0); setSigma(source, 1.0); Q.add(source); while (!Q.isEmpty()) { Node v = Q.poll(); S.add(v); //Iterator k = v.getNeighborNodeIterator(); Iterator k = v.getLeavingEdgeIterator(); while (k.hasNext()) { //Node w = k.next(); Edge l = k.next(); Node w = l.getOpposite(v); double alt = distance(v) + weight(v, w); double dw = distance(w); if (alt < dw) { setDistance(w, alt); updatePriority(S, w); updatePriority(Q, w); if (dw == INFINITY) { Q.add(w); } setSigma(w, 0.0); clearPredecessorsOf(w); } if (distance(w) == alt) { setSigma(w, sigma(w) + sigma(v)); addToPredecessorsOf(w, v); } } } return S; } /** * Update the given priority queue if the given node changed its priority * (here distance) and if the node is already part of the queue. * * @param Q * The queue. * @param node * The node. */ protected void updatePriority(PriorityQueue Q, Node node) { if (Q.contains(node)) { Q.remove(node); Q.add(node); } } /** * The sigma value of the given node. * * @param node * Extract the sigma value of this node. * @return The sigma value. */ protected double sigma(Node node) { return node.getNumber(sigmaAttributeName); } /** * The distance value of the given node. * * @param node * Extract the distance value of this node. * @return The distance value. */ protected double distance(Node node) { return node.getNumber(distAttributeName); } /** * The delta value of the given node. * * @param node * Extract the delta value of this node. * @return The delta value. */ protected double delta(Node node) { return node.getNumber(deltaAttributeName); } /** * The centrality value of the given node or edge. * * @param elt * Extract the centrality of this node or edge. * @return The centrality value. */ public double centrality(Element elt) { return elt.getNumber(centralityAttributeName); } /** * List of predecessors of the given node. * * @param node * Extract the predecessors of this node. * @return The list of predecessors. */ @SuppressWarnings("all") protected Set predecessorsOf(Node node) { return (HashSet) node.getAttribute(predAttributeName); } /** * Set the sigma value of the given node. * * @param node * The node to modify. * @param sigma * The sigma value to store on the node. */ protected void setSigma(Node node, double sigma) { node.setAttribute(sigmaAttributeName, sigma); } /** * Set the distance value of the given node. * * @param node * The node to modify. * @param distance * The delta value to store on the node. */ protected void setDistance(Node node, double distance) { node.setAttribute(distAttributeName, distance); } /** * Set the delta value of the given node. * * @param node * The node to modify. * @param delta * The delta value to store on the node. */ protected void setDelta(Node node, double delta) { node.setAttribute(deltaAttributeName, delta); } /** * Set the centrality of the given node or edge. * * @param elt * The node or edge to modify. * @param centrality * The centrality to store on the node. */ public void setCentrality(Element elt, double centrality) { elt.setAttribute(centralityAttributeName, centrality); } /** * Set the weight of the edge between 'from' and 'to'. * * @param from * The source node. * @param to * The target node. * @param weight * The weight to store on the edge between 'from' and 'to'. */ public void setWeight(Node from, Node to, double weight) { if (from.hasEdgeBetween(to.getId())) from.getEdgeBetween(to.getId()).setAttribute(weightAttributeName, weight); } /** * The weight of the edge between 'from' and 'to'. * * @param from * The origin node. * @param to * The target node. * @return The weight on the edge between 'form' and 'to'. */ public double weight(Node from, Node to) { Edge edge = from.getEdgeBetween(to.getId()); if (edge != null) { if (edge.hasAttribute(weightAttributeName)) return edge.getNumber(weightAttributeName); else return 1.0; } else { return 0.0; } } /** * Remove all predecessors of the given node and then add it a first * predecessor. * * @param node * The node to modify. * @param predecessor * The predecessor to add. */ protected void replacePredecessorsOf(Node node, Node predecessor) { HashSet set = new HashSet(); set.add(predecessor); node.setAttribute(predAttributeName, set); } /** * Add a node to the predecessors of another. * * @param node * Modify the predecessors of this node. * @param predecessor * The predecessor to add. */ @SuppressWarnings("all") protected void addToPredecessorsOf(Node node, Node predecessor) { HashSet preds = (HashSet) node .getAttribute(predAttributeName); preds.add(predecessor); } /** * Remove all predecessors of the given node. * * @param node * Remove all predecessors of this node. */ protected void clearPredecessorsOf(Node node) { HashSet set = new HashSet(); node.setAttribute(predAttributeName, set); } /** * Set a default centrality of 0 to all nodes. * * @param graph * The graph to modify. */ protected void initAllNodes(Graph graph) { for (Node node : graph) { setCentrality(node, 0.0); } } /** * Set a default centrality of 0 to all edges. * * @param graph * The graph to modify. */ protected void initAllEdges(Graph graph) { if(doEdges) { for(Edge edge : graph.getEachEdge()) { setCentrality(edge, 0.0); } } } /** * Add a default value for attributes used during computation. * * @param graph * The graph to modify. */ protected void setupAllNodes(Graph graph) { for (Node node : graph) { clearPredecessorsOf(node); setSigma(node, 0.0); setDistance(node, INFINITY); setDelta(node, 0.0); } } /** * Delete attributes used by this algorithm in nodes and edges of the graph */ public void cleanGraph(){ cleanElement(graph.getEachEdge()); cleanElement(graph.getEachNode()); } /** * Delete attributes used by this algorithm in nodes of the graph */ public void cleanNodes(){ cleanElement(graph.getEachNode()); } /** * Delete attributes used by this algorithm in edges of the graph */ public void cleanEdges(){ cleanElement(graph.getEachEdge()); } /** * Delete attributes used by this algorithm in elements of a graph * @param it the list of elements */ private void cleanElement(Iterable it){ for(Element e : it){ if(e.hasAttribute(predAttributeName)) e.removeAttribute(predAttributeName); if(e.hasAttribute(sigmaAttributeName)) e.removeAttribute(sigmaAttributeName); if(e.hasAttribute(distAttributeName)) e.removeAttribute(distAttributeName); if(e.hasAttribute(deltaAttributeName)) e.removeAttribute(deltaAttributeName); } } /** * Increasing comparator used for priority queues. */ protected class BrandesNodeComparatorLargerFirst implements Comparator { public int compare(Node x, Node y) { // return (int) ( (distance(y)*1000.0) - (distance(x)*1000.0) ); double yy = distance(y); double xx = distance(x); if (xx > yy) return -1; else if (xx < yy) return 1; return 0; } } /** * Decreasing comparator used for priority queues. */ protected class BrandesNodeComparatorSmallerFirst implements Comparator { public int compare(Node x, Node y) { // return (int) ( (distance(x)*1000.0) - (distance(y)*1000.0) ); double yy = distance(y); double xx = distance(x); if (xx > yy) return 1; else if (xx < yy) return -1; return 0; } } /** * Interface allowing to be notified of the algorithm progress. */ public interface Progress { /** * Progress of the algorithm. * * @param percent * a value between 0 and 1, 0 meaning 0% and 1 meaning 100%. */ void progress(float percent); } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy