/*
Copyright 2010 by Sean Luke and George Mason University
Licensed under the Academic Free License version 3.0
See the file "LICENSE" for more information
*/
package sim.field.network.stats;
import sim.field.network.*;
import sim.util.*;
public class ConnectivityStatistics
{
/**
Checks if a directed graph is strongly connected.
*/
public static boolean isStronglyConnected( final Network network )
{
if( network.isDirected() )
{
final Bag bag = getStronglyConnectedComponents(network);
return( bag.numObjs == 1 );
}
else
return false; // return a false if the network is undirected
}
/**
Computes the strongly connected components of an ORIENTED graph.
@return Bag containing the connected components (each element in the bag is another bag of nodes).
*/
public static Bag getStronglyConnectedComponents( final Network network )
{
if( !network.isDirected() )
throw new RuntimeException( "Connect.getStronglyConnectedComponents should be called only with directed graphs" );
Bag result = new Bag();
final int N = NetworkStatistics.getNumberNodes(network);
double[] finishingTime = new double[N]; // double vector to allow the use of heap later on
int[] color = new int[N]; // 0=WHITE, 1=GRAY, 2=BLACK
int time = 0;
for( int i = 0 ; i < N ; i++ )
color[i] = 0;
for( int i = 0 ; i < N ; i++ )
if( color[i] == 0 )
{
IntBag myStack = new IntBag();
myStack.push(i);
while( !myStack.isEmpty() )
{
int j = myStack.pop();
if( color[j] == 0 ) // if it's a white node
{
color[j] = 1;
myStack.push(j);
time++;
final Bag edgesOut = network.getEdgesOut(network.allNodes.objs[j]);
for( int k = 0 ; k < edgesOut.numObjs ; k++ )
{
final Edge edge = (Edge)(edgesOut.objs[k]);
final int toNode = network.getNodeIndex(edge.to());
if( color[toNode] == 0 )
myStack.push(toNode);
}
}
else
{
color[j] = 2;
time++;
finishingTime[j] = -time; //negative value, such that heap is in descending order
}
}
}
Integer[] indexes = new Integer[N];
for( int i = 0 ; i < N ; i++ )
indexes[i] = new Integer(i);
DoubleHeap heap = new DoubleHeap( finishingTime, indexes, N );
for( int i = 0 ; i < N ; i++ )
color[i] = 0;
while( !heap.isEmpty() )
{
final int i = ((Integer)(heap.extractMin())).intValue();
if( color[i] == 0 )
{
Bag component = new Bag(); // the objects in the current strongly connected component
IntBag myStack = new IntBag();
myStack.push(i);
while( !myStack.isEmpty() )
{
final int j = myStack.pop();
if( color[j] == 0 ) // if it's a white node
{
color[j] = 1;
component.add( network.allNodes.objs[j] );
final Bag edgesOut = network.getEdgesIn(network.allNodes.objs[j]); // here we use the in-edges, because the graph should have been transposed
for( int k = 0 ; k < edgesOut.numObjs ; k++ )
{
final Edge edge = (Edge)(edgesOut.objs[k]);
final int toNode = network.getNodeIndex(edge.from()); // this is now edge.from() because the graph should have been transposed
if( color[toNode] == 0 )
myStack.push(toNode);
}
}
}
result.add( component );
}
}
return result;
}
/**
* Computes the connected components of an undirected OR
* the weakly connected components of an directed graph
* graph using DFS.
* @return A Bag of Bags of nodes.
*/
public static Bag getConnectedComponents( final Network network)
{
return new ConnectedComponentFactory(network).getComponents();
}
/**
* Determines whether the graph is connected (for undirected graphs) OR
* weakly connected (for directed graphs)
*/
public static boolean isConnected( final Network network)
{
return new ConnectedComponentFactory(network).isConnected();
}
static class ConnectedComponentFactory
{
final Network network;
final int n;
final Bag components;
//instead of making a new hashtable, stick all nodes in it
//and remove them as they get visited,
//I rely on the hashtable inside the Network and
//the nodes' indices in the allNodes bag.
final boolean[] visited;
int countVisited;
public ConnectedComponentFactory( final Network network)
{
this.network = network;
n = network.allNodes.numObjs;
visited = new boolean[n];
for(int i=0;i<n;i++)
visited[i]=false;
countVisited = 0;
components = new Bag(n);
}
public Bag getComponents()
{
boolean directed = network.isDirected();
for( int i = 0 ; i < n; i++ )
{
if(!visited[i])
{
Bag component = new Bag();
components.add(component);
if(directed)
exploreWD(network.allNodes.objs[i], i, component);
else
exploreU(network.allNodes.objs[i], i, component);
}
}
return components;
}
public boolean isConnected()
{
if(network.isDirected())
exploreWD(network.allNodes.objs[0], 0, null);
else
exploreU(network.allNodes.objs[0], 0, null);
return (countVisited ==n);
}
/**
* explore for Weakly connected components in a Directed graph
* The Bag 'component' can be null if I'm only interested in whether the graph is connected.
*/
private void exploreWD(Object nodeObj, int nodeIndex, Bag component)
{
visited[nodeIndex]=true; countVisited++;
if(component!=null)
component.add(nodeObj);
Bag temp;
temp = network.getEdgesOut( nodeObj );
for(int i=0;i<temp.numObjs;i++)
{
Object node2 = ((Edge)temp.objs[i]).getOtherNode(nodeObj);
int node2Index = network.getNodeIndex(node2);
if(!visited[node2Index])
exploreWD(node2, node2Index, component);
}
temp = network.getEdgesIn ( nodeObj );
for(int i=0;i<temp.numObjs;i++)
{
Object node2 = ((Edge)temp.objs[i]).getOtherNode(nodeObj);
int node2Index = network.getNodeIndex(node2);
if(!visited[node2Index])
exploreWD(node2, node2Index, component);
}
}
// The Bag 'component' can be null if I'm only interested in whether the graph is connected.
private void exploreU(Object nodeObj, int nodeIndex, Bag component)
{
visited[nodeIndex]=true;countVisited++;
if(component!=null) component.add(nodeObj);
Bag temp = network.getEdgesOut( nodeObj );
for(int i=0;i<temp.numObjs;i++)
{
Object node2 = ((Edge)temp.objs[i]).getOtherNode(nodeObj);
int node2Index = network.getNodeIndex(node2);
if(!visited[node2Index])
exploreU(node2, node2Index, component);
}
}
}
static class FlowData
{
public FlowData(int flow, int capacity) { this.flow=flow; this.capacity=capacity; }
public FlowData(FlowData md) { this.flow=md.flow; this.capacity=md.capacity; }
public int flow;
public int capacity;
}
/*
Creates the flow network from an original network.
*/
static Network createFlowNetwork( final Network network )
{
// Need to create a new graph with the same nodes, and edges with capacity 1.
// If the network is undirected, it has to be transformed into a directed network.
Network flow = new Network(true);
flow.allNodes = new Bag( network.allNodes );
final int N = NetworkStatistics.getNumberNodes(network);
if( network.isDirected() )
{
for( int i = 0 ; i < N ; i++ )
{
final Object node = network.allNodes.objs[i];
final Bag edgesIn = network.getEdgesIn(node);
for( int j = 0 ; j < edgesIn.numObjs ; j++ )
flow.addEdge( ((Edge)(edgesIn.objs[j])).from(), node, new FlowData(0,1) );
}
}
else // undirected
{
for( int i = 0 ; i < N ; i++ )
{
final Object node = network.allNodes.objs[i];
Bag edges = network.getEdgesIn(node);
for( int j = 0 ; j < edges.numObjs ; j++ )
{
flow.addEdge( ((Edge)(edges.objs[j])).getOtherNode(node), node, new FlowData(0,1) );
}
}
}
return flow;
}
/*
Computes the maximum flow in the network, starting from startNode and ending in endNode.
Assumes the network is directed!
*/
static int maxFlow( final Network network, final Object startNode, final Object endNode )
{
final int N = NetworkStatistics.getNumberNodes(network);
final int startIndex = network.getNodeIndex(startNode);
final int endIndex = network.getNodeIndex(endNode);
// reset all flows to 0
for( int i = 0 ; i < N ; i++ )
{
final Object node = network.allNodes.objs[i];
final Bag edgesOut = network.getEdgesOut( node );
for( int j = 0 ; j < edgesOut.numObjs ; j++ )
{
final Edge edge = (Edge)(edgesOut.objs[j]);
((FlowData)(edge.info)).flow = 0;
}
}
// the indexes of parent nodes for paths in residual network
final Edge[] parent = new Edge[N];
// the stack
IntBag stack = new IntBag();
boolean foundPath = true;
// the main ford-fulkerson algorithm
while( foundPath )
{
// mark foundPath to false such that if no path is found, the algorithm exits the while loop
foundPath = false;
// initialize the data structure
for( int i = 0 ; i < N ; i++ )
parent[i] = null;
// initialize the stack
stack.clear();
stack.push( startIndex );
// the ``recursive'' loop
while( !stack.isEmpty() )
{
// pop a node from the stack, and look at the out-edges
final int nodeIndex = stack.pop();
final Object node = network.allNodes.objs[nodeIndex];
// if we popped the endNode (with index endIndex), we can update the flows in the network
if( nodeIndex == endIndex )
{
// compute the maximum amount that the flow can be increased along the path
int maxAmount = -1;
Edge edge = parent[endIndex];
while( edge != null )
{
final FlowData temp = (FlowData)(edge.info);
if( temp.capacity - temp.flow > maxAmount )
maxAmount = temp.capacity - temp.flow;
edge = parent[network.getNodeIndex(edge.from())];
}
// now increment the flow along the path by maxAmount
edge = parent[endIndex];
while( edge != null )
{
final FlowData temp = (FlowData)(edge.info);
temp.flow += maxAmount;
edge = parent[network.getNodeIndex(edge.from())];
}
// mark that we found a path, and break the loop
foundPath = true;
break;
}
final Bag edgesOut = network.getEdgesOut(node);
for( int i = 0 ; i < edgesOut.numObjs ; i++ )
{
final Edge edge = (Edge)(edgesOut.objs[i]);
FlowData flowData = (FlowData)(edge.info);
// if the edge has same flow as capacity, the edge is useless
if( flowData.flow >= flowData.capacity )
continue;
final Object toNode = edge.to();
final int toIndex = network.getNodeIndex(toNode);
// if the node has already been visited, the edge is also useless
if( parent[toIndex] != null || toIndex == startIndex )
continue;
// otherwise, we can process the edge
parent[toIndex] = edge;
stack.push( toIndex );
}
}
}
// compute and return the total flow in the network
int totalFlow = 0;
final Bag bag = network.getEdgesOut( startNode );
for( int i = 0 ; i < bag.numObjs ; i++ )
{
final Edge e = (Edge)(bag.objs[i]);
final FlowData data = (FlowData)(e.info);
totalFlow += data.flow;
}
return totalFlow;
}
/**
Computes the edge connectivity of a network
(i.e. the minimum number of edges that can be removed to make the graph disconnected).
*/
public static int getEdgeConnectivity( final Network network )
{
final int N = NetworkStatistics.getNumberNodes(network);
if( N == 0 )
throw new RuntimeException( "The graph has no nodes at all." );
if( N == 1 ) // if there's a single node
return NetworkStatistics.getNumberActualEdges(network);
if( !isConnected(network) )
return 0;
if( network.isDirected() )
return getDigraphEdgeConnectivity( network );
else
return getGraphEdgeConnectivity( network );
}
/**
Computes the edge connectivity of a digraph (directed graph)
(i.e. the minimum number of edges that can be removed to make the graph disconnected).
Assumes the digraph is weakly connected and non-trivial.
*/
public static int getDigraphEdgeConnectivity( final Network network )
{
final int N = NetworkStatistics.getNumberNodes(network);
// create the flow network for the computations
final Network flowNet = createFlowNetwork( network );
// initialize a min
int min = Integer.MAX_VALUE;
for( int i = 0 ; i < N ; i++ )
{
// compute the flow through the flow network
final int flow = maxFlow( flowNet, network.allNodes.objs[i], network.allNodes.objs[(i+1)%N] );
if( min > flow )
min = flow;
}
return min;
}
/**
Computes the edge connectivity of an undirected graph
(i.e. the minimum number of edges that can be removed to make the graph disconnected).
*/
public static int getGraphEdgeConnectivity( final Network network )
{
final Bag D = getDominatingSet(network);
int min = Integer.MAX_VALUE;
final Network flowNet = createFlowNetwork(network);
for( int i = 1 ; i < D.numObjs ; i++ )
{
final int flow = maxFlow( flowNet, D.objs[0], D.objs[i] );
if( min > flow )
min = flow;
}
// compute the minimum degree of the graph.
// (due to representation, it is the same as the minimum in degree, or the minimum out degree)
final int minDegree = DegreeStatistics.getMinInDegree(network);
if( min > minDegree )
min = minDegree;
return min;
}
/*
Computes a dominating set for the network
(i.e. a set of nodes such that any node in the graph is adjacent to at least one node in the set).
*/
static Bag getDominatingSet( final Network network )
{
final int N = NetworkStatistics.getNumberNodes(network);
boolean[] adjacent = new boolean[N];
Bag result = new Bag();
for( int i = 0 ; i < N ; i++ )
adjacent[i] = false;
int numAdjacent = 0;
for( int i = 0 ; i < N ; i++ )
{
if( adjacent[i] )
continue;
adjacent[i] = true;
numAdjacent++;
final Object node = network.allNodes.objs[i];
result.add(node);
Bag edges = network.getEdgesIn(node);
for( int j = 0 ; j < edges.numObjs ; j++ )
{
final Edge edge = (Edge)(edges.objs[j]);
final int index = network.getNodeIndex(edge.getOtherNode(node));
if( !adjacent[index] )
{
adjacent[index] = true;
numAdjacent++;
}
}
if( numAdjacent == N )
break;
}
return result;
}
/**
Computes the node connectivity of a network
(i.e. the minimum number of nodes that can be removed to make the graph disconnected).
*/
public static int getNodeConnectivity( final Network network )
{
final int N = NetworkStatistics.getNumberNodes(network);
if( N == 0 )
throw new RuntimeException( "The graph has no nodes at all." );
if( N == 1 ) // if there's a single node
return 0;
if( !isConnected(network) )
return 0;
int result = N-1;
// Need to create a new graph with the doubled nodes, and edges with capacity 1.
// If the network is undirected, it has to be transformed into a directed network.
Network flow = new Network(true);
final Object[] w1s = new Object[N];
final Object[] w2s = new Object[N];
final Edge[] edgeW12 = new Edge[N];
for( int i = 0 ; i < N ; i++ )
{
w1s[i] = new Object();
flow.addNode(w1s[i]);
w2s[i] = new Object();
flow.addNode(w2s[i]);
edgeW12[i] = new Edge( w1s[i], w2s[i], new FlowData(0,1) );
flow.addEdge( edgeW12[i] );
}
if( network.isDirected() )
{
for( int i = 0 ; i < N ; i++ )
{
final Object node = network.allNodes.objs[i];
final Bag edgesIn = network.getEdgesIn(node);
for( int j = 0 ; j < edgesIn.numObjs ; j++ )
flow.addEdge( w2s[network.getNodeIndex(((Edge)(edgesIn.objs[j])).from())], w1s[network.getNodeIndex(node)], new FlowData(0,1) );
}
}
else // undirected
{
for( int i = 0 ; i < N ; i++ )
{
final Object node = network.allNodes.objs[i];
Bag edges = network.getEdgesIn(node);
for( int j = 0 ; j < edges.numObjs ; j++ )
{
flow.addEdge( w2s[network.getNodeIndex(((Edge)(edges.objs[j])).getOtherNode(node))], w1s[network.getNodeIndex(node)], new FlowData(0,1) );
}
}
}
// now the network is created, we need to:
// 1. select an arbitrary vertex u of minimum degree
// 2. compute k1 <- min K(u,v) with v in V\{u} and v not adjacent to u
// 3. compute k2 <- min K(x,y) with x and y adjacent to u and x not adjacent to y
// 4. the node connectivity equals the minimum of k1 and k2
// so, let's get hacking
// 1. select an arbitrary vertex u of minimum degree.
int uIndex = 0;
int minDegree = Integer.MAX_VALUE;
for( int i = 0 ; i < N; i++ )
{
final int deg = network.getEdgesOut(network.allNodes.objs[i]).numObjs+network.getEdgesIn(network.allNodes.objs[i]).numObjs;
if( minDegree > deg )
{
uIndex = i;
minDegree = deg;
}
}
// for easier access
final Object u = network.allNodes.objs[uIndex];
final Bag uIn = network.getEdgesIn(u);
final Bag uOut = network.getEdgesOut(u);
// 2. compute k1 <- min K(u,v) with v in V\{u} and v not adjacent to u
// NOTE: if the graph is directed, we use both K(u,v) and K(v,u)
final boolean[] adjacent = new boolean[N];
if( network.isDirected() )
{
// compute min K(u,v) for all v where (u,v) does not exist in the graph
for( int i = 0 ; i < N ; i++ )
adjacent[i] = false;
adjacent[uIndex] = true;
for( int i = 0 ; i < uOut.numObjs ; i++ )
{
final Object toNode = ((Edge)(uOut.objs[i])).to();
adjacent[network.getNodeIndex(toNode)] = true;
}
for( int i = 0 ; i < N ; i++ )
if( !adjacent[i] )
{
final int res = maxFlowNodeConnectivity( flow, u, uIndex, network.allNodes.objs[i], i, w1s, w2s, edgeW12 );
if( result > res )
result = res;
}
// compute min K(v,u) for all v where (v,u) does not exist in the graph
for( int i = 0 ; i < N ; i++ )
adjacent[i] = false;
adjacent[uIndex] = true;
for( int i = 0 ; i < uIn.numObjs ; i++ )
{
final Object toNode = ((Edge)(uIn.objs[i])).from();
adjacent[network.getNodeIndex(toNode)] = true;
}
for( int i = 0 ; i < N ; i++ )
if( !adjacent[i] )
{
final int res = maxFlowNodeConnectivity( flow, network.allNodes.objs[i], i, u, uIndex, w1s, w2s, edgeW12 );
if( result > res )
result = res;
}
}
else // the network is not directed
{
for( int i = 0 ; i < N ; i++ )
adjacent[i] = false;
adjacent[uIndex] = true;
for( int i = 0 ; i < uOut.numObjs ; i++ )
{
final Object toNode = ((Edge)(uOut.objs[i])).getOtherNode(u);
adjacent[network.getNodeIndex(toNode)] = true;
}
for( int i = 0 ; i < N ; i++ )
if( !adjacent[i] )
{
final int res = maxFlowNodeConnectivity( flow, u, uIndex, network.allNodes.objs[i], i, w1s, w2s, edgeW12 );
if( result > res )
result = res;
}
}
// 3. compute k2 <- min K(x,y) with x and y adjacent to u and x not adjacent to y
if( network.isDirected() )
{
for( int n1 = 0 ; n1 < uIn.numObjs+uOut.numObjs ; n1++ )
{
final Object fromNode = n1<uIn.numObjs ? ((Edge)(uIn.objs[n1])).getOtherNode(u) : ((Edge)(uOut.objs[n1-uIn.numObjs])).getOtherNode(u);
final int fromNodeIndex = network.getNodeIndex(fromNode);
for( int i = 0 ; i < N ; i++ )
adjacent[i] = false;
adjacent[uIndex] = true;
adjacent[fromNodeIndex] = true;
for( int i = 0 ; i < uIn.numObjs+uOut.numObjs ; i++ )
{
final Object toNode = i<uIn.numObjs ? ((Edge)(uIn.objs[i])).getOtherNode(u) : ((Edge)(uOut.objs[i-uIn.numObjs])).getOtherNode(u);
adjacent[network.getNodeIndex(toNode)] = true;
}
for( int i = 0 ; i < N ; i++ )
if( !adjacent[i] )
{
final int res = maxFlowNodeConnectivity( flow, fromNode, fromNodeIndex, network.allNodes.objs[i], i, w1s, w2s, edgeW12 );
if( result > res )
result = res;
}
}
}
else // the network is not directed
{
for( int n1 = 0 ; n1 < uIn.numObjs ; n1++ )
{
final Object fromNode = ((Edge)(uIn.objs[n1])).getOtherNode(u);
final int fromNodeIndex = network.getNodeIndex(fromNode);
for( int i = 0 ; i < N ; i++ )
adjacent[i] = false;
adjacent[uIndex] = true;
adjacent[fromNodeIndex] = true;
for( int i = 0 ; i < uOut.numObjs ; i++ )
{
final Object toNode = ((Edge)(uOut.objs[i])).getOtherNode(u);
adjacent[network.getNodeIndex(toNode)] = true;
}
for( int i = 0 ; i < N ; i++ )
if( !adjacent[i] )
{
final int res = maxFlowNodeConnectivity( flow, fromNode, fromNodeIndex, network.allNodes.objs[i], i, w1s, w2s, edgeW12 );
if( result > res )
result = res;
}
}
}
// 4. the node connectivity equals the minimum of k1 and k2
return result;
}
static int maxFlowNodeConnectivity( final Network flow,
final Object source,
final int sourceIndex,
final Object sink,
final int sinkIndex,
final Object[] w1s,
final Object[] w2s,
final Edge[] edgeW12 )
{
flow.removeEdge( edgeW12[sourceIndex] );
flow.removeEdge( edgeW12[sinkIndex] );
final int result = maxFlow( flow, w2s[sourceIndex], w1s[sinkIndex] );
flow.addEdge( edgeW12[sinkIndex] );
flow.addEdge( edgeW12[sourceIndex] );
return result;
}
}