package edu.nd.nina.alg; import java.util.Collections; import java.util.HashSet; import java.util.Hashtable; import java.util.Map.Entry; import java.util.Set; import java.util.Vector; import java.util.logging.Logger; import edu.nd.nina.Graph; import edu.nd.nina.Graphs; import edu.nd.nina.structs.Pair; import edu.nd.nina.structs.Triple; /** * Triangles and clustering coefficient * * @author Tim Weninger * * @param <V> * Vertex type, must extend Comparable<V> * @param <E> * Edge type */ public class Triangles<V extends Comparable<V>, E> { private static Logger logger = Logger.getLogger(Triangles.class.getName()); /** * Computes the distribution of average clustering coefficient as defined in * Watts and Strogatz, Collective dynamics of 'small-world' networks. * * @param graph * Graph snapshot * @param sampleNodes * Number of nodes to sample. -1 to process all nodes. * @return Triple<AverageCcF, closedTriangles, openTriangles> */ public static <V extends Comparable<V>, E> Triple<Float, Integer, Integer> getClusteringCoefficient( Graph<V, E> graph, int sampleNodes) { logger.info("Get Clustering Coefficient"); Integer closedTrianlges; Integer openTriangles; Vector<Triple<V, Integer, Integer>> nodeTriangleCount = getTriangles( graph, sampleNodes); Hashtable<Integer, Pair<Float, Float>> degreeSumCount = new Hashtable<Integer, Pair<Float, Float>>(); double sumCcf = 0.0; int closedTriads = 0; int openTriads = 0; for (int i = 0; i < nodeTriangleCount.size(); i++) { final int D = nodeTriangleCount.get(i).v2 + nodeTriangleCount.get(i).v3; final double ccf = D != 0 ? nodeTriangleCount.get(i).v2 / (double) D : 0.0; closedTriads += nodeTriangleCount.get(i).v2; openTriads += nodeTriangleCount.get(i).v3; Pair<Float, Float> sumCount = new Pair<Float, Float>((float) ccf, 1f); sumCcf += ccf; degreeSumCount.put(graph.edgesOf(nodeTriangleCount.get(i).v1) .size(), sumCount); } // get average clustering coefficient for each degree Vector<Pair<Float, Float>> degToCcfV = new Vector<Pair<Float, Float>>(); for (Entry<Integer, Pair<Float, Float>> e : degreeSumCount.entrySet()) { degToCcfV.add(new Pair<Float, Float>((float) e.getKey(), e .getValue().p1 / e.getValue().p2)); } closedTrianlges = closedTriads / 3; // each triad is counted 3 times openTriangles = openTriads; Collections.sort(degToCcfV); return new Triple<Float, Integer, Integer>( (float) (sumCcf / (float) nodeTriangleCount.size()), closedTrianlges, openTriangles); } /** * Count unique connected triples of nodes all nodes * * @param graph * Graph snapshot * @param sampleNodes * Number of nodes to sample. -1 to process all nodes. * @return Vector<Triple<Node, closedTrianlges, openTrianlges>> */ public static <V extends Comparable<V>, E> Vector<Triple<V, Integer, Integer>> getTriangles( Graph<V, E> graph, int sampleNodes) { Set<V> nbrSet = new HashSet<V>(); Vector<V> nodeV = new Vector<V>(); nodeV.addAll(graph.vertexSet()); Collections.shuffle(nodeV); if (sampleNodes == -1) { sampleNodes = graph.vertexSet().size(); } sampleNodes = Math.min(sampleNodes, graph.vertexSet().size()); logger.info("Get Triangles with " + sampleNodes + " samples"); Vector<Triple<V, Integer, Integer>> nodeTriangleCount = new Vector<Triple<V, Integer, Integer>>(); for (int node = 0; node < sampleNodes; node++) { logger.info(node + "% of triangles"); V v = nodeV.get(node); if (graph.edgesOf(v).size() < 2) { // zero triangles nodeTriangleCount.add(new Triple<V, Integer, Integer>(v, 0, 0)); continue; } // find neighborhood nbrSet.clear(); nbrSet.addAll(Graphs.neighborListOf(graph, v)); // count connected neighbors int openCount = 0, closedCount = 0; int i = 0; for (V SrcNode : nbrSet) { int j = 0; for (V dstNId : nbrSet) { if (j <= i) { if (graph.containsEdge(SrcNode, dstNId) || graph.containsEdge(dstNId, SrcNode)) { closedCount++; } // is edge else { openCount++; } } j++; } i++; } assert (2 * (openCount + closedCount) == nbrSet.size() * (nbrSet.size() - 1)); nodeTriangleCount.add(new Triple<V, Integer, Integer>(v, closedCount, openCount)); } return nodeTriangleCount; } /** * For each node count how many triangles in which it participates * * @param graph * Graph Snapshot * @return Vector<Pair<NumTriangles, Count>> */ public static <V extends Comparable<V>, E> Vector<Pair<Integer, Integer>> getTriangleParticipation( Graph<V, E> graph) { Vector<Pair<Integer, Integer>> TriangleCntV = new Vector<Pair<Integer, Integer>>(); Hashtable<Integer, Integer> TriangleCntH = new Hashtable<Integer, Integer>(); int total = graph.vertexSet().size(); int i=0; int perc = -1; for (V v : graph.vertexSet()) { if(perc < i++/(float)total * 100f){ logger.info(++perc + "%"); } final int triangles = getTrianglesForNode(graph, v); if (TriangleCntH.containsKey(triangles)) { TriangleCntH.put(triangles, TriangleCntH.get(triangles) + 1); } else { TriangleCntH.put(triangles, 1); } } for (Entry<Integer, Integer> e : TriangleCntH.entrySet()) { TriangleCntV.add(new Pair<Integer, Integer>(e.getKey(), e .getValue())); } Collections.sort(TriangleCntV); return TriangleCntV; } /** * Returns number of undirected triangles in which a node participates * * @param graph * Graph snapshot * @param v * Node to consider * @return Number of triangles */ private static <V extends Comparable<V>, E> int getTrianglesForNode( final Graph<V, E> graph, final V v) { int closedTriangles = 0; if (graph.edgesOf(v).size() < 2) { return 0; } // find neighborhood Set<V> nbrSet = new HashSet<V>(graph.edgesOf(v).size()); nbrSet.addAll(Graphs.neighborListOf(graph, v)); // count connected neighbors int i = 0; for (V srcNode : nbrSet) { int j = 0; for (V dstNode : nbrSet) { if (j <= i) { if (graph.containsEdge(srcNode, dstNode) || graph.containsEdge(dstNode, srcNode)) { closedTriangles++; } // is edge } j++; } i++; } return closedTriangles; } }