package net.sourceforge.jabm.report;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import net.sourceforge.jabm.agent.Agent;
import net.sourceforge.jabm.event.SimEvent;
import net.sourceforge.jabm.event.SimulationEvent;
import net.sourceforge.jabm.util.HashCodeComparator;
import org.apache.commons.collections15.Transformer;
import org.apache.commons.math3.stat.descriptive.SummaryStatistics;
import org.apache.log4j.Logger;
import edu.uci.ics.jung.algorithms.metrics.TriadicCensus;
import edu.uci.ics.jung.algorithms.shortestpath.DistanceStatistics;
import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath;
import edu.uci.ics.jung.graph.DirectedGraph;
import edu.uci.ics.jung.graph.Graph;
public class WeightedGraphMetrics extends AbstractReportVariables {
protected RelationshipTracker relationshipTracker;
protected Map<Agent, Double> clusteringCoefficients;
protected SummaryStatistics clusteringStats = new SummaryStatistics();
protected long timeStamp;
protected SummaryStatistics degreeStats = new SummaryStatistics();
protected HashMap<Agent,Integer> inDegreeByAgent;
protected HashMap<Agent, Integer> outDegreeByAgent;
protected long[] triadCounts = new long[16];
protected double diameter;
protected double averagePathLength;
private Map<Agent, Double> vertexStrengthByAgent =
new HashMap<Agent, Double>();
static Logger logger = Logger.getLogger(WeightedGraphMetrics.class);
public WeightedGraphMetrics(RelationshipTracker relationshipTracker) {
super("graphmetrics");
this.relationshipTracker = relationshipTracker;
}
public WeightedGraphMetrics() {
this(null);
}
@Override
public Map<Object, Number> getVariableBindings() {
Map<Object, Number> result = super.getVariableBindings();
recordSummaryStatistics("clusteringcoefficient", result,
clusteringStats);
result.put("averagePathLength", this.averagePathLength);
result.put("diameter", this.diameter);
// LinkedList<Agent> agents =
// new LinkedList<Agent>(vertexStrengthByAgent.keySet());
// Collections.sort(agents, new HashCodeComparator<Agent>());
// for (Agent agent : agents) {
// result.put("agent" + agent.hashCode(), inDegreeByAgent.get(agent)
// + outDegreeByAgent.get(agent));
// }
for(int i=0; i<16; i++) {
result.put("triad" + i, this.triadCounts[i]);
}
return result;
}
@Override
public void compute(SimEvent event) {
super.compute(event);
computeClusteringStats();
computeDegreeStats();
computeDiameter();
computeAveragePathLength();
computeTriads();
}
public Graph<Agent, WeightedEdge> getGraph() {
return relationshipTracker.getGraph();
}
public void computeDegreeStats() {
this.degreeStats = new SummaryStatistics();
inDegreeByAgent = new HashMap<Agent, Integer>();
outDegreeByAgent = new HashMap<Agent, Integer>();
vertexStrengthByAgent = new HashMap<Agent, Double>();
Graph<Agent, WeightedEdge> graph = getGraph();
for (Agent vertex : graph.getVertices()) {
int inDegree = graph.inDegree(vertex);
int outDegree = graph.outDegree(vertex);
degreeStats.addValue(inDegree + outDegree);
inDegreeByAgent.put(vertex, inDegree);
outDegreeByAgent.put(vertex, outDegree);
vertexStrengthByAgent.put(vertex,
relationshipTracker.vertexStrength(vertex));
}
}
public void computeDiameter() {
Graph<Agent, WeightedEdge> graph = getGraph();
this.diameter = DistanceStatistics.diameter(graph,
new UnweightedShortestPath<Agent, WeightedEdge>(graph), true);
}
public void computeAveragePathLength() {
SummaryStatistics distanceStats = new SummaryStatistics();
Graph<Agent, WeightedEdge> graph = getGraph();
Transformer<Agent,Double> distances =
DistanceStatistics.averageDistances(graph,
new UnweightedShortestPath<Agent, WeightedEdge>(graph));
for(Agent agent : graph.getVertices()) {
double d = distances.transform(agent);
distanceStats.addValue(d);
}
this.averagePathLength = distanceStats.getMean();
}
public void computeClusteringStats() {
clusteringStats = new SummaryStatistics();
for (Agent vertex : getGraph().getVertices()) {
if (degree(vertex) > 0) {
double clusteringCoefficient =
computeWeightedClusteringCoefficient(vertex);
clusteringStats.addValue(clusteringCoefficient);
logger.debug("cc(" + vertex.hashCode() + ") = "
+ clusteringCoefficient);
}
}
}
public void computeTriads() {
this.triadCounts =
TriadicCensus.getCounts(
(DirectedGraph<Agent, WeightedEdge>) getGraph());
}
public double outDegree(Agent vertex) {
return relationshipTracker.outDegree(vertex);
}
public double degree(Agent vertex) {
return relationshipTracker.outDegree(vertex) +
relationshipTracker.inDegree(vertex);
}
public double vertexStrength(Agent vertex) {
return relationshipTracker.vertexStrength(vertex);
}
public double edgeStrength(Agent i, Agent j) {
return relationshipTracker.edgeStrength(i, j)
+ relationshipTracker.edgeStrength(j, i);
}
public double computeWeightedClusteringCoefficient(Agent vertex) {
double c = 0.0;
ArrayList<Agent> neighbors = new ArrayList<Agent>(relationshipTracker
.getGraph().getNeighbors(vertex));
int n = neighbors.size();
if (n < 2) {
return 0.0;
}
for (int i = 0; i < n; i++) {
Agent w = neighbors.get(i);
for (int j = 0; j < n; j++) {
if (i != j) {
Agent x = neighbors.get(j);
if (relationshipTracker.getGraph().isNeighbor(w, x)) {
c += (edgeStrength(vertex, w) + edgeStrength(vertex, x)) / 2.0;
}
}
}
}
double totalStrength = vertexStrength(vertex);
if (totalStrength < 10E-10) {
return 0.0;
}
double result = c / (totalStrength * (n - 1));
if (Double.isNaN(result)) {
logger.error(result);
}
return result;
}
public RelationshipTracker getRelationshipTracker() {
return relationshipTracker;
}
public void setRelationshipTracker(RelationshipTracker relationshipTracker) {
this.relationshipTracker = relationshipTracker;
}
public void reset(SimulationEvent event) {
// TODO Auto-generated method stub
}
public Map<Agent, Double> getClusteringCoefficients() {
return clusteringCoefficients;
}
public SummaryStatistics getClusteringStats() {
return clusteringStats;
}
/**
* Returns a <code>Map</code> of vertices to their clustering coefficients.
* The clustering coefficient cc(v) of a vertex v is defined as follows:
* <ul>
* <li/><code>degree(v) == {0,1}</code>: 0
* <li/><code>degree(v) == n, n >= 2</code>: given S, the set of neighbors
* of <code>v</code>: cc(v) = (the sum over all w in S of the number of
* other elements of w that are neighbors of w) / ((|S| * (|S| - 1) / 2).
* Less formally, the fraction of <code>v</code>'s neighbors that are also
* neighbors of each other.
* <p><b>Note</b>: This algorithm treats its argument as an undirected graph;
* edge direction is ignored.
* @param graph the graph whose clustering coefficients are to be calculated
* @see "The structure and function of complex networks, M.E.J. Newman, aps.arxiv.org/abs/cond-mat/0303516"
*/
public static <V,E> Map<V, Double> clusteringCoefficients(Graph<V,E> graph)
{
Map<V,Double> coefficients = new HashMap<V,Double>();
for (V v : graph.getVertices())
{
int n = graph.getNeighborCount(v);
if (n < 2)
coefficients.put(v, new Double(0));
else
{
// how many of v's neighbors are connected to each other?
ArrayList<V> neighbors = new ArrayList<V>(graph.getNeighbors(v));
double edge_count = 0;
for (int i = 0; i < n; i++)
{
V w = neighbors.get(i);
for (int j = i+1; j < n; j++ )
{
V x = neighbors.get(j);
edge_count += graph.isNeighbor(w, x) ? 1 : 0;
}
}
double possible_edges = (n * (n - 1))/2.0;
coefficients.put(v, new Double(edge_count / possible_edges));
}
}
return coefficients;
}
public static <V, E> int triangles(Graph<V, E> graph, V v) {
int n = graph.getNeighborCount(v);
ArrayList<V> neighbors = new ArrayList<V>(graph.getNeighbors(v));
int edge_count = 0;
for (int i = 0; i < n; i++)
{
V w = neighbors.get(i);
for (int j = i+1; j < n; j++ )
{
V x = neighbors.get(j);
edge_count += graph.isNeighbor(w, x) ? 1 : 0;
}
}
return edge_count;
}
}