/*
Copyright 2008-2011 Gephi
Authors : Patick J. McSweeney <pjmcswee@syr.edu>, Sebastien Heymann <seb@gephi.org>
Website : http://www.gephi.org
This file is part of Gephi.
Gephi is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
Gephi 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 Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with Gephi. If not, see <http://www.gnu.org/licenses/>.
*/
package org.gephi.statistics.plugin;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import org.gephi.data.attributes.api.AttributeColumn;
import org.gephi.data.attributes.api.AttributeModel;
import org.gephi.data.attributes.api.AttributeOrigin;
import org.gephi.data.attributes.api.AttributeRow;
import org.gephi.data.attributes.api.AttributeTable;
import org.gephi.data.attributes.api.AttributeType;
import org.gephi.graph.api.Edge;
import org.gephi.graph.api.EdgeIterable;
import org.gephi.graph.api.GraphController;
import org.gephi.graph.api.GraphModel;
import org.gephi.graph.api.HierarchicalDirectedGraph;
import org.gephi.graph.api.HierarchicalUndirectedGraph;
import org.gephi.graph.api.Node;
import org.gephi.graph.api.NodeIterable;
import org.gephi.statistics.spi.Statistics;
import org.gephi.utils.longtask.spi.LongTask;
import org.gephi.utils.progress.Progress;
import org.gephi.utils.progress.ProgressTicket;
import org.openide.util.Lookup;
/**
*
* @author pjmcswee
*/
public class ConnectedComponents implements Statistics, LongTask {
public static final String WEAKLY = "componentnumber";
public static final String STRONG = "strongcompnum";
private boolean isDirected;
private ProgressTicket progress;
private boolean isCanceled;
private int componentCount;
private int stronglyCount;
private int[] componentsSize;
int count;
public ConnectedComponents() {
GraphController graphController = Lookup.getDefault().lookup(GraphController.class);
if (graphController != null && graphController.getModel() != null) {
isDirected = graphController.getModel().isDirected();
}
}
public void execute(GraphModel graphModel, AttributeModel attributeModel) {
HierarchicalUndirectedGraph undirectedGraph = graphModel.getHierarchicalUndirectedGraphVisible();
weaklyConnected(undirectedGraph, attributeModel);
if (isDirected) {
HierarchicalDirectedGraph directedGraph = graphModel.getHierarchicalDirectedGraphVisible();
top_tarjans(directedGraph, attributeModel);
}
}
public void weaklyConnected(HierarchicalUndirectedGraph hgraph, AttributeModel attributeModel) {
isCanceled = false;
componentCount = 0;
AttributeTable nodeTable = attributeModel.getNodeTable();
AttributeColumn componentCol = nodeTable.getColumn(WEAKLY);
if (componentCol == null) {
componentCol = nodeTable.addColumn(WEAKLY, "Component ID", AttributeType.INT, AttributeOrigin.COMPUTED, new Integer(0));
}
List<Integer> sizeList = new ArrayList<Integer>();
hgraph.readLock();
HashMap<Node, Integer> indicies = new HashMap<Node, Integer>();
int index = 0;
for (Node s : hgraph.getNodes()) {
indicies.put(s, index);
index++;
}
int N = hgraph.getNodeCount();
//Keep track of which nodes have been seen
int[] color = new int[N];
Progress.start(progress, hgraph.getNodeCount());
int seenCount = 0;
while (seenCount < N) {
//The search Q
LinkedList<Node> Q = new LinkedList<Node>();
//The component-list
LinkedList<Node> component = new LinkedList<Node>();
//Seed the seach Q
NodeIterable iter = hgraph.getNodes();
for (Node first : iter) {
if (color[indicies.get(first)] == 0) {
Q.add(first);
iter.doBreak();
break;
}
}
//While there are more nodes to search
while (!Q.isEmpty()) {
if (isCanceled) {
hgraph.readUnlock();
return;
}
//Get the next Node and add it to the component list
Node u = Q.removeFirst();
component.add(u);
//Iterate over all of u's neighbors
EdgeIterable edgeIter = hgraph.getEdgesAndMetaEdges(u);
//For each neighbor
for (Edge edge : edgeIter) {
Node reachable = hgraph.getOpposite(u, edge);
int id = indicies.get(reachable);
//If this neighbor is unvisited
if (color[id] == 0) {
color[id] = 1;
//Add it to the search Q
Q.addLast(reachable);
//Mark it as used
Progress.progress(progress, seenCount);
}
}
color[indicies.get(u)] = 2;
seenCount++;
}
for (Node s : component) {
AttributeRow row = (AttributeRow) s.getNodeData().getAttributes();
row.setValue(componentCol, componentCount);
}
sizeList.add(component.size());
componentCount++;
}
hgraph.readUnlock();
componentsSize = new int[sizeList.size()];
for (int i = 0; i < sizeList.size(); i++) {
componentsSize[i] = sizeList.get(i);
}
}
public void top_tarjans(HierarchicalDirectedGraph hgraph, AttributeModel attributeModel) {
count = 1;
stronglyCount = 0;
AttributeTable nodeTable = attributeModel.getNodeTable();
AttributeColumn componentCol = nodeTable.getColumn(STRONG);
if (componentCol == null) {
componentCol = nodeTable.addColumn(STRONG, "Strongly-Connected ID", AttributeType.INT, AttributeOrigin.COMPUTED, new Integer(0));
}
hgraph.readLock();
HashMap<Node, Integer> indicies = new HashMap<Node, Integer>();
int v = 0;
for (Node s : hgraph.getNodes()) {
indicies.put(s, v);
v++;
}
int N = hgraph.getNodeCount();
int[] index = new int[N];
int[] low_index = new int[N];
while (true) {
//The search Q
LinkedList<Node> S = new LinkedList<Node>();
//The component-list
//LinkedList<Node> component = new LinkedList<Node>();
//Seed the seach Q
Node first = null;
NodeIterable iter = hgraph.getNodes();
for (Node u : iter) {
if (index[indicies.get(u)] == 0) {
first = u;
iter.doBreak();
break;
}
}
if (first == null) {
hgraph.readUnlockAll();
return;
}
tarjans(componentCol, S, hgraph, first, index, low_index, indicies);
}
}
private void tarjans(AttributeColumn col, LinkedList<Node> S, HierarchicalDirectedGraph hgraph, Node f, int[] index, int[] low_index, HashMap<Node, Integer> indicies) {
int id = indicies.get(f);
index[id] = count;
low_index[id] = count;
count++;
S.addFirst(f);
EdgeIterable edgeIter = hgraph.getOutEdgesAndMetaOutEdges(f);
for (Edge e : edgeIter) {
Node u = hgraph.getOpposite(f, e);
int x = indicies.get(u);
if (index[x] == 0) {
tarjans(col, S, hgraph, u, index, low_index, indicies);
low_index[id] = Math.min(low_index[x], low_index[id]);
} else if (S.contains(u)) {
low_index[id] = Math.min(low_index[id], index[x]);
}
}
if (low_index[id] == index[id]) {
Node v = null;
while (v != f) {
v = S.removeFirst();
AttributeRow row = (AttributeRow) v.getNodeData().getAttributes();
row.setValue(col, stronglyCount);
}
stronglyCount++;
}
}
public int getConnectedComponentsCount() {
return componentCount;
}
public void setDirected(boolean isDirected) {
this.isDirected = isDirected;
}
public boolean isDirected() {
return isDirected;
}
public int[] getComponentsSize() {
return componentsSize;
}
public int getGiantComponent() {
int[] sizes = getComponentsSize();
int max = Integer.MIN_VALUE;
int maxIndex = -1;
for (int i = 0; i < sizes.length; i++) {
if (sizes[i] > max) {
max = sizes[i];
maxIndex = i;
}
}
return maxIndex;
}
public String getReport() {
String report = "<HTML> <BODY> <h1>Connected Components Report </h1> "
+ "<hr>"
+ "<br>"
+ "<h2> Parameters: </h2>"
+ "Network Interpretation: " + (isDirected ? "directed" : "undirected") + "<br>"
+ "<br> <h2> Results: </h2>"
+ "Weakly Connected Components: " + componentCount + "<br>"
+ (isDirected ? "Stronlgy Connected Components: " + stronglyCount + "<br>" : "")
+ "<br />" + "<h2> Algorithm: </h2>"
+ "Robert Tarjan, <i>Depth-First Search and Linear Graph Algorithms</i>, in SIAM Journal on Computing 1 (2): 146–160 (1972)<br />"
+ "</BODY> </HTML>";
return report;
}
public boolean cancel() {
isCanceled = true;
return true;
}
public void setProgressTicket(ProgressTicket progressTicket) {
progress = progressTicket;
}
}