Commit ad69736a authored by Antoine's avatar Antoine

Added edge-betweeness centrality and edge direction.

Thanks to the contribution of Thibaut Démare.
parent 86d05e5c
...@@ -222,6 +222,12 @@ public class TestBetweenessCentrality { ...@@ -222,6 +222,12 @@ public class TestBetweenessCentrality {
assertEquals(0, (Double) graph.getNode("C").getAttribute("Cb"), 0); assertEquals(0, (Double) graph.getNode("C").getAttribute("Cb"), 0);
assertEquals(2, (Double) graph.getNode("D").getAttribute("Cb"), 0); assertEquals(2, (Double) graph.getNode("D").getAttribute("Cb"), 0);
assertEquals(4, (Double) graph.getNode("E").getAttribute("Cb"), 0); assertEquals(4, (Double) graph.getNode("E").getAttribute("Cb"), 0);
assertEquals(8, (Double) graph.getEdge("AB").getAttribute("Cb"), 0);
assertEquals(8, (Double) graph.getEdge("AE").getAttribute("Cb"), 0);
assertEquals(0, (Double) graph.getEdge("BE").getAttribute("Cb"), 0);
assertEquals(4, (Double) graph.getEdge("BC").getAttribute("Cb"), 0);
assertEquals(4, (Double) graph.getEdge("CD").getAttribute("Cb"), 0);
assertEquals(8, (Double) graph.getEdge("ED").getAttribute("Cb"), 0);
} }
@Test @Test
......
...@@ -45,8 +45,10 @@ import org.graphstream.graph.Path; ...@@ -45,8 +45,10 @@ import org.graphstream.graph.Path;
* An implementation of the A* algorithm. * An implementation of the A* algorithm.
* *
* <p> * <p>
* A* computes the shortest path from a node to another in a graph. It can * A* computes the shortest path from a node to another in a graph. It guarantees
* eventually fail if the two nodes are in two distinct connected components. * that the path found is the shortest one, given its heuristic is admissible,
* and a path exists between the two nodes. It will fail if the two nodes are in
* two distinct connected components.
* </p> * </p>
* *
* <p> * <p>
...@@ -63,15 +65,22 @@ import org.graphstream.graph.Path; ...@@ -63,15 +65,22 @@ import org.graphstream.graph.Path;
* *
* <p> * <p>
* By default the {@link org.graphstream.algorithm.AStar.Costs} implementation * By default the {@link org.graphstream.algorithm.AStar.Costs} implementation
* used uses a heuristic that returns 0 for any heuristic. This makes A* an * used uses a heuristic that always returns 0. This makes A* an * equivalent of
* equivalent of the Dijkstra algorithm, but also makes it far less efficient. * the Dijkstra algorithm, but also makes it less efficient.
* </p>
*
* <p>
* If there are several equivalent shortest paths between the two nodes, the returned
* one is arbitrary. Therefore this AStar algorithm works with multi-graphs but if two
* edges between two nodes have the same properties, the one that will be chosen will
* be arbitrary.
* </p> * </p>
* *
* <h2>Usage</h2> * <h2>Usage</h2>
* *
* <p>The basic usage is to create an instance of A*, then to ask it to compute * <p>The basic usage is to create an instance of A* (optionally specify a {@link Costs}
* from a shortest path from one target to one destination, and finally to ask * object), then to ask it to compute from a shortest path from one target to one
* for that path: * destination, and finally to ask for that path:
* </p> * </p>
* <pre> * <pre>
* AStart astar = new AStar(graph); * AStart astar = new AStar(graph);
...@@ -80,11 +89,11 @@ import org.graphstream.graph.Path; ...@@ -80,11 +89,11 @@ import org.graphstream.graph.Path;
* </pre> * </pre>
* <p> * <p>
* The advantage of A* is that it can consider any cost function to drive the * The advantage of A* is that it can consider any cost function to drive the
* search. You can create your own cost functions implementing the * search. You can (and should) create your own cost functions implementing the
* {@link org.graphstream.algorithm.AStar.Costs} interface. * {@link org.graphstream.algorithm.AStar.Costs} interface.
* </p> * </p>
* <p> * <p>
* You can also test the default "distance" cost function on a graph that has * You can also test the default euclidean "distance" cost function on a graph that has
* "x" and "y" values. You specify the {@link Costs} function before calling the * "x" and "y" values. You specify the {@link Costs} function before calling the
* {@link #compute(String,String)} method: * {@link #compute(String,String)} method:
* </p> * </p>
...@@ -152,7 +161,6 @@ import org.graphstream.graph.Path; ...@@ -152,7 +161,6 @@ import org.graphstream.graph.Path;
* } * }
* </pre> * </pre>
* *
*
* @complexity The complexity of A* depends on the heuristic. * @complexity The complexity of A* depends on the heuristic.
*/ */
public class AStar implements Algorithm { public class AStar implements Algorithm {
...@@ -482,7 +490,6 @@ public class AStar implements Algorithm { ...@@ -482,7 +490,6 @@ public class AStar implements Algorithm {
// Nested classes // Nested classes
/** /**
* The definition of an heuristic. The heuristic is in charge of evaluating
* the distance between the current position and the target. * the distance between the current position and the target.
*/ */
public interface Costs { public interface Costs {
...@@ -496,7 +503,7 @@ public class AStar implements Algorithm { ...@@ -496,7 +503,7 @@ public class AStar implements Algorithm {
* @return The estimated cost between a node and a target node. * @return The estimated cost between a node and a target node.
*/ */
double heuristic(Node node, Node target); double heuristic(Node node, Node target);
/** /**
* Cost of displacement from parent to next. The next node must be * Cost of displacement from parent to next. The next node must be
* directly connected to parent, or -1 is returned. * directly connected to parent, or -1 is returned.
...@@ -504,6 +511,7 @@ public class AStar implements Algorithm { ...@@ -504,6 +511,7 @@ public class AStar implements Algorithm {
* @param parent * @param parent
* The node we come from. * The node we come from.
* @param from * @param from
* The definition of an heuristic. The heuristic is in charge of evaluating
* The edge used between the two nodes (in case this is a * The edge used between the two nodes (in case this is a
* multi-graph). * multi-graph).
* @param next * @param next
......
...@@ -37,6 +37,7 @@ import java.util.PriorityQueue; ...@@ -37,6 +37,7 @@ import java.util.PriorityQueue;
import java.util.Set; import java.util.Set;
import org.graphstream.graph.Edge; import org.graphstream.graph.Edge;
import org.graphstream.graph.Element;
import org.graphstream.graph.Graph; import org.graphstream.graph.Graph;
import org.graphstream.graph.Node; import org.graphstream.graph.Node;
...@@ -52,12 +53,12 @@ import org.graphstream.graph.Node; ...@@ -52,12 +53,12 @@ import org.graphstream.graph.Node;
* <h2>Usage</h2> * <h2>Usage</h2>
* *
* <p> * <p>
* This algorithm, by default, stores the centrality values for each edge inside * This algorithm, by default, stores the centrality values for each node inside
* the "Cb" attribute. You can change this attribute name at construction time. * the "Cb" attribute. You can change this attribute name at construction time.
* </p> * </p>
* *
* <p> * <p>
* This algorithm does not accept multi-graphs (p-graphs with p>1) yet. * <b>This algorithm does not accept multi-graphs (p-graphs with p>1) yet.</b>
* </p> * </p>
* *
* <p> * <p>
...@@ -159,30 +160,39 @@ import org.graphstream.graph.Node; ...@@ -159,30 +160,39 @@ import org.graphstream.graph.Node;
* issn 0378-8733, "DOI: 10.1016/j.socnet.2007.11.001". * issn 0378-8733, "DOI: 10.1016/j.socnet.2007.11.001".
*/ */
public class BetweennessCentrality implements Algorithm { public class BetweennessCentrality implements Algorithm {
// Attribute
protected static double INFINITY = 1000000.0; protected static double INFINITY = 1000000.0;
/** Store the centrality value in this attribute on nodes and edges. */
protected String centralityAttributeName = "Cb"; protected String centralityAttributeName = "Cb";
/** The predecessors. */
protected String predAttributeName = "brandes.P"; protected String predAttributeName = "brandes.P";
/** The sigma value. */
protected String sigmaAttributeName = "brandes.sigma"; protected String sigmaAttributeName = "brandes.sigma";
/** The distance value. */
protected String distAttributeName = "brandes.d"; protected String distAttributeName = "brandes.d";
/** The delta value. */
protected String deltaAttributeName = "brandes.delta"; protected String deltaAttributeName = "brandes.delta";
/** Name of the attribute used to retrieve weights on edges. */
protected String weightAttributeName = "weight"; protected String weightAttributeName = "weight";
/** Do not use weights ? */
protected boolean unweighted = true; protected boolean unweighted = true;
/** The graph to modify. */
protected Graph graph; protected Graph graph;
/** The progress call-back method. */
protected Progress progress = null; protected Progress progress = null;
// Construction /** Compute the centrality of edges. */
protected boolean doEdges = true;
/** /**
* New centrality algorithm that will perform as if the graph was * New centrality algorithm that will perform as if the graph was
* unweighted. By default the centrality will be stored in a "Cb" attribute * unweighted. By default the centrality will be stored in a "Cb" attribute
...@@ -226,8 +236,6 @@ public class BetweennessCentrality implements Algorithm { ...@@ -226,8 +236,6 @@ public class BetweennessCentrality implements Algorithm {
this.unweighted = false; this.unweighted = false;
} }
// Access
/** /**
* Name of the attribute used to retrieve weights on edges. * Name of the attribute used to retrieve weights on edges.
*/ */
...@@ -242,8 +250,6 @@ public class BetweennessCentrality implements Algorithm { ...@@ -242,8 +250,6 @@ public class BetweennessCentrality implements Algorithm {
return centralityAttributeName; return centralityAttributeName;
} }
// Command
/** /**
* Specify the name of the weight attribute to retrieve edge attributes. * 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 * This automatically set the algorithm to perform on the graph as if it was
...@@ -270,6 +276,17 @@ public class BetweennessCentrality implements Algorithm { ...@@ -270,6 +276,17 @@ public class BetweennessCentrality implements Algorithm {
public void setUnweighted() { public void setUnweighted() {
unweighted = true; 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 * Specify the name of the attribute used to store the computed centrality
...@@ -306,13 +323,14 @@ public class BetweennessCentrality implements Algorithm { ...@@ -306,13 +323,14 @@ public class BetweennessCentrality implements Algorithm {
} }
/** /**
* Compute the betweenness centrality on the given graph for each node. This * Compute the betweenness centrality on the given graph for each node and
* method is equivalent to a call in sequence to the two methods * eventually edges. This method is equivalent to a call in sequence to the
* {@link #init(Graph)} then {@link #compute()}. * two methods {@link #init(Graph)} then {@link #compute()}.
*/ */
public void betweennessCentrality(Graph graph) { public void betweennessCentrality(Graph graph) {
init(graph); init(graph);
initAllNodes(graph); initAllNodes(graph);
initAllEdges(graph);
float n = graph.getNodeCount(); float n = graph.getNodeCount();
float i = 0; float i = 0;
...@@ -326,13 +344,18 @@ public class BetweennessCentrality implements Algorithm { ...@@ -326,13 +344,18 @@ public class BetweennessCentrality implements Algorithm {
S = dijkstraExplore2(s, graph); S = dijkstraExplore2(s, graph);
// The really new things in the Brandes algorithm are here: // The really new things in the Brandes algorithm are here:
// Accumulation phase:
while (!S.isEmpty()) { while (!S.isEmpty()) {
Node w = S.poll(); Node w = S.poll();
for (Node v : predecessorsOf(w)) { for (Node v : predecessorsOf(w)) {
setDelta(v, delta(v) double c = ((sigma(v) / sigma(w)) * (1.0 + delta(w)));
+ ((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) { if (w != s) {
setCentrality(w, centrality(w) + delta(w)); setCentrality(w, centrality(w) + delta(w));
...@@ -371,10 +394,11 @@ public class BetweennessCentrality implements Algorithm { ...@@ -371,10 +394,11 @@ public class BetweennessCentrality implements Algorithm {
Node v = Q.removeFirst(); Node v = Q.removeFirst();
S.add(v); S.add(v);
Iterator<? extends Node> ww = v.getNeighborNodeIterator(); Iterator<? extends Edge> ww = v.getLeavingEdgeIterator();
while (ww.hasNext()) { while (ww.hasNext()) {
Node w = ww.next(); Edge l = ww.next();
Node w = l.getOpposite(v);//ww.next();
if (distance(w) == INFINITY) { if (distance(w) == INFINITY) {
setDistance(w, distance(v) + 1); setDistance(w, distance(v) + 1);
...@@ -422,10 +446,14 @@ public class BetweennessCentrality implements Algorithm { ...@@ -422,10 +446,14 @@ public class BetweennessCentrality implements Algorithm {
} else { } else {
S.add(u); S.add(u);
Iterator<? extends Node> k = u.getNeighborNodeIterator(); // Iterator<? extends Node> k = u.getNeighborNodeIterator();
Iterator<? extends Edge> k = u.getLeavingEdgeIterator();
while (k.hasNext()) { while (k.hasNext()) {
Node v = k.next(); // Node v = k.next();
Edge l = k.next();
Node v = l.getOpposite(u);
double alt = distance(u) + weight(u, v); double alt = distance(u) + weight(u, v);
if (alt < distance(v)) { if (alt < distance(v)) {
...@@ -488,10 +516,14 @@ public class BetweennessCentrality implements Algorithm { ...@@ -488,10 +516,14 @@ public class BetweennessCentrality implements Algorithm {
S.add(v); S.add(v);
Iterator<? extends Node> k = v.getNeighborNodeIterator(); //Iterator<? extends Node> k = v.getNeighborNodeIterator();
Iterator<? extends Edge> k = v.getLeavingEdgeIterator();
while (k.hasNext()) { while (k.hasNext()) {
Node w = k.next(); //Node w = k.next();
Edge l = k.next();
Node w = l.getOpposite(v);
double alt = distance(v) + weight(v, w); double alt = distance(v) + weight(v, w);
double dw = distance(w); double dw = distance(w);
...@@ -565,16 +597,16 @@ public class BetweennessCentrality implements Algorithm { ...@@ -565,16 +597,16 @@ public class BetweennessCentrality implements Algorithm {
} }
/** /**
* The centrality value of the given node. * The centrality value of the given node or edge.
* *
* @param node * @param elt
* Extract the centrality of this node. * Extract the centrality of this node or edge.
* @return The centrality value. * @return The centrality value.
*/ */
public double centrality(Node node) { public double centrality(Element elt) {
return node.getNumber(centralityAttributeName); return elt.getNumber(centralityAttributeName);
} }
/** /**
* List of predecessors of the given node. * List of predecessors of the given node.
* *
...@@ -624,17 +656,17 @@ public class BetweennessCentrality implements Algorithm { ...@@ -624,17 +656,17 @@ public class BetweennessCentrality implements Algorithm {
} }
/** /**
* Set the centrality of the given node. * Set the centrality of the given node or edge.
* *
* @param node * @param elt
* The node to modify. * The node or edge to modify.
* @param centrality * @param centrality
* The centrality to store on the node. * The centrality to store on the node.
*/ */
public void setCentrality(Node node, double centrality) { public void setCentrality(Element elt, double centrality) {
node.setAttribute(centralityAttributeName, centrality); elt.setAttribute(centralityAttributeName, centrality);
} }
/** /**
* Set the weight of the edge between 'from' and 'to'. * Set the weight of the edge between 'from' and 'to'.
* *
...@@ -727,6 +759,20 @@ public class BetweennessCentrality implements Algorithm { ...@@ -727,6 +759,20 @@ public class BetweennessCentrality implements Algorithm {
setCentrality(node, 0.0); 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. * Add a default value for attributes used during computation.
......
...@@ -266,8 +266,8 @@ public class Eades84Layout extends PipeBase implements Layout { ...@@ -266,8 +266,8 @@ public class Eades84Layout extends PipeBase implements Layout {
} }
public void particleMoved(Object id, double x, double y, double z) { public void particleMoved(Object id, double x, double y, double z) {
for (LayoutListener listener : listeners) //for (LayoutListener listener : listeners)
listener.nodeMoved((String) id, x, y, z); // listener.nodeMoved((String) id, x, y, z);
Object xyz[] = new Object[3]; Object xyz[] = new Object[3];
xyz[0] = x; xyz[0] = x;
......
...@@ -207,8 +207,8 @@ public class HierarchicalLayout extends PipeBase implements Layout { ...@@ -207,8 +207,8 @@ public class HierarchicalLayout extends PipeBase implements Layout {
sendNodeAttributeChanged(sourceId, n.getId(), "xyz", null, sendNodeAttributeChanged(sourceId, n.getId(), "xyz", null,
new double[] { p.x, p.y, 0 }); new double[] { p.x, p.y, 0 });
for (int i = 0; i < listeners.size(); i++) // for (int i = 0; i < listeners.size(); i++)
listeners.get(i).nodeMoved(n.getId(), p.x, p.y, 0); // listeners.get(i).nodeMoved(n.getId(), p.x, p.y, 0);
} }
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment