package i5.las2peer.services.ocd.metrics; import i5.las2peer.services.ocd.graphs.Cover; import i5.las2peer.services.ocd.graphs.GraphType; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.la4j.vector.Vectors; import y.base.Graph; import y.base.Node; import y.base.NodeCursor; /** * Implements the extended modularity metric. */ public class ExtendedModularityMetric implements StatisticalMeasure { public ExtendedModularityMetric() { } @Override public void setParameters(Map<String, String> parameters) { } @Override public Map<String, String> getParameters() { return new HashMap<String, String>(); } @Override public Set<GraphType> compatibleGraphTypes() { Set<GraphType> compatibleTypes = new HashSet<GraphType>(); compatibleTypes.add(GraphType.DIRECTED); compatibleTypes.add(GraphType.ZERO_WEIGHTS); return compatibleTypes; } public double measure(Cover cover) throws InterruptedException { double metricValue = 0; Graph graph = cover.getGraph(); NodeCursor nodesA = graph.nodes(); NodeCursor nodesB = graph.nodes(); Node nodeA; Node nodeB; while(nodesA.ok()) { nodeA = nodesA.node(); nodesB.toFirst(); while(nodesB.ok()) { nodeB = nodesB.node(); if(nodeB.index() > nodeA.index()) { break; } metricValue += getNodePairModularityContribution(cover, nodesA.node(), nodesB.node()); nodesB.next(); } nodesA.next(); } if(graph.edgeCount() > 0) { metricValue /= graph.edgeCount(); } return metricValue; } /* * Returns the belonging coefficient of an edge for a certain community. * @param cover The cover being measured. * @param sourceNode The source node of the edge. * @param targetNode The target node of the edge. * @param communityIndex The community index. * @return The belonging coefficient. */ private double getEdgeBelongingCoefficient(Cover cover, Node sourceNode, Node targetNode, int communityIndex) { return cover.getBelongingFactor(sourceNode, communityIndex) * cover.getBelongingFactor(targetNode, communityIndex); } /* * Returns the modularity index contribution by the null model for two given nodes a and b and a certain community. * This contains the contribution for edge a -> b and edge b -> a. * @param cover The cover being measured. * @param nodeA The first node. * @param nodeB The second node. * @param communityIndex The community index. * @return The null model contribution value. */ private double getNullModelContribution(Cover cover, Node nodeA, Node nodeB, int communityIndex) { double coeff = cover.getBelongingFactor(nodeA, communityIndex); coeff *= cover.getBelongingFactor(nodeB, communityIndex); if(nodeA.index() != nodeB.index()) { coeff *= nodeA.outDegree() * nodeB.inDegree() + nodeA.inDegree() * nodeB.outDegree(); } else { coeff *= nodeA.outDegree() * nodeB.inDegree(); } if(coeff != 0) { coeff /= Math.pow(cover.getGraph().nodeCount(), 2); /* * Edge count cannot be 0 here due to the node degrees. */ coeff /= cover.getGraph().edgeCount(); coeff *= Math.pow(cover.getMemberships().getColumn(communityIndex).fold(Vectors.mkManhattanNormAccumulator()), 2); } return coeff; } /* * Returns the modularity index for the two nodes a and b. * This includes the edges a -> b and b -> a. * @param cover The cover being measured. * @param nodeA The first node. * @param nodeB The second node. * @return The modularity index for nodes a and b with regard to all communities. */ private double getNodePairModularityContribution(Cover cover, Node nodeA, Node nodeB) throws InterruptedException { double edgeContribution = 0; for(int i=0; i<cover.communityCount(); i++) { if(Thread.interrupted()) { throw new InterruptedException(); } double coverContribution = 0; if(nodeA.getEdgeTo(nodeB) != null) { coverContribution += getEdgeBelongingCoefficient(cover, nodeA, nodeB, i); } if(nodeB.getEdgeTo(nodeA) != null) { coverContribution += getEdgeBelongingCoefficient(cover, nodeB, nodeA, i); } double nullModelContribution = getNullModelContribution(cover, nodeA, nodeB, i); edgeContribution += coverContribution - nullModelContribution; } return edgeContribution; } }