package i5.las2peer.services.ocd.algorithms; import i5.las2peer.services.ocd.graphs.Cover; import i5.las2peer.services.ocd.graphs.CoverCreationType; import i5.las2peer.services.ocd.graphs.CustomGraph; import i5.las2peer.services.ocd.graphs.GraphType; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.la4j.matrix.Matrix; import org.la4j.matrix.sparse.CCSMatrix; import y.base.Node; import y.base.NodeCursor; public class MergingOfOverlappingCommunitiesAlgorithm implements OcdAlgorithm { @Override public Map<String, String> getParameters() { return new HashMap<String, String>(); } @Override public void setParameters(Map<String, String> parameters) throws IllegalArgumentException { if(parameters.size() > 0) { throw new IllegalArgumentException(); } } @Override public CoverCreationType getAlgorithmType() { return CoverCreationType.MERGING_OF_OVERLAPPING_COMMUNITIES_ALGORITHM; } @Override public Set<GraphType> compatibleGraphTypes() { Set<GraphType> compatibleTypes = new HashSet<GraphType>(); compatibleTypes.add(GraphType.WEIGHTED); return compatibleTypes; } @Override public Cover detectOverlappingCommunities(CustomGraph graph) throws InterruptedException { Map<Node, Set<Node>> activeCommunities = new HashMap<Node, Set<Node>>(); Map<Node, Set<Node>> unactiveCommunities = new HashMap<Node, Set<Node>>(); Map<Node, Node> deactivatedBy = new HashMap<Node, Node>(); Map<Node, Map<Node, Double>> inclusionAlphas = new HashMap<Node, Map<Node, Double>>(); Map<Node, Double> alphaBounds = new HashMap<Node, Double>(); Map<Node, Set<Node>> communityNeighbors = new HashMap<Node, Set<Node>>(); Map<Node, Double> nodeDegrees = new HashMap<Node, Double>(); Map<Node, Map<Node, Double>> internalNeighborDegrees = new HashMap<Node, Map<Node, Double>>(); Map<Node, Double> communityDegrees = new HashMap<Node, Double>(); Map<Node, Double> internalCommunityDegrees = new HashMap<Node, Double>(); Set<Node> mainCommunities = new HashSet<Node>(); Set<Node> inclusionNodes = new HashSet<Node>(); init(graph, activeCommunities, inclusionAlphas, alphaBounds, communityNeighbors, nodeDegrees, internalNeighborDegrees, communityDegrees, internalCommunityDegrees); Set<Node> deactivatedCommunities = new HashSet<Node>(); double maxAlpha; double alpha; Node communityId; int minCommunitySize; while(!activeCommunities.isEmpty()) { minCommunitySize = Integer.MAX_VALUE; for(Set<Node> community : activeCommunities.values()) { if(Thread.interrupted()) { throw new InterruptedException(); } if(community.size() < minCommunitySize) { minCommunitySize = community.size(); } } deactivatedCommunities.clear(); for(Map.Entry<Node, Set<Node>> entry : activeCommunities.entrySet()) { if(Thread.interrupted()) { throw new InterruptedException(); } if(entry.getValue().size() == minCommunitySize) { maxAlpha = 0; inclusionNodes.clear(); communityId = entry.getKey(); for(Node neighbor : communityNeighbors.get(communityId)) { alpha = calculateInclusionAlpha(internalCommunityDegrees.get(communityId), communityDegrees.get(communityId), internalNeighborDegrees.get(communityId).get(neighbor), nodeDegrees.get(neighbor)); if(alpha >= maxAlpha) { if(alpha > maxAlpha) { maxAlpha = alpha; inclusionNodes.clear(); } inclusionNodes.add(neighbor); } } if(!inclusionNodes.isEmpty()) { updateCommunity(graph, communityId, inclusionNodes, maxAlpha, activeCommunities, communityNeighbors, nodeDegrees, internalNeighborDegrees, communityDegrees, internalCommunityDegrees, inclusionAlphas, alphaBounds); } if(didDeactivate(graph, communityId, activeCommunities, unactiveCommunities, deactivatedBy)) { deactivatedCommunities.add(communityId); if(unactiveCommunities.size() > graph.nodeCount() - Math.log(graph.nodeCount())) { mainCommunities.add(communityId); } } } } for(Node deactivedId : deactivatedCommunities) { if(Thread.interrupted()) { throw new InterruptedException(); } activeCommunities.remove(deactivedId); } } double resolutionAlpha = determineResolutionAlpha(graph, inclusionAlphas, deactivatedBy, mainCommunities); Matrix memberships = determineMembershipMatrix(graph, unactiveCommunities, deactivatedBy, inclusionAlphas, resolutionAlpha); return new Cover(graph, memberships); } /* * Calculates the membership matrix for the output cover. * @param graph The graph being analyzed. * @param unactiveCommunities The detected (unactivated) communities. * @param deactivatedBy A mapping from the id of each community to the id of the community that it was deactivated by. * Note that the mapping returns NULL for the community that was deactivated last. * @param inclusionAlphas A mapping from all community members to their inclusion alphas. * @param resolutionAlpha The resolution alpha. * @return The membership matrix. */ private Matrix determineMembershipMatrix(CustomGraph graph, Map<Node, Set<Node>> unactiveCommunities, Map<Node, Node> deactivatedBy, Map<Node, Map<Node, Double>> inclusionAlphas, double resolutionAlpha) throws InterruptedException { Map<Node, Integer> originalCommunitySizes = new HashMap<Node, Integer>(); for(Map.Entry<Node, Set<Node>> entry : unactiveCommunities.entrySet()) { originalCommunitySizes.put(entry.getKey(), entry.getValue().size()); Iterator<Node> memberIt = entry.getValue().iterator(); while(memberIt.hasNext()) { if(Thread.interrupted()) { throw new InterruptedException(); } if(resolutionAlpha > inclusionAlphas.get(entry.getKey()).get(memberIt.next())) { memberIt.remove(); } } } Iterator<Map.Entry<Node, Set<Node>>> entryIt = unactiveCommunities.entrySet().iterator(); Map.Entry<Node, Set<Node>> entry; Node deactivatorId; while(entryIt.hasNext()) { if(Thread.interrupted()) { throw new InterruptedException(); } entry = entryIt.next(); deactivatorId = deactivatedBy.get(entry.getKey()); if(deactivatorId != null && ( unactiveCommunities.get(deactivatorId) == null || unactiveCommunities.get(deactivatorId).size() >= originalCommunitySizes.get(entry.getKey()) ) ) { entryIt.remove(); } } Matrix memberships = new CCSMatrix(graph.nodeCount(), unactiveCommunities.size()); int communityIndex = 0; for(Set<Node> community : unactiveCommunities.values()) { for(Node member : community) { if(Thread.interrupted()) { throw new InterruptedException(); } memberships.set(member.index(), communityIndex, 1); } communityIndex++; } return memberships; } /* * Returns the resolution alpha, i.e. the alpha with the most stable plateau of 1/alpha. * @param graph The graph being analyzed. * @param inclusionAlphas A mapping from all community members to their inclusion alphas. * @param deactivatedBy A mapping from the id of each community to the id of the community that it was deactivated by. * Note that the mapping returns NULL for the community that was deactivated last. * @param mainCommunities The ids of a selection of communities that form the main branches of the community dendrogram. * I.e. communities which were deactivated latest. * @return The resolution alpha. */ private double determineResolutionAlpha(CustomGraph graph, Map<Node, Map<Node, Double>> inclusionAlphas, Map<Node, Node> deactivatedBy, Set<Node> mainCommunities) throws InterruptedException { TreeSet<Double> alphaSequence = determineUnitAlphaSequence(inclusionAlphas); double resolutionAlpha = 3 * calculateSingleSequenceResolutionAlpha(alphaSequence); for(Node communityId : mainCommunities) { alphaSequence = determineCommunityAlphaSequence(graph, inclusionAlphas, deactivatedBy, communityId); resolutionAlpha += calculateSingleSequenceResolutionAlpha(alphaSequence) / mainCommunities.size(); } double normalizationCoefficient = 3d; if(!mainCommunities.isEmpty()) { normalizationCoefficient++; } return resolutionAlpha / normalizationCoefficient; } /* * Calculates the resolution alpha for a given alpha sequence. * @param alphaSequence An ordered sequence of alpha values. * @return The resolution alpha. */ private double calculateSingleSequenceResolutionAlpha(TreeSet<Double> alphaSequence) throws InterruptedException { double resolutionAlpha; if(alphaSequence.size() <= 2) { resolutionAlpha = alphaSequence.first(); } else { double maxPlateauSize = 0; double plateauSize; double lastAlpha; double alpha; Iterator<Double> it = alphaSequence.descendingIterator(); it.next(); lastAlpha = it.next(); resolutionAlpha = lastAlpha; while(it.hasNext()) { if(Thread.interrupted()) { throw new InterruptedException(); } alpha = it.next(); plateauSize = 1/alpha - 1/lastAlpha; if(plateauSize > maxPlateauSize) { maxPlateauSize = plateauSize; resolutionAlpha = lastAlpha; } lastAlpha = alpha; } } return resolutionAlpha; } /* * Determines the joined alpha sequence of all communities. * @param inclusionAlphas A mapping from all community members to their inclusion alphas. * @return The joined alpha sequence. */ private TreeSet<Double> determineUnitAlphaSequence(Map<Node, Map<Node, Double>> inclusionAlphas) throws InterruptedException { TreeSet<Double> alphaSequence = new TreeSet<Double>(); for(Map<Node, Double> alphaMap : inclusionAlphas.values()) { for(Double alpha : alphaMap.values()) { if(Thread.interrupted()) { throw new InterruptedException(); } alphaSequence.add(alpha); } } return alphaSequence; } /* * Determines the alpha sequence of a single community. * @param graph The graph being analyzed. * @param inclusionAlphas A mapping from all community members to their inclusion alphas. * @param deactivatedBy A mapping from the id of each community to the id of the community that it was deactivated by. * Note that the mapping returns NULL for the community that was deactivated last. * @param communityId The community id. * @return The community's alpha sequence. */ private TreeSet<Double> determineCommunityAlphaSequence(CustomGraph graph, Map<Node, Map<Node, Double>> inclusionAlphas, Map<Node, Node> deactivatedBy, Node communityId) throws InterruptedException { TreeSet<Double> alphaSequence = new TreeSet<Double>(); NodeCursor nodes = graph.nodes(); Node node; Node currentCommunityId; while(nodes.ok()) { if(Thread.interrupted()) { throw new InterruptedException(); } node = nodes.node(); currentCommunityId = communityId; while(!inclusionAlphas.get(currentCommunityId).containsKey(node)) { currentCommunityId = deactivatedBy.get(currentCommunityId); } alphaSequence.add(inclusionAlphas.get(currentCommunityId).get(node)); nodes.next(); } return alphaSequence; } /* * Deactivates a community if it contains all graph nodes or if it equals another community. * Note that it is not yet removed from the active communities due to concurrency issues. * @param graph The graph to be analyzed. * @param communityId The community id. * @param activeCommunities A mapping from the ids of active communities to their community members. * @param unactiveCommunities A mapping from the ids of unactive communities to their community members. * @param deactivatedBy A mapping from the id of each community to the id of the community that it was deactivated by. * Note that the mapping returns NULL for the community that was deactivated last. * @return TRUE if the community was deactivated, else FALSE. */ private boolean didDeactivate(CustomGraph graph, Node communityId, Map<Node, Set<Node>> activeCommunities, Map<Node, Set<Node>> unactiveCommunities, Map<Node, Node> deactivatedBy) throws InterruptedException { Set<Node> community = activeCommunities.get(communityId); Iterator<Map.Entry<Node, Set<Node>>> entryIt = activeCommunities.entrySet().iterator(); Map.Entry<Node, Set<Node>> entry; Node deactivatorId = null; while(entryIt.hasNext()) { if(Thread.interrupted()) { throw new InterruptedException(); } entry = entryIt.next(); if(entry.getKey() != communityId) { if(!deactivatedBy.containsKey(entry.getKey()) && entry.getValue().equals(community)) { deactivatorId = entry.getKey(); break; } } } if(deactivatorId != null || community.size() == graph.nodeCount()) { unactiveCommunities.put(communityId, community); if(deactivatorId != null) { deactivatedBy.put(communityId, deactivatorId); } return true; } else { return false; } } /* * Calculates the inclusion alpha for a new candidate community member. * @param internalCommunityDegree The weighted internal community degree. * @param totalCommunityDegree The weighted total community degree. * @param internalNeighborDegree The weighted internal neighbor degree. * @param totalNeighborDegree The weighted total neighbor degree. * @return The inclusion alpha. */ private double calculateInclusionAlpha(double internalCommunityDegree, double totalCommunityDegree, double internalNeighborDegree, double totalNeighborDegree) { double alpha = Math.log10(internalCommunityDegree + 2*internalNeighborDegree + 1) - Math.log10(internalCommunityDegree + 1); alpha /= Math.log10(totalCommunityDegree + totalNeighborDegree) - Math.log10(totalCommunityDegree); return alpha; } /* * Updates all community parameters after new nodes were added a community. * @param graph The graph being analyzed. * @param communityId The id node of the community the new nodes were added to. * @param inclusionNodes The nodes added to the community. * @param inclusionAlpha The inclusion alpha of the new nodes. * @param communities A mapping from each community index node to the community members. * @param communityNeighbors A mapping from each community index node to the community neighbors. * @param nodeDegrees A mapping from each node to its weighted degree. * @param internalNeighborDegrees A mapping from each community index node to the weighted internal degree. * @param communityDegrees A mapping from each community index node to the weighted total community degree. * @param internalCommunityDegrees A mapping from each community index node to the weighted internal community degree. * @param inclusionAlphas A mapping from each community index node to the community member inclusion alphas. * @param alphaBounds A mapping from each community index node to the upper bound for inclusion alphas. */ private void updateCommunity(CustomGraph graph, Node communityId, Set<Node> inclusionNodes, double inclusionAlpha, Map<Node, Set<Node>> communities, Map<Node, Set<Node>> communityNeighbors, Map<Node, Double> nodeDegrees, Map<Node, Map<Node, Double>> internalNeighborDegrees, Map<Node, Double> communityDegrees, Map<Node, Double> internalCommunityDegrees, Map<Node, Map<Node, Double>> inclusionAlphas, Map<Node, Double> alphaBounds) throws InterruptedException { double internalCommunityDegree; double totalCommunityDegree; NodeCursor successors; Node neighbor; double internalNeighborDegree; if(inclusionAlpha > alphaBounds.get(communityId)) { inclusionAlpha = alphaBounds.get(communityId); } else { alphaBounds.put(communityId, inclusionAlpha); } for(Node inclusionNode : inclusionNodes) { if(Thread.interrupted()) { throw new InterruptedException(); } /* * Update of community values. */ communityNeighbors.get(communityId).remove(inclusionNode); internalCommunityDegree = internalCommunityDegrees.get(communityId); internalCommunityDegree += 2 * internalNeighborDegrees.get(communityId).get(inclusionNode); internalCommunityDegrees.put(communityId, internalCommunityDegree); internalNeighborDegrees.get(communityId).remove(inclusionNode); totalCommunityDegree = communityDegrees.get(communityId); totalCommunityDegree += nodeDegrees.get(inclusionNode); communityDegrees.put(communityId, totalCommunityDegree); inclusionAlphas.get(communityId).put(inclusionNode, inclusionAlpha); communities.get(communityId).add(inclusionNode); successors = inclusionNode.successors(); /* * Neighborhood update. */ while(successors.ok()) { neighbor = successors.node(); if(communityNeighbors.get(communityId).contains(neighbor)) { internalNeighborDegree = internalNeighborDegrees.get(communityId).get(neighbor); internalNeighborDegree += graph.getEdgeWeight(inclusionNode.getEdgeTo(neighbor)); internalNeighborDegrees.get(communityId).put(neighbor, internalNeighborDegree); } else if(!communities.get(communityId).contains(neighbor)) { communityNeighbors.get(communityId).add(neighbor); internalNeighborDegree = graph.getEdgeWeight(inclusionNode.getEdgeTo(neighbor)); internalNeighborDegrees.get(communityId).put(neighbor, internalNeighborDegree); } successors.next(); } } } /* * Initializes all parameters other than graph for the algorithm execution. * @param graph The graph being analyzed. * @param communities A mapping from each community index node to the community members. * @param inclusionAlphas A mapping from each community index node to the community member inclusion alphas. * @param alphaBounds A mapping from each community index node to the upper bound for inclusion alphas. * @param communityNeighbors A mapping from each community index node to the community neighbors. * @param weightedNodeDegrees A mapping from each node to its weighted node degree. * @param internalWeightedNeighborDegrees A mapping from each community index node to the weighted internal degrees * of the community neighbors. * @param weightedCommunityDegrees A mapping from each community index node to the weighted total community degree. * @param internalWeightedCommunityDegrees A mapping from each community index node to the weighted internal community degree. */ private void init(CustomGraph graph, Map<Node, Set<Node>> communities, Map<Node, Map<Node, Double>> inclusionAlphas, Map<Node, Double> alphaBounds, Map<Node, Set<Node>> communityNeighbors, Map<Node, Double> weightedNodeDegrees, Map<Node, Map<Node, Double>> internalWeightedNeighborDegrees, Map<Node, Double> weightedCommunityDegrees, Map<Node, Double> internalWeightedCommunityDegrees) throws InterruptedException { NodeCursor nodes = graph.nodes(); NodeCursor successors; Node node; Node neighbor; double edgeWeight; Set<Node> neighbors; Map<Node, Double> internalWeightedCommunityNeighborDegrees; Set<Node> communityMembers; Map<Node, Double> communityAlphas; double weightedDegree; while(nodes.ok()) { if(Thread.interrupted()) { throw new InterruptedException(); } node = nodes.node(); weightedDegree = 0; successors = node.successors(); neighbors = new HashSet<Node>(); internalWeightedCommunityNeighborDegrees = new HashMap<Node, Double>(); while(successors.ok()) { neighbor = successors.node(); neighbors.add(neighbor); edgeWeight = graph.getEdgeWeight(node.getEdgeTo(neighbor)); weightedDegree += edgeWeight; internalWeightedCommunityNeighborDegrees.put(neighbor, edgeWeight); successors.next(); } weightedNodeDegrees.put(node, weightedDegree); weightedCommunityDegrees.put(node, weightedDegree); internalWeightedCommunityDegrees.put(node, 0d); communityNeighbors.put(node, neighbors); internalWeightedNeighborDegrees.put(node, internalWeightedCommunityNeighborDegrees); communityMembers = new HashSet<Node>(); communityMembers.add(node); communities.put(node, communityMembers); communityAlphas = new HashMap<Node, Double>(); communityAlphas.put(node, Double.POSITIVE_INFINITY); inclusionAlphas.put(node, communityAlphas); alphaBounds.put(node, Double.POSITIVE_INFINITY); nodes.next(); } } }