diff --git a/modules/DesktopStatistics/src/main/java/org/gephi/desktop/statistics/StatisticsPanel.java b/modules/DesktopStatistics/src/main/java/org/gephi/desktop/statistics/StatisticsPanel.java index e01c5b948bf848cf96748425571b1b5339cc4f40..308a95c5f736661e1231d8dc966ff061789b112a 100644 --- a/modules/DesktopStatistics/src/main/java/org/gephi/desktop/statistics/StatisticsPanel.java +++ b/modules/DesktopStatistics/src/main/java/org/gephi/desktop/statistics/StatisticsPanel.java @@ -176,6 +176,8 @@ public class StatisticsPanel extends JPanel { Map cats = new LinkedHashMap<>(); cats.put(StatisticsUI.CATEGORY_NETWORK_OVERVIEW, new StatisticsCategory(StatisticsUI.CATEGORY_NETWORK_OVERVIEW, 100)); + cats.put(StatisticsUI.CATEGORY_COMMUNITY_DETECTION, + new StatisticsCategory(StatisticsUI.CATEGORY_COMMUNITY_DETECTION, 150)); cats.put(StatisticsUI.CATEGORY_NODE_OVERVIEW, new StatisticsCategory(StatisticsUI.CATEGORY_NODE_OVERVIEW, 200)); cats.put(StatisticsUI.CATEGORY_EDGE_OVERVIEW, new StatisticsCategory(StatisticsUI.CATEGORY_EDGE_OVERVIEW, 300)); cats.put(StatisticsUI.CATEGORY_DYNAMIC, new StatisticsCategory(StatisticsUI.CATEGORY_DYNAMIC, 400)); diff --git a/modules/StatisticsAPI/src/main/java/org/gephi/statistics/spi/StatisticsUI.java b/modules/StatisticsAPI/src/main/java/org/gephi/statistics/spi/StatisticsUI.java index 2473c9da196801067e08d73d529691b7fc93a84b..f2c394749735aefa27219ba73b5cdd8adee02d6a 100644 --- a/modules/StatisticsAPI/src/main/java/org/gephi/statistics/spi/StatisticsUI.java +++ b/modules/StatisticsAPI/src/main/java/org/gephi/statistics/spi/StatisticsUI.java @@ -64,6 +64,8 @@ public interface StatisticsUI { String CATEGORY_NETWORK_OVERVIEW = NbBundle.getMessage(StatisticsUI.class, "StatisticsUI.category.networkOverview"); + String CATEGORY_COMMUNITY_DETECTION = + NbBundle.getMessage(StatisticsUI.class, "StatisticsUI.category.communityDetection"); String CATEGORY_NODE_OVERVIEW = NbBundle.getMessage(StatisticsUI.class, "StatisticsUI.category.nodeOverview"); String CATEGORY_EDGE_OVERVIEW = @@ -128,6 +130,7 @@ public interface StatisticsUI { *
  • {@link StatisticsUI#CATEGORY_NODE_OVERVIEW}
  • *
  • {@link StatisticsUI#CATEGORY_EDGE_OVERVIEW}
  • *
  • {@link StatisticsUI#CATEGORY_DYNAMIC}
  • + *
  • {@link StatisticsUI#CATEGORY_COMMUNITY_DETECTION}
  • * Returns a custom String for defining a new category. * * @return this statistics' category diff --git a/modules/StatisticsAPI/src/main/resources/org/gephi/statistics/spi/Bundle.properties b/modules/StatisticsAPI/src/main/resources/org/gephi/statistics/spi/Bundle.properties index cc1c464c0d00d70b69fc08eee45e22633a3e564e..c2c4a84d777f609fcd60adb540cb03150b7caa1a 100644 --- a/modules/StatisticsAPI/src/main/resources/org/gephi/statistics/spi/Bundle.properties +++ b/modules/StatisticsAPI/src/main/resources/org/gephi/statistics/spi/Bundle.properties @@ -1,4 +1,5 @@ StatisticsUI.category.networkOverview= Network Overview StatisticsUI.category.nodeOverview = Node Overview StatisticsUI.category.edgeOverview = Edge Overview -StatisticsUI.category.dynamic = Dynamic \ No newline at end of file +StatisticsUI.category.dynamic = Dynamic +StatisticsUI.category.communityDetection = Community Detection \ No newline at end of file diff --git a/modules/StatisticsPlugin/src/main/java/org/gephi/statistics/plugin/StatisticalInferenceClustering.java b/modules/StatisticsPlugin/src/main/java/org/gephi/statistics/plugin/StatisticalInferenceClustering.java new file mode 100644 index 0000000000000000000000000000000000000000..cdcae00e56b870cebc6869e0bcb526d4ceb35ef1 --- /dev/null +++ b/modules/StatisticsPlugin/src/main/java/org/gephi/statistics/plugin/StatisticalInferenceClustering.java @@ -0,0 +1,969 @@ +/* + Copyright 2008-2011 Gephi + Authors : Mathieu Jacomy, Tiago Peixoto + Website : http://www.gephi.org + + This file is part of Gephi. + + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + + Copyright 2011 Gephi Consortium. All rights reserved. + + The contents of this file are subject to the terms of either the GNU + General Public License Version 3 only ("GPL") or the Common + Development and Distribution License("CDDL") (collectively, the + "License"). You may not use this file except in compliance with the + License. You can obtain a copy of the License at + http://gephi.org/about/legal/license-notice/ + or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the + specific language governing permissions and limitations under the + License. When distributing the software, include this License Header + Notice in each file and include the License files at + /cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the + License Header, with the fields enclosed by brackets [] replaced by + your own identifying information: + "Portions Copyrighted [year] [name of copyright owner]" + + If you wish your version of this file to be governed by only the CDDL + or only the GPL Version 3, indicate your decision by adding + "[Contributor] elects to include this software in this distribution + under the [CDDL or GPL Version 3] license." If you do not indicate a + single choice of license, a recipient has the option to distribute + your version of this file under either the CDDL, the GPL Version 3 or + to extend the choice of license to its licensees as provided above. + However, if you add GPL Version 3 code and therefore, elected the GPL + Version 3 license, then the option applies only if the new code is + made subject to such option by the copyright holder. + + Contributor(s): + + Portions Copyrighted 2011 Gephi Consortium. + */ + +package org.gephi.statistics.plugin; + +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import org.apache.commons.math3.special.Gamma; +import org.gephi.graph.api.Column; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.Graph; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; +import org.gephi.graph.api.Table; +import org.gephi.statistics.spi.Statistics; +import org.gephi.utils.longtask.spi.LongTask; +import org.gephi.utils.progress.Progress; +import org.gephi.utils.progress.ProgressTicket; +import org.jfree.chart.ChartFactory; +import org.jfree.chart.JFreeChart; +import org.jfree.chart.plot.PlotOrientation; +import org.jfree.data.xy.XYSeries; +import org.jfree.data.xy.XYSeriesCollection; + +/** + * @author Mathieu Jacomy & Tiago Peixoto + */ + +public class StatisticalInferenceClustering implements Statistics, LongTask { + + public static final String STAT_INF_CLASS = "stat_inf_class"; + private final boolean useWeight = false; + private boolean isCanceled; + private StatisticalInferenceClustering.CommunityStructure structure; + private ProgressTicket progress; + private double descriptionLength; + + private static double lBinom(double n, double m) { + return Gamma.logGamma(n + 1) - Gamma.logGamma(n - m + 1) - Gamma.logGamma(m + 1); + } + + @Override + public boolean cancel() { + this.isCanceled = true; + return true; + } + + @Override + public void setProgressTicket(ProgressTicket progressTicket) { + this.progress = progressTicket; + } + + @Override + public void execute(GraphModel graphModel) { + Graph graph = graphModel.getUndirectedGraphVisible(); + execute(graph); + } + + public void execute(Graph graph) { + isCanceled = false; + + graph.readLock(); + try { + structure = new StatisticalInferenceClustering.CommunityStructure(graph); + int[] comStructure = new int[graph.getNodeCount()]; + + if (graph.getNodeCount() > 0) {//Fixes issue #713 Modularity Calculation Throws Exception On Empty Graph + HashMap computedStatInfMetrics = + computePartition(graph, structure, comStructure, useWeight); + descriptionLength = computedStatInfMetrics.get("descriptionLength"); + } else { + descriptionLength = 0; + } + + saveValues(comStructure, graph, structure); + } finally { + graph.readUnlock(); + } + } + + protected HashMap computePartition(Graph graph, + StatisticalInferenceClustering.CommunityStructure theStructure, + int[] comStructure, + boolean weighted) { + isCanceled = false; + Progress.start(progress); + Random rand = new Random(); + + HashMap results = new HashMap<>(); + + if (isCanceled) { + return results; + } + boolean someChange = true; + boolean initRound = true; + while (someChange) { + //System.out.println("Number of partitions: "+theStructure.communities.size()); + someChange = false; + boolean localChange = true; + while (localChange) { + localChange = false; + int start = 0; + // Randomize + start = Math.abs(rand.nextInt()) % theStructure.N; + + int step = 0; + for (int i = start; step < theStructure.N; i = (i + 1) % theStructure.N) { + step++; + StatisticalInferenceClustering.Community bestCommunity = + updateBestCommunity(theStructure, i, initRound); + if ((theStructure.nodeCommunities[i] != bestCommunity) && (bestCommunity != null)) { + //double S_before = computeDescriptionLength(graph, theStructure); + //System.out.println("Move node "+i+" to com "+bestCommunity.id+" : S_before="+S_before); + theStructure.moveNodeTo(i, bestCommunity); + //double S_after = computeDescriptionLength(graph, theStructure); + //System.out.println("Move node "+i+" to com "+bestCommunity.id+" : S_after="+S_after+ " (Diff = "+(S_after - S_before)+")"); + localChange = true; + } + if (isCanceled) { + return results; + } + } + someChange = localChange || someChange; + initRound = false; + if (isCanceled) { + return results; + } + } + + if (someChange) { + theStructure.zoomOut(); + } + } + + fillComStructure(graph, theStructure, comStructure); + double computedDescriptionLength = computeDescriptionLength(graph, theStructure); + + results.put("descriptionLength", computedDescriptionLength); + + return results; + } + + public double delta(int node, + StatisticalInferenceClustering.Community community, + StatisticalInferenceClustering.CommunityStructure theStructure, + Double e_in, + Double e_out, + Double E, + Double B, + Double N + ) { + //System.out.println("*** Compute delta for node "+node+" with respect to community "+community.id+" ***"); + + // Node degree + double k = theStructure.weights[node]; + // Node weight: how many real nodes (not meta-nodes) the group represents + double nodeWeight = theStructure.graphNodeCount[node]; + + // Number of edges of target community (with itself or another one) + double e_r_target = community.weightSum; + // Number of edges within target community + Double e_rr_target = community.internalWeightSum; + // Number of real graph nodes of target community + int n_r_target = community.graphNodeCount; + + // Number of edges of current (where the node belongs) community (with itself or another one) + double e_r_current = theStructure.nodeCommunities[node].weightSum; + // Number of edges within current community (where the node belongs) + Double e_rr_current = theStructure.nodeCommunities[node].internalWeightSum; + // Number of real graph nodes of current community + int n_r_current = theStructure.nodeCommunities[node].graphNodeCount; + + // Description length: before + double S_b = 0.; + S_b -= Gamma.logGamma(e_out + 1); + if (e_out > 0) { + S_b += e_out * lBinom(B, 2); + } + S_b += Gamma.logGamma(e_r_current + 1); + S_b += Gamma.logGamma(e_r_target + 1); + S_b -= (e_rr_current) * Math.log(2) + Gamma.logGamma(e_rr_current + 1); + S_b -= (e_rr_target) * Math.log(2) + Gamma.logGamma(e_rr_target + 1); + S_b -= Gamma.logGamma(n_r_current + 1); + S_b -= Gamma.logGamma(n_r_target + 1); + S_b += lBinom(n_r_current + e_r_current - 1, e_r_current); + S_b += lBinom(n_r_target + e_r_target - 1, e_r_target); + S_b += lBinom(B + e_in - 1, e_in); + if (B > 1) { + S_b += Math.log(E + 1); + } + S_b += lBinom(N - 1, B - 1); + + // Count the gains and losses + // -> loop over the neighbors + double delta_e_out = 0.; + double delta_e_in = 0.; + double delta_e_r_current = -k; + double delta_e_r_target = +k; + double delta_e_rr_current = 0.; + double delta_e_rr_target = 0.; + for (ComputationEdge e : theStructure.topology[node]) { + int nei = e.target; + Float w = e.weight; + if (nei == node) { + // Node self-loops + delta_e_rr_current -= w; + delta_e_rr_target += w; + } else { + // Losses (as if the node disappeared) + if (theStructure.nodeCommunities[node] == theStructure.nodeCommunities[nei]) { + // The neighbor is in current community, so + // the node will leave the neighbor's community + delta_e_rr_current -= w; + delta_e_in -= w; + } else { + // The neighbor is not in current community, so + // the node will not leave the neighbor's community + delta_e_out -= w; + } + // Gains (as if the node reappeared) + if (community == theStructure.nodeCommunities[nei]) { + // The neighbor is in target community, so + // the node will arrive in the neighbor's community + delta_e_rr_target += w; // add weight between node and community -> OK + delta_e_in += w; + } else { + // The neighbor is not in target community, so + // the node will not arrive in the neighbor's community + delta_e_out += w; + } + } + } + Double delta_B = 0.; + if (theStructure.nodeCommunities[node].weightSum == theStructure.weights[node]) { + // The node is the only one in the community + delta_B = -1.; + } + // Note: if it were possible to add the node to an empty group, we would have to check that + // the target group is empty or not, and if so, add one to delta_B. + + // Description length: after + double S_a = 0.; + S_a -= Gamma.logGamma(e_out + delta_e_out + 1); + if (e_out + delta_e_out > 0) { + S_a += (e_out + delta_e_out) * lBinom(B + delta_B, 2); + } + S_a += Gamma.logGamma(e_r_target + delta_e_r_target + 1); + S_a -= (e_rr_target + delta_e_rr_target) * Math.log(2) + Gamma.logGamma(e_rr_target + delta_e_rr_target + 1); + S_a -= Gamma.logGamma(n_r_target + nodeWeight + 1); + S_a += lBinom(n_r_target + nodeWeight + e_r_target + delta_e_r_target - 1, e_r_target + delta_e_r_target); + if (delta_B == 0) { + // These calculations only apply if current category + // would still exist after moving the node + // (i.e. if it was not the last one) + S_a += Gamma.logGamma(e_r_current + delta_e_r_current + 1); + S_a -= (e_rr_current + delta_e_rr_current) * Math.log(2) + + Gamma.logGamma(e_rr_current + delta_e_rr_current + 1); + S_a -= Gamma.logGamma(n_r_current - nodeWeight + 1); + S_a += + lBinom(n_r_current - nodeWeight + e_r_current + delta_e_r_current - 1, e_r_current + delta_e_r_current); + } + + S_a += lBinom(B + delta_B + e_in + delta_e_in - 1, e_in + delta_e_in); + if (B + delta_B > 1) { + S_a += Math.log(E + 1); + } + S_a += lBinom(N - 1, B + delta_B - 1); + + return S_a - S_b; + } + + private StatisticalInferenceClustering.Community updateBestCommunity( + StatisticalInferenceClustering.CommunityStructure theStructure, int node_id, boolean initialization) { + // Total number of edges (graph size) + Double E = theStructure.graphWeightSum; + // Total number of edges from one community to the same one + Double e_in = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + // Total number of edges from one community to another + Double e_out = E - e_in; + // Total number of communities + Double B = (double) theStructure.communities.size(); + // Total number of nodes (not metanodes!!!) + Double N = (double) theStructure.graph.getNodeCount(); + + //System.out.println("Test best community for node "+node_id+" (currently com "+theStructure.nodeCommunities[node_id].id+") Initialization: "+initialization); + + double best = Double.MAX_VALUE; + StatisticalInferenceClustering.Community bestCommunity = null; + Set iter = theStructure.nodeConnectionsWeight[node_id].keySet(); + for (StatisticalInferenceClustering.Community com : iter) { + if (com != theStructure.nodeCommunities[node_id]) { + double deltaValue = delta(node_id, com, theStructure, e_in, e_out, E, B, N); + if (Double.isNaN(deltaValue)) { + // TODO: change this to an exception + System.out.println( + "WARNING - ALGO ERROR - Statistical inference - DELTA is NaN (this is not supposed to happen)"); + } + //System.out.println("Node "+node_id+" => com "+com.id+" DELTA="+deltaValue); + if ((deltaValue < 0 || (initialization && Math.exp(-deltaValue) < Math.random())) && + deltaValue < best) { + best = deltaValue; + bestCommunity = com; + } + } + } + + if (bestCommunity == null) { + //System.out.println("(NO CHANGE) com "+theStructure.nodeCommunities[node_id].id); + bestCommunity = theStructure.nodeCommunities[node_id]; + } else { + //System.out.println("Best community is "+bestCommunity.id); + } + return bestCommunity; + } + + double computeDescriptionLength(Graph graph, StatisticalInferenceClustering.CommunityStructure theStructure) { + // Total number of edges (graph size) + double E = theStructure.graphWeightSum; + // Total number of edges from one community to the same one + double e_in = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + // Total number of edges from one community to another + double e_out = E - e_in; + // Total number of communities + Double B = (double) theStructure.communities.size(); + // Total number of nodes (not metanodes!!!) + Double N = (double) theStructure.graph.getNodeCount(); + + // Description length + double S = 0.; + + S -= Gamma.logGamma(e_out + 1); + if (e_out > 0) { + S += e_out * lBinom(B, 2); + } + for (Community community : theStructure.communities) { + // Number of edges of community (with itself or another one) + double e_r = community.weightSum; + // Number of edges within community + double e_rr = community.internalWeightSum; + // Number of nodes in the community + int n_r = community.graphNodeCount; + + S += Gamma.logGamma(e_r + 1); + S -= (e_rr) * Math.log(2) + Gamma.logGamma(e_rr + 1); + S -= Gamma.logGamma(n_r + 1); + S += lBinom(n_r + e_r - 1, e_r); + } + + S += lBinom(B + e_in - 1, e_in); + + if (B > 1) { + S += Math.log(E + 1); + } + + S += lBinom(N - 1, B - 1); + S += Gamma.logGamma(N + 1); + S += Math.log(N); + + for (Node n : graph.getNodes()) { + // degree + double k = graph.getDegree(n); + S -= Gamma.logGamma(k + 1); + } + + return S; + } + + private int[] fillComStructure(Graph graph, StatisticalInferenceClustering.CommunityStructure theStructure, + int[] comStructure) { + int count = 0; + + for (StatisticalInferenceClustering.Community com : theStructure.communities) { + for (Integer node : com.nodes) { + StatisticalInferenceClustering.Community hidden = theStructure.invMap.get(node); + for (Integer nodeInt : hidden.nodes) { + comStructure[nodeInt] = count; + } + } + count++; + } + return comStructure; + } + + private void saveValues(int[] struct, Graph graph, StatisticalInferenceClustering.CommunityStructure theStructure) { + Table nodeTable = graph.getModel().getNodeTable(); + Column modCol = nodeTable.getColumn(STAT_INF_CLASS); + if (modCol == null) { + modCol = nodeTable.addColumn(STAT_INF_CLASS, "Inferred Class", Integer.class, 0); + } + for (Node n : graph.getNodes()) { + int n_index = theStructure.map.get(n); + n.setAttribute(modCol, struct[n_index]); + } + } + + @Override + public String getReport() { + //Distribution series + Map sizeDist = new HashMap<>(); + for (Node n : structure.graph.getNodes()) { + Integer v = (Integer) n.getAttribute(STAT_INF_CLASS); + if (!sizeDist.containsKey(v)) { + sizeDist.put(v, 0); + } + sizeDist.put(v, sizeDist.get(v) + 1); + } + + XYSeries dSeries = ChartUtils.createXYSeries(sizeDist, "Size Distribution"); + + XYSeriesCollection dataset1 = new XYSeriesCollection(); + dataset1.addSeries(dSeries); + + JFreeChart chart = ChartFactory.createXYLineChart( + "Size Distribution", + "Stat Inf Class", + "Size (number of nodes)", + dataset1, + PlotOrientation.VERTICAL, + true, + false, + false); + chart.removeLegend(); + ChartUtils.decorateChart(chart); + ChartUtils.scaleChart(chart, dSeries, false); + String imageFile = ChartUtils.renderChart(chart, "communities-size-distribution.png"); + + NumberFormat f = new DecimalFormat("#0.000"); + + String report = "

    Statistical Inference Report

    " + + "
    " + + "

    Results:

    " + + "Description Length: " + f.format(descriptionLength) + "
    " + + "Number of Communities: " + structure.communities.size() + + "

    " + imageFile + + "

    " + "

    Algorithm:

    " + + "Statistical inference of assortative community structures
    " + + "Lizhi Zhang, Tiago P. Peixoto
    " + + "Phys. Rev. Research 2 043271 (2020)
    " + + "https://dx.doi.org/10.1103/PhysRevResearch.2.043271

    " + + "

    " + + "Bayesian stochastic blockmodeling
    " + + "Tiago P. Peixoto
    " + + "Chapter in “Advances in Network Clustering and Blockmodeling,” edited by
    " + + "P. Doreian, V. Batagelj, A. Ferligoj (Wiley, 2019)
    " + + "https://dx.doi.org/10.1002/9781119483298.ch11
    " + + " "; + + return report; + } + + public double getDescriptionLength() { + return descriptionLength; + } + + static class Community { + static int count = 0; + protected int id; + double weightSum; // e_r, i.e. sum of edge weights for the community, inside and outside altogether + // Note: here we count the internal edges twice + double internalWeightSum; // e_rr, i.e. sum of internal edge weights + int graphNodeCount; // How many real nodes (useful after zoomOut) + StatisticalInferenceClustering.CommunityStructure structure; + List nodes; + HashMap connectionsWeight; + HashMap connectionsCount; + + public Community(StatisticalInferenceClustering.Community com) { + this.id = count++; + this.weightSum = 0; + structure = com.structure; + connectionsWeight = new HashMap<>(); + connectionsCount = new HashMap<>(); + nodes = new ArrayList<>(); + } + + public Community(StatisticalInferenceClustering.CommunityStructure structure) { + this.id = count++; + this.weightSum = 0; + this.structure = structure; + connectionsWeight = new HashMap<>(); + connectionsCount = new HashMap<>(); + nodes = new ArrayList<>(); + } + + public int size() { + return nodes.size(); + } + + public void seed(int node) { + nodes.add(node); + weightSum += structure.weights[node]; + internalWeightSum += structure.internalWeights[node]; + graphNodeCount += structure.graphNodeCount[node]; + } + + public boolean add(int node) { + nodes.add(node); + weightSum += structure.weights[node]; + graphNodeCount += structure.graphNodeCount[node]; + return true; + } + + public boolean remove(int node) { + boolean result = nodes.remove((Integer) node); + weightSum -= structure.weights[node]; + graphNodeCount -= structure.graphNodeCount[node]; + if (nodes.isEmpty()) { + structure.communities.remove(this); + } + return result; + } + + public String getMonitoring() { + String monitoring = ""; + int count = 0; + for (int nodeIndex : nodes) { + if (count++ > 0) { + monitoring += " "; + } + monitoring += nodeIndex; + } + return monitoring; + } + } + + class CommunityStructure { + + HashMap[] nodeConnectionsWeight; + HashMap[] nodeConnectionsCount; + HashMap map; + StatisticalInferenceClustering.Community[] nodeCommunities; + Graph graph; + double[] graphNodeCount; // number of graph nodes represented by that node + double[] weights; // The weighted degree of the nodes (in short) + double[] internalWeights; // The sum of internal edges weights + double graphWeightSum; // The weighted sum of degrees + List[] topology; + List communities; + int N; + HashMap invMap; + + + CommunityStructure(Graph graph) { + //System.out.println("### INIT COMMUNITY STRUCTURE"); + this.graph = graph; + N = graph.getNodeCount(); + invMap = new HashMap<>(); + // nodeConnectionsWeight is basically a table of, for each node, then for each community, + // how many connections they have. + nodeConnectionsWeight = new HashMap[N]; + // nodeConnectionsCount is basically the same thing but unweighted. Remarkably, in case of parallel edges, + // but not taking weights into account, nodeConnectionsWeight will still count 1 for each parallel edges, + // while nodeConnectionsCount will count just 1. + nodeConnectionsCount = new HashMap[N]; + // graphNodeCount is the number of real nodes (graph nodes) in each nodes. This is necessary because + // each node might in fact be a community of nodes (see zoomOut method) + graphNodeCount = new double[N]; + // nodeCommunities is an index of which community each node belongs to + nodeCommunities = new StatisticalInferenceClustering.Community[N]; + map = new HashMap<>(); // keeps track of the integer ids of the nodes + // The topology is basically an index of the outbound computation edges for each node + topology = new ArrayList[N]; + communities = new ArrayList<>(); + int index = 0; + weights = new double[N]; // The weight is basically the weighted degree of a node + internalWeights = new double[N]; + + NodeIterable nodesIterable = graph.getNodes(); + for (Node node : nodesIterable) { + map.put(node, index); + nodeCommunities[index] = new StatisticalInferenceClustering.Community(this); + + nodeConnectionsWeight[index] = new HashMap<>(); + nodeConnectionsCount[index] = new HashMap<>(); + weights[index] = 0; // Note: weight is degree, but we add that later on + graphNodeCount[index] = 1; + internalWeights[index] = 0; + nodeCommunities[index].seed(index); + StatisticalInferenceClustering.Community hidden = + new StatisticalInferenceClustering.Community(structure); + hidden.nodes.add(index); + invMap.put(index, hidden); + communities.add(nodeCommunities[index]); + index++; + if (isCanceled) { + nodesIterable.doBreak(); + return; + } + } + + int[] edgeTypes = graph.getModel().getEdgeTypes(); + + nodesIterable = graph.getNodes(); + for (Node node : nodesIterable) { + int node_index = map.get(node); + StatisticalInferenceClustering.Community com = nodeCommunities[node_index]; + topology[node_index] = new ArrayList<>(); + + Set uniqueNeighbors = new HashSet<>(graph.getNeighbors(node).toCollection()); + for (Node neighbor : uniqueNeighbors) { + if (node == neighbor) { + continue; + } + int neighbor_index = map.get(neighbor); + float weight = 0; + + //Sum all parallel edges weight: + for (int edgeType : edgeTypes) { + for (Edge edge : graph.getEdges(node, neighbor, edgeType)) { + if (useWeight) { + // TODO: the algorithm only works with integer weights + //weight += edge.getWeight(graph.getView()); + } else { + weight += 1; + } + } + } + + //Finally add a single edge with the summed weight of all parallel edges: + //Fixes issue #1419 Getting null pointer error when trying to calculate modularity + weights[node_index] += weight; + com.weightSum += weight; + if (node_index == neighbor_index) { + internalWeights[node_index] += weight; + } + ComputationEdge ce = new ComputationEdge(node_index, neighbor_index, weight); + topology[node_index].add(ce); + StatisticalInferenceClustering.Community adjCom = nodeCommunities[neighbor_index]; + + //System.out.println("Add links from node "+node_index+" to community "+adjCom.id); + nodeConnectionsWeight[node_index].put(adjCom, weight); + nodeConnectionsCount[node_index].put(adjCom, 1); + + StatisticalInferenceClustering.Community nodeCom = nodeCommunities[node_index]; + //System.out.println("Add links from community "+nodeCom.id+" to community "+adjCom.id); + nodeCom.connectionsWeight.put(adjCom, weight); + nodeCom.connectionsCount.put(adjCom, 1); + + //System.out.println("Add links from node "+neighbor_index+" to community "+nodeCom.id); + nodeConnectionsWeight[neighbor_index].put(nodeCom, weight); + nodeConnectionsCount[neighbor_index].put(nodeCom, 1); + + //System.out.println("Add links from community "+adjCom.id+" to community "+nodeCom.id); + adjCom.connectionsWeight.put(nodeCom, weight); + adjCom.connectionsCount.put(nodeCom, 1); + + graphWeightSum += weight; + } + + + if (isCanceled) { + nodesIterable.doBreak(); + return; + } + } + graphWeightSum /= 2.0; + } + + private void addNodeTo(int node, StatisticalInferenceClustering.Community to) { + //System.out.println("### ADD NODE "+node+" TO COMMUNITY "+to.id); + to.add(node); + nodeCommunities[node] = to; + + for (ComputationEdge e : topology[node]) { + int neighbor = e.target; + + //////// + //Add Node Connection to this community + //System.out.println("Add links from node "+neighbor+" to community "+to.id); + //System.out.println("Add links from node "+neighbor+" to community "+to.id); + nodeConnectionsWeight[neighbor].merge(to, e.weight, Float::sum); + nodeConnectionsCount[neighbor].merge(to, 1, Integer::sum); + + /////////////////// + StatisticalInferenceClustering.Community adjCom = nodeCommunities[neighbor]; + //System.out.println("Add links from community "+adjCom.id+" to community "+to.id); + //System.out.println("Add links from community "+adjCom.id+" to community "+to.id); + adjCom.connectionsWeight.merge(to, e.weight, Float::sum); + adjCom.connectionsCount.merge(to, 1, Integer::sum); + + if (node == neighbor) { + continue; + } + + //System.out.println("Add links from node "+node+" to community "+adjCom.id); + //System.out.println("Add links from node "+node+" to community "+adjCom.id); + nodeConnectionsWeight[node].merge(adjCom, e.weight, Float::sum); + nodeConnectionsCount[node].merge(adjCom, 1, Integer::sum); + + if (to != adjCom) { + //System.out.println("Add links from community "+to.id+" to community "+adjCom.id); + //System.out.println("Add links from community "+to.id+" to community "+adjCom.id); + to.connectionsWeight.merge(adjCom, e.weight, Float::sum); + + to.connectionsCount.merge(adjCom, 1, Integer::sum); + + } + } + to.internalWeightSum += nodeConnectionsWeight[node].getOrDefault(to, 0.f); + } + + private void removeNodeFromItsCommunity(int node) { + //System.out.println("### REMOVE NODE FROM ITS COMMUNITY "+node); + StatisticalInferenceClustering.Community community = nodeCommunities[node]; + community.internalWeightSum -= nodeConnectionsWeight[node].getOrDefault(community, 0.f); + for (ComputationEdge e : topology[node]) { + int neighbor = e.target; + + //////// + //Remove Node Connection to this community + Float edgesTo = nodeConnectionsWeight[neighbor].get(community); + Integer countEdgesTo = nodeConnectionsCount[neighbor].get(community); + if (countEdgesTo - 1 == 0) { + //System.out.println("REMOVE links from node "+neighbor+" to community "+community.id); + nodeConnectionsWeight[neighbor].remove(community); + nodeConnectionsCount[neighbor].remove(community); + } else { + //System.out.println("Add links from node "+neighbor+" to community "+community.id); + nodeConnectionsWeight[neighbor].put(community, edgesTo - e.weight); + nodeConnectionsCount[neighbor].put(community, countEdgesTo - 1); + } + + /////////////////// + //Remove Adjacent Community's connection to this community + StatisticalInferenceClustering.Community adjCom = nodeCommunities[neighbor]; + Float oEdgesto = adjCom.connectionsWeight.get(community); + Integer oCountEdgesto = adjCom.connectionsCount.get(community); + if (oCountEdgesto - 1 == 0) { + //System.out.println("Remove links from community "+adjCom.id+" to community "+community.id+" *"); + adjCom.connectionsWeight.remove(community); + adjCom.connectionsCount.remove(community); + } else { + //System.out.println("Remove links from community "+adjCom.id+" to community "+community.id); + adjCom.connectionsWeight.put(community, oEdgesto - e.weight); + adjCom.connectionsCount.put(community, oCountEdgesto - 1); + } + + if (node == neighbor) { + continue; + } + + if (adjCom != community) { + Float comEdgesto = community.connectionsWeight.get(adjCom); + Integer comCountEdgesto = community.connectionsCount.get(adjCom); + if (comCountEdgesto - 1 == 0) { + //System.out.println("Remove links from community "+community.id+" to community "+adjCom.id+" *"); + community.connectionsWeight.remove(adjCom); + community.connectionsCount.remove(adjCom); + } else { + //System.out.println("Remove links from community "+community.id+" to community "+adjCom.id); + community.connectionsWeight.put(adjCom, comEdgesto - e.weight); + community.connectionsCount.put(adjCom, comCountEdgesto - 1); + } + } + + Float nodeEgesTo = nodeConnectionsWeight[node].get(adjCom); + Integer nodeCountEgesTo = nodeConnectionsCount[node].get(adjCom); + if (nodeCountEgesTo - 1 == 0) { + //System.out.println("REMOVE links from node "+node+" to community "+adjCom.id+ " *"); + nodeConnectionsWeight[node].remove(adjCom); + nodeConnectionsCount[node].remove(adjCom); + } else { + //System.out.println("REMOVE links from node "+node+" to community "+adjCom.id); + nodeConnectionsWeight[node].put(adjCom, nodeEgesTo - e.weight); + nodeConnectionsCount[node].put(adjCom, nodeCountEgesTo - 1); + } + + } + community.remove(node); + } + + private void moveNodeTo(int node, StatisticalInferenceClustering.Community to) { + //System.out.println("### MOVE NODE "+node+" TO COM "+to.id); + removeNodeFromItsCommunity(node); + addNodeTo(node, to); + } + + protected void _moveNodeTo(int node, StatisticalInferenceClustering.Community to) { + // NOTE: THIS IS FOR UNIT TEST PURPOSE ONLY + moveNodeTo(node, to); + } + + protected void _zoomOut() { + // NOTE: THIS IS FOR UNIT TEST PURPOSE ONLY + zoomOut(); + } + + private void zoomOut() { + //System.out.println("### ZOOM OUT"); + int M = communities.size(); + // The new topology uses preexisting communities as nodes + ArrayList[] newTopology = new ArrayList[M]; + int index = 0; + // nodeCommunities is an index of the communities per node. + // In this context, the preexisting communities will become the nodes + // of new upper-level communities (meta-communities). + nodeCommunities = new StatisticalInferenceClustering.Community[M]; + nodeConnectionsWeight = new HashMap[M]; + nodeConnectionsCount = new HashMap[M]; + double[] oldGraphNodeCount = graphNodeCount.clone(); + HashMap newInvMap = new HashMap<>(); + for (int i = 0; i < communities.size(); i++) { + // For each community "com", that we want to transform into a node in the new topology... + StatisticalInferenceClustering.Community com = communities.get(i); + nodeConnectionsWeight[index] = new HashMap<>(); + nodeConnectionsCount[index] = new HashMap<>(); + + newTopology[index] = new ArrayList<>(); + // For each community "com", we create a meta-community nodeCommunities[index] containing only it + nodeCommunities[index] = new StatisticalInferenceClustering.Community(com); + // iter is the set of communities with which com has (weighted) links. + Set iter = com.connectionsWeight.keySet(); + // weightSum is the number of edges from the community (into itself or not) + double weightSum = 0; + double graphNodeSum = 0; + + StatisticalInferenceClustering.Community hidden = + new StatisticalInferenceClustering.Community(structure); + for (Integer nodeInt : com.nodes) { + graphNodeSum += oldGraphNodeCount[nodeInt]; + StatisticalInferenceClustering.Community oldHidden = invMap.get(nodeInt); + hidden.nodes.addAll(oldHidden.nodes); + } + newInvMap.put(index, hidden); + for (StatisticalInferenceClustering.Community adjCom : iter) { + // adjCom is an adjacent community to com + int target = communities.indexOf(adjCom); + float weight = com.connectionsWeight.get(adjCom); + if (target == index) { + weightSum += 2. * weight; + } else { + weightSum += weight; + } + ComputationEdge e = new ComputationEdge(index, target, weight); + newTopology[index].add(e); + } + weights[index] = weightSum; + graphNodeCount[index] = graphNodeSum; + internalWeights[index] = com.internalWeightSum; + nodeCommunities[index].seed(index); + + index++; + } + communities.clear(); + + for (int i = 0; i < M; i++) { + StatisticalInferenceClustering.Community com = nodeCommunities[i]; + communities.add(com); + for (ComputationEdge e : newTopology[i]) { + //System.out.println("Add links from node "+i+" to community "+nodeCommunities[e.target].id); + nodeConnectionsWeight[i].put(nodeCommunities[e.target], e.weight); + nodeConnectionsCount[i].put(nodeCommunities[e.target], 1); + //System.out.println("Add links from community "+com.id+" to community "+nodeCommunities[e.target].id); + com.connectionsWeight.put(nodeCommunities[e.target], e.weight); + com.connectionsCount.put(nodeCommunities[e.target], 1); + } + + } + + N = M; + topology = newTopology; + invMap = newInvMap; + } + + public String getMonitoring() { + String monitoring = ""; + + for (StatisticalInferenceClustering.Community com : communities) { + monitoring += "com" + com.id + "["; + int count = 0; + for (Integer node : com.nodes) { + StatisticalInferenceClustering.Community hidden = invMap.get(node); + if (count++ > 0) { + monitoring += " "; + } + monitoring += "n" + node + "(" + hidden.getMonitoring() + ")"; + } + monitoring += "] "; + } + + return monitoring; + } + + // Useful for monitoring and debugging + public boolean checkIntegrity() { + boolean integrity = true; + Double E = graphWeightSum; + Double e_in = communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + Double e_out = E - e_in; + Double B = Double.valueOf(communities.size()); + Double N = Double.valueOf(graph.getNodeCount()); + + // Check the integrity of nodeConnectionsWeight + double nodeComWeightSum = 0; + for (int node = 0; node < nodeConnectionsWeight.length; node++) { + HashMap hm = nodeConnectionsWeight[node]; + Collection values = hm.values(); + nodeComWeightSum += values.stream().mapToDouble(v -> (double) v).sum(); + } + + // TODO: what should be done, in fact, + // is to check that for each node the sum of nodeConnectionsWeight + // equals its degree. + + return integrity; + } + } + + static class ComputationEdge { + + int source; + int target; + float weight; + + public ComputationEdge(int s, int t, float w) { + source = s; + target = t; + weight = w; + } + } +} diff --git a/modules/StatisticsPlugin/src/main/java/org/gephi/statistics/plugin/builder/StatisticalInferenceBuilder.java b/modules/StatisticsPlugin/src/main/java/org/gephi/statistics/plugin/builder/StatisticalInferenceBuilder.java new file mode 100644 index 0000000000000000000000000000000000000000..408d3cfd8a9f144ca601046f1bee3c14e813c9ee --- /dev/null +++ b/modules/StatisticsPlugin/src/main/java/org/gephi/statistics/plugin/builder/StatisticalInferenceBuilder.java @@ -0,0 +1,71 @@ +/* + Copyright 2008-2011 Gephi + Authors : Mathieu Jacomy, Tiago Peixoto + Website : http://www.gephi.org + + This file is part of Gephi. + + DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + + Copyright 2011 Gephi Consortium. All rights reserved. + + The contents of this file are subject to the terms of either the GNU + General Public License Version 3 only ("GPL") or the Common + Development and Distribution License("CDDL") (collectively, the + "License"). You may not use this file except in compliance with the + License. You can obtain a copy of the License at + http://gephi.org/about/legal/license-notice/ + or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the + specific language governing permissions and limitations under the + License. When distributing the software, include this License Header + Notice in each file and include the License files at + /cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the + License Header, with the fields enclosed by brackets [] replaced by + your own identifying information: + "Portions Copyrighted [year] [name of copyright owner]" + + If you wish your version of this file to be governed by only the CDDL + or only the GPL Version 3, indicate your decision by adding + "[Contributor] elects to include this software in this distribution + under the [CDDL or GPL Version 3] license." If you do not indicate a + single choice of license, a recipient has the option to distribute + your version of this file under either the CDDL, the GPL Version 3 or + to extend the choice of license to its licensees as provided above. + However, if you add GPL Version 3 code and therefore, elected the GPL + Version 3 license, then the option applies only if the new code is + made subject to such option by the copyright holder. + + Contributor(s): + + Portions Copyrighted 2011 Gephi Consortium. + */ + +package org.gephi.statistics.plugin.builder; + +import org.gephi.statistics.plugin.StatisticalInferenceClustering; +import org.gephi.statistics.spi.Statistics; +import org.gephi.statistics.spi.StatisticsBuilder; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; + +/** + * @author Mathieu Jacomy & Tiago Peixoto + */ +@ServiceProvider(service = StatisticsBuilder.class) +public class StatisticalInferenceBuilder implements StatisticsBuilder { + + @Override + public String getName() { + return NbBundle.getMessage(StatisticalInferenceBuilder.class, "StatisticalInference.name"); + } + + @Override + public Statistics getStatistics() { + return new StatisticalInferenceClustering(); + } + + @Override + public Class getStatisticsClass() { + return StatisticalInferenceClustering.class; + } +} diff --git a/modules/StatisticsPlugin/src/main/resources/org/gephi/statistics/plugin/builder/Bundle.properties b/modules/StatisticsPlugin/src/main/resources/org/gephi/statistics/plugin/builder/Bundle.properties index 00a381e6f1cb3c1e9d0ec14c3b3ab8a08d9bb1f4..26d2f2d902ed39926fb1372490a48d72751dc714 100644 --- a/modules/StatisticsPlugin/src/main/resources/org/gephi/statistics/plugin/builder/Bundle.properties +++ b/modules/StatisticsPlugin/src/main/resources/org/gephi/statistics/plugin/builder/Bundle.properties @@ -3,6 +3,7 @@ ClusteringCoefficent.name=Clustering Coefficient GraphDistance.name=Graph Distance DegreeDistribution.name=Degree Distribution Modularity.name=Modularity +StatisticalInference.name=Stat. Inference Clustering PageRank.name=Page Rank Hits.name=HITS InOutDegree.name=InOut Degree diff --git a/modules/StatisticsPlugin/src/test/java/org/gephi/statistics/plugin/StatisticalInferenceTest.java b/modules/StatisticsPlugin/src/test/java/org/gephi/statistics/plugin/StatisticalInferenceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..4b5dd1b583a229f7a80819f64c8d2b2ddbcd27ca --- /dev/null +++ b/modules/StatisticsPlugin/src/test/java/org/gephi/statistics/plugin/StatisticalInferenceTest.java @@ -0,0 +1,812 @@ +/* +Copyright 2008-2010 Gephi +Authors : Mathieu Bastian +Website : http://www.gephi.org + +This file is part of Gephi. + +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + +Copyright 2011 Gephi Consortium. All rights reserved. + +The contents of this file are subject to the terms of either the GNU +General Public License Version 3 only ("GPL") or the Common +Development and Distribution License("CDDL") (collectively, the +"License"). You may not use this file except in compliance with the +License. You can obtain a copy of the License at +http://gephi.org/about/legal/license-notice/ +or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the +specific language governing permissions and limitations under the +License. When distributing the software, include this License Header +Notice in each file and include the License files at +/cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the +License Header, with the fields enclosed by brackets [] replaced by +your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" + +If you wish your version of this file to be governed by only the CDDL +or only the GPL Version 3, indicate your decision by adding +"[Contributor] elects to include this software in this distribution +under the [CDDL or GPL Version 3] license." If you do not indicate a +single choice of license, a recipient has the option to distribute +your version of this file under either the CDDL, the GPL Version 3 or +to extend the choice of license to its licensees as provided above. +However, if you add GPL Version 3 code and therefore, elected the GPL +Version 3 license, then the option applies only if the new code is +made subject to such option by the copyright holder. + +Contributor(s): + +Portions Copyrighted 2011 Gephi Consortium. + */ + +package org.gephi.statistics.plugin; + +import java.util.ArrayList; +import java.util.HashMap; +import junit.framework.TestCase; +import org.gephi.graph.api.Edge; +import org.gephi.graph.api.GraphModel; +import org.gephi.graph.api.Node; +import org.gephi.graph.api.NodeIterable; +import org.gephi.graph.api.UndirectedGraph; +import org.gephi.io.importer.GraphImporter; +import org.junit.Test; + +/** + * @author Mathieu Jacomy + */ + +public class StatisticalInferenceTest extends TestCase { + + private UndirectedGraph getCliquesBridgeGraph() { + GraphModel graphModel = GraphModel.Factory.newInstance(); + UndirectedGraph undirectedGraph = graphModel.getUndirectedGraph(); + + Node node0 = graphModel.factory().newNode("0"); + Node node1 = graphModel.factory().newNode("1"); + Node node2 = graphModel.factory().newNode("2"); + Node node3 = graphModel.factory().newNode("3"); + Node node4 = graphModel.factory().newNode("4"); + Node node5 = graphModel.factory().newNode("5"); + Node node6 = graphModel.factory().newNode("6"); + Node node7 = graphModel.factory().newNode("7"); + + undirectedGraph.addNode(node0); + undirectedGraph.addNode(node1); + undirectedGraph.addNode(node2); + undirectedGraph.addNode(node3); + undirectedGraph.addNode(node4); + undirectedGraph.addNode(node5); + undirectedGraph.addNode(node6); + undirectedGraph.addNode(node7); + + // Clique A + Edge edge01 = graphModel.factory().newEdge(node0, node1, false); + Edge edge12 = graphModel.factory().newEdge(node1, node2, false); + Edge edge23 = graphModel.factory().newEdge(node2, node3, false); + Edge edge30 = graphModel.factory().newEdge(node3, node0, false); + Edge edge02 = graphModel.factory().newEdge(node0, node2, false); + Edge edge13 = graphModel.factory().newEdge(node1, node3, false); + // Bridge + Edge edge04 = graphModel.factory().newEdge(node0, node4, false); + // Clique B + Edge edge45 = graphModel.factory().newEdge(node4, node5, false); + Edge edge56 = graphModel.factory().newEdge(node5, node6, false); + Edge edge67 = graphModel.factory().newEdge(node6, node7, false); + Edge edge74 = graphModel.factory().newEdge(node7, node4, false); + Edge edge46 = graphModel.factory().newEdge(node4, node6, false); + Edge edge57 = graphModel.factory().newEdge(node5, node7, false); + + undirectedGraph.addEdge(edge01); + undirectedGraph.addEdge(edge12); + undirectedGraph.addEdge(edge23); + undirectedGraph.addEdge(edge30); + undirectedGraph.addEdge(edge02); + undirectedGraph.addEdge(edge13); + undirectedGraph.addEdge(edge04); + undirectedGraph.addEdge(edge45); + undirectedGraph.addEdge(edge56); + undirectedGraph.addEdge(edge67); + undirectedGraph.addEdge(edge74); + undirectedGraph.addEdge(edge46); + undirectedGraph.addEdge(edge57); + + UndirectedGraph graph = graphModel.getUndirectedGraph(); + return graph; + } + + @Test + public void testCliquesBridgeGraph_descriptionLength() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + // At initialization, each node is in its own community. + // Here we just test the description length at init. + // We test for the know value (from GraphTools) + + double descriptionLength_atInit = sic.computeDescriptionLength(graph, theStructure); + assertEquals(36.0896, descriptionLength_atInit, 0.0001); + + // Now we move the nodes so that one community remains for each clique + StatisticalInferenceClustering.Community cA = theStructure.nodeCommunities[0]; + StatisticalInferenceClustering.Community cB = theStructure.nodeCommunities[4]; + theStructure._moveNodeTo(1, cA); + theStructure._moveNodeTo(2, cA); + theStructure._moveNodeTo(3, cA); + theStructure._moveNodeTo(5, cB); + theStructure._moveNodeTo(6, cB); + theStructure._moveNodeTo(7, cB); + + // Now we test that the description length is shorter when the communities + // match the expectations (one community per clique) + + double descriptionLength_atIdealPartition = sic.computeDescriptionLength(graph, theStructure); + assertTrue(descriptionLength_atIdealPartition < descriptionLength_atInit); + } + + @Test + public void testDescriptionLengthOneCommunity() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + // Now we move the nodes in the same community + StatisticalInferenceClustering.Community com = theStructure.nodeCommunities[0]; + theStructure._moveNodeTo(1, com); + theStructure._moveNodeTo(2, com); + theStructure._moveNodeTo(3, com); + theStructure._moveNodeTo(4, com); + theStructure._moveNodeTo(5, com); + theStructure._moveNodeTo(6, com); + theStructure._moveNodeTo(7, com); + + // Now we test that the description length is shorter when the communities + // match the expectations (one community per clique) + + double descriptionLength = sic.computeDescriptionLength(graph, theStructure); + assertEquals(29.93900552172898, descriptionLength, 0.0001); + + // Zoom out + theStructure._zoomOut(); + + descriptionLength = sic.computeDescriptionLength(graph, theStructure); + assertEquals(29.93900552172898, descriptionLength, 0.0001); + } + + @Test + public void testCommunityWeightsBookkeeping() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + // Note: at initialization, each node is in its own community. + + for (int node = 0; node < 8; node++) { + // The community for each node should have a weight equal to the degree of that node. + assertEquals(theStructure.weights[node], theStructure.nodeCommunities[node].weightSum); + // The community for each node should have an inner weight equal to zero. + assertEquals(0., theStructure.nodeCommunities[node].internalWeightSum); + } + + // Move node 1 to the same community as node 0: it now contains nodes 0 and 1 (degrees 4 and 3). + theStructure._moveNodeTo(1, theStructure.nodeCommunities[0]); + assertEquals(7., theStructure.nodeCommunities[0].weightSum); + // There is 1 internal link + assertEquals(1., theStructure.nodeCommunities[0].internalWeightSum); + + // Move node 1 to the same community as node 2: now, the community of node 0 contains just nodes 0 (degree 4). + theStructure._moveNodeTo(1, theStructure.nodeCommunities[2]); + assertEquals(4., theStructure.nodeCommunities[0].weightSum); + // There is 0 internal link + assertEquals(0., theStructure.nodeCommunities[0].internalWeightSum); + } + + @Test + public void testMiscMetricsBookkeeping() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + // Note: at initialization, each node is in its own community. + // We move the nodes to just two communities, one for each clique. + StatisticalInferenceClustering.Community cA = theStructure.nodeCommunities[0]; + StatisticalInferenceClustering.Community cB = theStructure.nodeCommunities[4]; + theStructure._moveNodeTo(1, cA); + theStructure._moveNodeTo(2, cA); + theStructure._moveNodeTo(3, cA); + theStructure._moveNodeTo(5, cB); + theStructure._moveNodeTo(6, cB); + theStructure._moveNodeTo(7, cB); + + // Total number of edges (graph size) + Double E = theStructure.graphWeightSum; + assertEquals(13., E); + + // Total number of edges from one community to the same one + Double e_in = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + assertEquals(12., e_in); // 6 inner links per clique + + // Total number of edges from one community to another + Double e_out = E - e_in; + assertEquals(1., e_out); // 1 bridge + + // Total number of communities + Double B = Double.valueOf(theStructure.communities.size()); + assertEquals(2., B); + + // Total number of nodes (not metanodes!!!) + Double N = Double.valueOf(theStructure.graph.getNodeCount()); + assertEquals(8., N); + } + + @Test + public void testSimpleDescriptionLengthDelta() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + // Note: at initialization, each node is in its own community. + + // Compute description length + double descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + + // Test moving node 1 to the same community as node 0 + int node = 1; + StatisticalInferenceClustering.Community community = theStructure.nodeCommunities[0]; // Node 0's community + + // Benchmark the delta + Double E = theStructure.graphWeightSum; + Double e_in = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + Double e_out = E - e_in; + Double B = Double.valueOf(theStructure.communities.size()); + Double N = Double.valueOf(theStructure.graph.getNodeCount()); + double descriptionLength_delta = sic.delta(node, community, theStructure, e_in, e_out, E, B, N); + + // Actually move the node + theStructure._moveNodeTo(node, community); + + // Compute description length again + double descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + + // Delta should be (approximately) equal to the difference + assertEquals(descriptionLength_after - descriptionLength_before, descriptionLength_delta, 0.0001); + } + + @Test + public void testDescriptionLengthDeltaWithZoomOut() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + // Make some groups and zoom out. + theStructure._moveNodeTo(1, theStructure.nodeCommunities[0]); + theStructure._moveNodeTo(2, theStructure.nodeCommunities[0]); + theStructure._zoomOut(); + theStructure._moveNodeTo(2, theStructure.nodeCommunities[0]); + theStructure._moveNodeTo(3, theStructure.nodeCommunities[1]); + + // Compute description length + double descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + + int node = 1; + StatisticalInferenceClustering.Community community = theStructure.nodeCommunities[0]; // Node 0's community + + // Benchmark the delta + Double E = theStructure.graphWeightSum; + Double e_in = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + Double e_out = E - e_in; + Double B = Double.valueOf(theStructure.communities.size()); + Double N = Double.valueOf(theStructure.graph.getNodeCount()); + double descriptionLength_delta = sic.delta(node, community, theStructure, e_in, e_out, E, B, N); + + // Actually move the node + theStructure._moveNodeTo(node, community); + + // Compute description length again + double descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + + // Delta should be (approximately) equal to the difference + assertEquals(descriptionLength_after - descriptionLength_before, descriptionLength_delta, 0.0001); + } + + @Test + public void testDescriptionLengthDeltaWithZoomOut_x2() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + // Make some groups and shuffle around to stress bookkeeping + theStructure._moveNodeTo(4, theStructure.nodeCommunities[0]); + theStructure._moveNodeTo(5, theStructure.nodeCommunities[1]); + theStructure._moveNodeTo(6, theStructure.nodeCommunities[2]); + theStructure._moveNodeTo(1, theStructure.nodeCommunities[0]); + theStructure._moveNodeTo(4, theStructure.nodeCommunities[5]); + theStructure._moveNodeTo(2, theStructure.nodeCommunities[3]); + theStructure._moveNodeTo(6, theStructure.nodeCommunities[7]); + //System.out.println(theStructure.getMonitoring()); + // > com0[n0(0) n1(1)] com2[n5(5) n4(4)] com6[n3(3) n2(2)] com14[n7(7) n6(6)] + + // Zoom out + theStructure._zoomOut(); + //System.out.println(theStructure.getMonitoring()); + // > com16[n0(0 1)] com18[n1(5 4)] com20[n2(3 2)] com22[n3(7 6)] + + // Shuffle around to stress bookkeeping + theStructure._moveNodeTo(2, theStructure.nodeCommunities[0]); + theStructure._moveNodeTo(0, theStructure.nodeCommunities[1]); + theStructure._moveNodeTo(1, theStructure.nodeCommunities[3]); + //System.out.println(theStructure.getMonitoring()); + // > com16[n2(3 2)] com18[n0(0 1)] com22[n3(7 6) n1(5 4)] + + // Zoom out again + theStructure._zoomOut(); + //System.out.println(theStructure.getMonitoring()); + // > com24[n0(3 2)] com26[n1(0 1)] com28[n2(7 6 5 4)] + + // Test + + // Compute description length + double descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + + int node = 1; + StatisticalInferenceClustering.Community community = theStructure.nodeCommunities[0]; // Node 0's community + + // Benchmark the delta + Double E = theStructure.graphWeightSum; + Double e_in = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + Double e_out = E - e_in; + Double B = Double.valueOf(theStructure.communities.size()); + Double N = Double.valueOf(theStructure.graph.getNodeCount()); + double descriptionLength_delta = sic.delta(node, community, theStructure, e_in, e_out, E, B, N); + + // Actually move the node + theStructure._moveNodeTo(node, community); + + // Compute description length again + double descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + + // Delta should be (approximately) equal to the difference + assertEquals(descriptionLength_after - descriptionLength_before, descriptionLength_delta, 0.0001); + } + + // The four next tests are networks from Tiago Peixoto, with a reference partition and description length. + @Test + public void testDescriptionLength_football() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "football.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + HashMap knownCommunities = new HashMap<>(); + NodeIterable nodesIterable = graph.getNodes(); + for (Node node : nodesIterable) { + Integer targetCom = (Integer) node.getAttribute("key1"); + int nodeIndex = theStructure.map.get(node); + StatisticalInferenceClustering.Community initCom = theStructure.nodeCommunities[nodeIndex]; + if (knownCommunities.containsKey(targetCom)) { + theStructure._moveNodeTo(nodeIndex, knownCommunities.get(targetCom)); + } else { + knownCommunities.put(targetCom, initCom); + } + } + + double descriptionLength = sic.computeDescriptionLength(graph, theStructure); + assertEquals(1850.2102335828238, descriptionLength, 0.0001); + } + + @Test + public void testDescriptionLength_moviegalaxies() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "moviegalaxies.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + HashMap knownCommunities = new HashMap<>(); + NodeIterable nodesIterable = graph.getNodes(); + for (Node node : nodesIterable) { + Integer targetCom = (Integer) node.getAttribute("key1"); + int nodeIndex = theStructure.map.get(node); + StatisticalInferenceClustering.Community initCom = theStructure.nodeCommunities[nodeIndex]; + if (knownCommunities.containsKey(targetCom)) { + theStructure._moveNodeTo(nodeIndex, knownCommunities.get(targetCom)); + } else { + knownCommunities.put(targetCom, initCom); + } + } + + double descriptionLength = sic.computeDescriptionLength(graph, theStructure); + assertEquals(229.04187438186472, descriptionLength, 0.0001); + } + + @Test + public void testDescriptionLength_5cliques() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "5-cliques.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + HashMap knownCommunities = new HashMap<>(); + NodeIterable nodesIterable = graph.getNodes(); + for (Node node : nodesIterable) { + Integer targetCom = (Integer) node.getAttribute("key1"); + int nodeIndex = theStructure.map.get(node); + StatisticalInferenceClustering.Community initCom = theStructure.nodeCommunities[nodeIndex]; + if (knownCommunities.containsKey(targetCom)) { + theStructure._moveNodeTo(nodeIndex, knownCommunities.get(targetCom)); + } else { + knownCommunities.put(targetCom, initCom); + } + } + + double descriptionLength = sic.computeDescriptionLength(graph, theStructure); + assertEquals(150.10880360418344, descriptionLength, 0.0001); + } + + @Test + public void testDescriptionLength_2cliques() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "two-cliques.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + HashMap knownCommunities = new HashMap<>(); + NodeIterable nodesIterable = graph.getNodes(); + for (Node node : nodesIterable) { + Integer targetCom = (Integer) node.getAttribute("key1"); + int nodeIndex = theStructure.map.get(node); + StatisticalInferenceClustering.Community initCom = theStructure.nodeCommunities[nodeIndex]; + if (knownCommunities.containsKey(targetCom)) { + theStructure._moveNodeTo(nodeIndex, knownCommunities.get(targetCom)); + } else { + knownCommunities.put(targetCom, initCom); + } + } + + double descriptionLength = sic.computeDescriptionLength(graph, theStructure); + assertEquals(43.479327707987835, descriptionLength, 0.0001); + } + + @Test + public void testMiscMetricsConsistentThroughZoomOut() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + StatisticalInferenceClustering.Community cA1 = theStructure.nodeCommunities[0]; + StatisticalInferenceClustering.Community cA2 = theStructure.nodeCommunities[3]; + StatisticalInferenceClustering.Community cB = theStructure.nodeCommunities[4]; + theStructure._moveNodeTo(1, cA1); + theStructure._moveNodeTo(2, cA1); + theStructure._moveNodeTo(5, cB); + theStructure._moveNodeTo(6, cB); + theStructure._moveNodeTo(7, cB); + + // Total number of edges (graph size) + double E_before = theStructure.graphWeightSum; + // Total number of edges from one community to the same one + double e_in_before = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + // Total number of communities + double B_before = Double.valueOf(theStructure.communities.size()); + // Total number of nodes (not metanodes!!!) + double N_before = Double.valueOf(theStructure.graph.getNodeCount()); + + ArrayList e_r_before = new ArrayList<>(); + ArrayList e_rr_before = new ArrayList<>(); + ArrayList n_r_before = new ArrayList<>(); + for (StatisticalInferenceClustering.Community community : theStructure.communities) { + // Number of edges of community (with itself or another one) + double e_r = community.weightSum; + // Number of edges within community + double e_rr = community.internalWeightSum; + // Number of nodes in the community + int n_r = community.graphNodeCount; + + e_r_before.add(e_r); + e_rr_before.add(e_rr); + n_r_before.add(n_r); + } + + theStructure._zoomOut(); + + // Total number of edges (graph size) + double E_after = theStructure.graphWeightSum; + // Total number of edges from one community to the same one + double e_in_after = theStructure.communities.stream().mapToDouble(c -> c.internalWeightSum).sum(); + // Total number of communities + double B_after = Double.valueOf(theStructure.communities.size()); + // Total number of nodes (not metanodes!!!) + double N_after = Double.valueOf(theStructure.graph.getNodeCount()); + + ArrayList e_r_after = new ArrayList<>(); + ArrayList e_rr_after = new ArrayList<>(); + ArrayList n_r_after = new ArrayList<>(); + for (StatisticalInferenceClustering.Community community : theStructure.communities) { + // Number of edges of community (with itself or another one) + double e_r = community.weightSum; + // Number of edges within community + double e_rr = community.internalWeightSum; + // Number of nodes in the community + int n_r = community.graphNodeCount; + + e_r_after.add(e_r); + e_rr_after.add(e_rr); + n_r_after.add(n_r); + } + + assertEquals(E_before, E_after); + assertEquals(e_in_before, e_in_after); + assertEquals(B_before, B_after); + assertEquals(N_before, N_after); + + assertEquals(e_r_before, e_r_after); + assertEquals(e_rr_before, e_rr_after); + assertEquals(n_r_before, n_r_after); + } + + @Test + public void testDescriptionLengthConsistentThroughZoomOut_simple() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + StatisticalInferenceClustering.Community cA1 = theStructure.nodeCommunities[0]; + StatisticalInferenceClustering.Community cA2 = theStructure.nodeCommunities[3]; + StatisticalInferenceClustering.Community cB = theStructure.nodeCommunities[4]; + theStructure._moveNodeTo(1, cA1); + theStructure._moveNodeTo(2, cA1); + theStructure._moveNodeTo(5, cB); + theStructure._moveNodeTo(6, cB); + theStructure._moveNodeTo(7, cB); + + double descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + + theStructure._zoomOut(); + + double descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + + assertEquals(descriptionLength_before, descriptionLength_after, 0.00001); + } + + @Test + public void testDescriptionLengthConsistentThroughZoomOut_complicated() { + UndirectedGraph graph = getCliquesBridgeGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + double descriptionLength_before; + double descriptionLength_after; + + //System.out.println("\n# Initial"); + //System.out.println(" Structure: "+theStructure.getMonitoring()); + //System.out.println(" DL: "+sic.computeDescriptionLength(graph, theStructure)); + + // Move the nodes in categories + //System.out.println("\n# 1st round of group rearranging"); + theStructure._moveNodeTo(1, theStructure.nodeCommunities[0]); + theStructure._moveNodeTo(3, theStructure.nodeCommunities[2]); + //System.out.println(" Structure: "+theStructure.getMonitoring()); + descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + //System.out.println(" DL: "+descriptionLength_before); + + // Zoom out + //System.out.println("\n# Zoom out"); + theStructure._zoomOut(); + //System.out.println(" Structure: "+theStructure.getMonitoring()); + descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + //System.out.println(" DL: "+descriptionLength_after); + + assertEquals(descriptionLength_before, descriptionLength_after, 0.00001); + + // Move the nodes in categories + //System.out.println("\n# 2nd round of group rearranging"); + theStructure._moveNodeTo(1, theStructure.nodeCommunities[2]); + //System.out.println(" Structure: "+theStructure.getMonitoring()); + descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + //System.out.println(" DL: "+descriptionLength_before); + + // Zoom out + //System.out.println("\n# Zoom out"); + theStructure._zoomOut(); + //System.out.println(" Structure: "+theStructure.getMonitoring()); + descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + //System.out.println(" DL: "+descriptionLength_after); + + assertEquals(descriptionLength_before, descriptionLength_after, 0.00001); + + // Move the nodes in categories + //System.out.println("\n# 3rd round of group rearranging"); + theStructure._moveNodeTo(2, theStructure.nodeCommunities[4]); + theStructure._moveNodeTo(3, theStructure.nodeCommunities[4]); + //System.out.println(" Structure: "+theStructure.getMonitoring()); + descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + //System.out.println(" DL: "+descriptionLength_before); + + // Zoom out + //System.out.println("\n# Zoom out"); + theStructure._zoomOut(); + //System.out.println(" Structure: "+theStructure.getMonitoring()); + descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + //System.out.println(" DL: "+descriptionLength_after); + + assertEquals(descriptionLength_before, descriptionLength_after, 0.00001); + } + + /* + // This test is not unitary enough, it may randomly fail by design. Useful for debugging though. + @Test + public void testMinimizationHeuristic_football() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "football.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + + sic.execute(graph); + double descriptionLength = sic.getDescriptionLength(); + + double targetDescLength = 1850.2102335828238; + double errorMargin = 0.1; + assertEquals(targetDescLength, descriptionLength, errorMargin * targetDescLength); + } + */ + + @Test + public void testMinimizationHeuristic_5cliques() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "5-cliques.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + + sic.execute(graph); + double descriptionLength = sic.getDescriptionLength(); + + double targetDescLength = 150.10880360418344; + double errorMargin = 0.01; + assertEquals(targetDescLength, descriptionLength, errorMargin * targetDescLength); + } + + @Test + public void testMinimizationHeuristic_moviegalaxies() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "moviegalaxies.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + + sic.execute(graph); + double descriptionLength = sic.getDescriptionLength(); + + double targetDescLength = 229.04187438186472; + double errorMargin = 0.1; + assertEquals(targetDescLength, descriptionLength, errorMargin * targetDescLength); + } + + @Test + public void testDescriptionLengthZoomOut_football() { + GraphModel graphModel = GraphImporter.importGraph(DummyTest.class, "football.graphml"); + UndirectedGraph graph = graphModel.getUndirectedGraph(); + StatisticalInferenceClustering sic = new StatisticalInferenceClustering(); + StatisticalInferenceClustering.CommunityStructure theStructure = sic.new CommunityStructure(graph); + + // We reproduce a given setting + StatisticalInferenceClustering.Community c0 = theStructure.nodeCommunities[74]; + theStructure._moveNodeTo(102, c0); + theStructure._moveNodeTo(107, c0); + theStructure._moveNodeTo(49, c0); + theStructure._moveNodeTo(84, c0); + theStructure._moveNodeTo(82, c0); + theStructure._moveNodeTo(77, c0); + theStructure._moveNodeTo(72, c0); + theStructure._moveNodeTo(2, c0); + theStructure._moveNodeTo(98, c0); + theStructure._moveNodeTo(10, c0); + StatisticalInferenceClustering.Community c1 = theStructure.nodeCommunities[30]; + theStructure._moveNodeTo(19, c1); + theStructure._moveNodeTo(60, c1); + theStructure._moveNodeTo(71, c1); + theStructure._moveNodeTo(18, c1); + theStructure._moveNodeTo(99, c1); + theStructure._moveNodeTo(35, c1); + theStructure._moveNodeTo(79, c1); + theStructure._moveNodeTo(38, c1); + theStructure._moveNodeTo(85, c1); + theStructure._moveNodeTo(28, c1); + theStructure._moveNodeTo(55, c1); + theStructure._moveNodeTo(6, c1); + theStructure._moveNodeTo(31, c1); + theStructure._moveNodeTo(54, c1); + StatisticalInferenceClustering.Community c2 = theStructure.nodeCommunities[20]; + theStructure._moveNodeTo(36, c2); + theStructure._moveNodeTo(75, c2); + theStructure._moveNodeTo(48, c2); + theStructure._moveNodeTo(92, c2); + theStructure._moveNodeTo(58, c2); + theStructure._moveNodeTo(59, c2); + theStructure._moveNodeTo(113, c2); + StatisticalInferenceClustering.Community c3 = theStructure.nodeCommunities[68]; + theStructure._moveNodeTo(8, c3); + theStructure._moveNodeTo(22, c3); + theStructure._moveNodeTo(78, c3); + theStructure._moveNodeTo(51, c3); + theStructure._moveNodeTo(111, c3); + theStructure._moveNodeTo(40, c3); + theStructure._moveNodeTo(7, c3); + theStructure._moveNodeTo(21, c3); + theStructure._moveNodeTo(108, c3); + StatisticalInferenceClustering.Community c4 = theStructure.nodeCommunities[70]; + theStructure._moveNodeTo(87, c4); + theStructure._moveNodeTo(64, c4); + theStructure._moveNodeTo(63, c4); + theStructure._moveNodeTo(97, c4); + theStructure._moveNodeTo(24, c4); + theStructure._moveNodeTo(66, c4); + theStructure._moveNodeTo(56, c4); + theStructure._moveNodeTo(65, c4); + theStructure._moveNodeTo(27, c4); + theStructure._moveNodeTo(95, c4); + theStructure._moveNodeTo(76, c4); + theStructure._moveNodeTo(96, c4); + theStructure._moveNodeTo(57, c4); + theStructure._moveNodeTo(91, c4); + theStructure._moveNodeTo(86, c4); + theStructure._moveNodeTo(53, c4); + theStructure._moveNodeTo(17, c4); + theStructure._moveNodeTo(12, c4); + theStructure._moveNodeTo(44, c4); + theStructure._moveNodeTo(112, c4); + StatisticalInferenceClustering.Community c5 = theStructure.nodeCommunities[103]; + theStructure._moveNodeTo(109, c5); + theStructure._moveNodeTo(37, c5); + theStructure._moveNodeTo(89, c5); + theStructure._moveNodeTo(33, c5); + theStructure._moveNodeTo(105, c5); + theStructure._moveNodeTo(25, c5); + theStructure._moveNodeTo(106, c5); + theStructure._moveNodeTo(62, c5); + theStructure._moveNodeTo(45, c5); + theStructure._moveNodeTo(1, c5); + theStructure._moveNodeTo(101, c5); + StatisticalInferenceClustering.Community c6 = theStructure.nodeCommunities[23]; + theStructure._moveNodeTo(0, c6); + theStructure._moveNodeTo(93, c6); + theStructure._moveNodeTo(9, c6); + theStructure._moveNodeTo(16, c6); + theStructure._moveNodeTo(81, c6); + theStructure._moveNodeTo(41, c6); + theStructure._moveNodeTo(50, c6); + theStructure._moveNodeTo(90, c6); + theStructure._moveNodeTo(5, c6); + theStructure._moveNodeTo(4, c6); + StatisticalInferenceClustering.Community c7 = theStructure.nodeCommunities[100]; + theStructure._moveNodeTo(39, c7); + theStructure._moveNodeTo(43, c7); + theStructure._moveNodeTo(14, c7); + theStructure._moveNodeTo(32, c7); + theStructure._moveNodeTo(47, c7); + theStructure._moveNodeTo(42, c7); + theStructure._moveNodeTo(34, c7); + theStructure._moveNodeTo(94, c7); + theStructure._moveNodeTo(13, c7); + theStructure._moveNodeTo(15, c7); + theStructure._moveNodeTo(26, c7); + theStructure._moveNodeTo(61, c7); + theStructure._moveNodeTo(29, c7); + theStructure._moveNodeTo(80, c7); + StatisticalInferenceClustering.Community c8 = theStructure.nodeCommunities[67]; + theStructure._moveNodeTo(88, c8); + theStructure._moveNodeTo(69, c8); + theStructure._moveNodeTo(83, c8); + theStructure._moveNodeTo(73, c8); + theStructure._moveNodeTo(114, c8); + theStructure._moveNodeTo(104, c8); + theStructure._moveNodeTo(11, c8); + theStructure._moveNodeTo(52, c8); + theStructure._moveNodeTo(3, c8); + theStructure._moveNodeTo(46, c8); + theStructure._moveNodeTo(110, c8); + + double descriptionLength_before = sic.computeDescriptionLength(graph, theStructure); + + theStructure._zoomOut(); + + double descriptionLength_after = sic.computeDescriptionLength(graph, theStructure); + + assertEquals(descriptionLength_before, descriptionLength_after, 0.00001); + } +} + diff --git a/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/5-cliques.graphml b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/5-cliques.graphml new file mode 100644 index 0000000000000000000000000000000000000000..2c301e2197eaab1f54ac612fef61b7ef1134a3a6 --- /dev/null +++ b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/5-cliques.graphml @@ -0,0 +1,203 @@ + + + + + + + + + + + 150.10880360418344 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + 2 + + + 2 + + + 2 + + + 2 + + + 2 + + + 3 + + + 3 + + + 3 + + + 3 + + + 3 + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/football.graphml b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/football.graphml new file mode 100644 index 0000000000000000000000000000000000000000..02d020016d4930c44532fbb8d34ff1bbf7d2f056 --- /dev/null +++ b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/football.graphml @@ -0,0 +1,1591 @@ + + + + + + + + + + + 1850.2102335828238 + + + + 9 + + + 6 + + + 3 + + + 1 + + + 9 + + + 1 + + + 3 + + + 4 + + + 4 + + + 9 + + + 1 + + + 10 + + + 0 + + + 3 + + + 0 + + + 3 + + + 9 + + + 2 + + + 0 + + + 5 + + + 2 + + + 4 + + + 4 + + + 9 + + + 10 + + + 6 + + + 0 + + + 2 + + + 10 + + + 5 + + + 5 + + + 0 + + + 3 + + + 6 + + + 0 + + + 5 + + + 11 + + + 6 + + + 0 + + + 3 + + + 1 + + + 9 + + + 0 + + + 0 + + + 7 + + + 6 + + + 8 + + + 3 + + + 7 + + + 8 + + + 10 + + + 4 + + + 1 + + + 8 + + + 0 + + + 5 + + + 2 + + + 7 + + + 11 + + + 11 + + + 3 + + + 0 + + + 2 + + + 11 + + + 3 + + + 2 + + + 7 + + + 8 + + + 4 + + + 10 + + + 2 + + + 0 + + + 1 + + + 8 + + + 1 + + + 7 + + + 2 + + + 4 + + + 4 + + + 5 + + + 5 + + + 1 + + + 5 + + + 8 + + + 1 + + + 0 + + + 7 + + + 2 + + + 8 + + + 6 + + + 10 + + + 7 + + + 7 + + + 9 + + + 5 + + + 2 + + + 2 + + + 11 + + + 1 + + + 0 + + + 3 + + + 5 + + + 1 + + + 6 + + + 9 + + + 6 + + + 3 + + + 1 + + + 4 + + + 6 + + + 8 + + + 4 + + + 7 + + + 2 + + + 8 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/moviegalaxies.graphml b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/moviegalaxies.graphml new file mode 100644 index 0000000000000000000000000000000000000000..bb58ed2924f63dfaadef936a71cbaa1c9a5eac34 --- /dev/null +++ b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/moviegalaxies.graphml @@ -0,0 +1,305 @@ + + + + + + + + + + + 229.04187438186472 + + + + 1 + + + 1 + + + 1 + + + 0 + + + 2 + + + 0 + + + 1 + + + 0 + + + 1 + + + 2 + + + 2 + + + 0 + + + 1 + + + 1 + + + 3 + + + 0 + + + 0 + + + 2 + + + 2 + + + 1 + + + 0 + + + 1 + + + 2 + + + 0 + + + 2 + + + 0 + + + 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/two-cliques.graphml b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/two-cliques.graphml new file mode 100644 index 0000000000000000000000000000000000000000..a6d33688abadb24eb15ece926482f9bb8cee0310 --- /dev/null +++ b/modules/StatisticsPlugin/src/test/resources/org/gephi/statistics/plugin/two-cliques.graphml @@ -0,0 +1,90 @@ + + + + + + + + + + + 43.479327707987835 + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + 1 + + + 1 + + + 1 + + + 1 + + + 1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/StatisticsPluginUI/src/main/java/org/gephi/ui/statistics/plugin/ModularityUI.java b/modules/StatisticsPluginUI/src/main/java/org/gephi/ui/statistics/plugin/ModularityUI.java index b44a59c1fce433c812a1fc0bf107aafcf40fdc34..cd19147451139aae32d36a86f1d786ef12d6b071 100644 --- a/modules/StatisticsPluginUI/src/main/java/org/gephi/ui/statistics/plugin/ModularityUI.java +++ b/modules/StatisticsPluginUI/src/main/java/org/gephi/ui/statistics/plugin/ModularityUI.java @@ -104,7 +104,7 @@ public class ModularityUI implements StatisticsUI { @Override public String getCategory() { - return StatisticsUI.CATEGORY_NETWORK_OVERVIEW; + return StatisticsUI.CATEGORY_COMMUNITY_DETECTION; } @Override diff --git a/modules/StatisticsPluginUI/src/main/java/org/gephi/ui/statistics/plugin/StatisticalInferenceClusteringUI.java b/modules/StatisticsPluginUI/src/main/java/org/gephi/ui/statistics/plugin/StatisticalInferenceClusteringUI.java new file mode 100644 index 0000000000000000000000000000000000000000..573c3d11185f47eaebb71f92ccf030a4937708e1 --- /dev/null +++ b/modules/StatisticsPluginUI/src/main/java/org/gephi/ui/statistics/plugin/StatisticalInferenceClusteringUI.java @@ -0,0 +1,104 @@ +/* +Copyright 2008-2010 Gephi +Authors : Mathieu Bastian +Website : http://www.gephi.org + +This file is part of Gephi. + +DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. + +Copyright 2011 Gephi Consortium. All rights reserved. + +The contents of this file are subject to the terms of either the GNU +General Public License Version 3 only ("GPL") or the Common +Development and Distribution License("CDDL") (collectively, the +"License"). You may not use this file except in compliance with the +License. You can obtain a copy of the License at +http://gephi.org/about/legal/license-notice/ +or /cddl-1.0.txt and /gpl-3.0.txt. See the License for the +specific language governing permissions and limitations under the +License. When distributing the software, include this License Header +Notice in each file and include the License files at +/cddl-1.0.txt and /gpl-3.0.txt. If applicable, add the following below the +License Header, with the fields enclosed by brackets [] replaced by +your own identifying information: +"Portions Copyrighted [year] [name of copyright owner]" + +If you wish your version of this file to be governed by only the CDDL +or only the GPL Version 3, indicate your decision by adding +"[Contributor] elects to include this software in this distribution +under the [CDDL or GPL Version 3] license." If you do not indicate a +single choice of license, a recipient has the option to distribute +your version of this file under either the CDDL, the GPL Version 3 or +to extend the choice of license to its licensees as provided above. +However, if you add GPL Version 3 code and therefore, elected the GPL +Version 3 license, then the option applies only if the new code is +made subject to such option by the copyright holder. + +Contributor(s): + +Portions Copyrighted 2011 Gephi Consortium. + */ + +package org.gephi.ui.statistics.plugin; + +import java.text.DecimalFormat; +import javax.swing.JPanel; +import org.gephi.statistics.plugin.StatisticalInferenceClustering; +import org.gephi.statistics.spi.Statistics; +import org.gephi.statistics.spi.StatisticsUI; +import org.openide.util.NbBundle; +import org.openide.util.lookup.ServiceProvider; + +@ServiceProvider(service = StatisticsUI.class) +public class StatisticalInferenceClusteringUI implements StatisticsUI { + + StatisticalInferenceClustering descriptionLength; + + @Override + public JPanel getSettingsPanel() { + return null; + } + + @Override + public void setup(Statistics statistics) { + this.descriptionLength = (StatisticalInferenceClustering) statistics; + } + + @Override + public void unsetup() { + descriptionLength = null; + } + + @Override + public Class getStatisticsClass() { + return StatisticalInferenceClustering.class; + } + + @Override + public String getValue() { + DecimalFormat df = new DecimalFormat("###.###"); + return "" + df.format(descriptionLength.getDescriptionLength()); + } + + @Override + public String getDisplayName() { + return NbBundle.getMessage(getClass(), "StatisticalInferenceClusteringUI.name"); + } + + @Override + public String getCategory() { + return StatisticsUI.CATEGORY_COMMUNITY_DETECTION; + } + + @Override + public int getPosition() { + return 600; + } + + @Override + public String getShortDescription() { + return NbBundle.getMessage(getClass(), "StatisticalInferenceClusteringUI.shortDescription"); + } + +} diff --git a/modules/StatisticsPluginUI/src/main/resources/org/gephi/ui/statistics/plugin/Bundle.properties b/modules/StatisticsPluginUI/src/main/resources/org/gephi/ui/statistics/plugin/Bundle.properties index 44e2bf4d9668acaad2534687dbb15e9b542a5290..12d4491e370d43f244b0176342db25cb883ae57a 100644 --- a/modules/StatisticsPluginUI/src/main/resources/org/gephi/ui/statistics/plugin/Bundle.properties +++ b/modules/StatisticsPluginUI/src/main/resources/org/gephi/ui/statistics/plugin/Bundle.properties @@ -78,6 +78,8 @@ InOutDegreeUI.name=Average Degree InOutDegreeUI.shortDescription=Average Degree ModularityUI.name=Modularity ModularityUI.shortDescription=Community detection algorithm. +StatisticalInferenceClusteringUI.name=Statistical Inference +StatisticalInferenceClusteringUI.shortDescription=Community detection algorithm. PageRankUI.name=PageRank PageRankUI.shortDescription=Ranks nodes "pages" according to how often a user following links will non-randomly reach the node "page". PathLengthUI.name=Avg. Path Length