Add graph references
This commit is contained in:
@@ -0,0 +1,735 @@
|
||||
/*
|
||||
igraph library.
|
||||
Copyright (C) 2007-2020 The igraph development team
|
||||
|
||||
This program is free software; you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation; either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
||||
02110-1301 USA
|
||||
|
||||
*/
|
||||
|
||||
#include "igraph_community.h"
|
||||
|
||||
#include "igraph_constructors.h"
|
||||
#include "igraph_conversion.h"
|
||||
#include "igraph_interface.h"
|
||||
#include "igraph_memory.h"
|
||||
#include "igraph_qsort.h"
|
||||
#include "igraph_random.h"
|
||||
|
||||
#include "core/interruption.h"
|
||||
|
||||
/* Structure storing a community */
|
||||
typedef struct {
|
||||
igraph_int_t size; /* Size of the community */
|
||||
igraph_real_t weight_inside; /* Sum of edge weights inside community */
|
||||
igraph_real_t weight_all; /* Sum of edge weights starting/ending in the community */
|
||||
} igraph_i_multilevel_community;
|
||||
|
||||
/* Global community list structure */
|
||||
typedef struct {
|
||||
igraph_int_t communities_no, vertices_no; /* Number of communities, number of vertices */
|
||||
igraph_real_t weight_sum; /* Sum of edges weight in the whole graph */
|
||||
igraph_i_multilevel_community *item; /* List of communities */
|
||||
igraph_vector_int_t *membership; /* Community IDs */
|
||||
igraph_vector_t *weights; /* Graph edge weights */
|
||||
} igraph_i_multilevel_community_list;
|
||||
|
||||
/* Computes the modularity of a community partitioning */
|
||||
static igraph_real_t igraph_i_multilevel_community_modularity(
|
||||
const igraph_i_multilevel_community_list *communities,
|
||||
const igraph_real_t resolution) {
|
||||
igraph_real_t result = 0.0;
|
||||
igraph_real_t m = communities->weight_sum;
|
||||
|
||||
for (igraph_int_t i = 0; i < communities->vertices_no; i++) {
|
||||
if (communities->item[i].size > 0) {
|
||||
result += (communities->item[i].weight_inside - resolution * communities->item[i].weight_all * communities->item[i].weight_all / m) / m;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
igraph_int_t from;
|
||||
igraph_int_t to;
|
||||
igraph_int_t id;
|
||||
} igraph_i_multilevel_link;
|
||||
|
||||
static int igraph_i_multilevel_link_cmp(const void *a, const void *b) {
|
||||
igraph_int_t diff;
|
||||
|
||||
diff = ((igraph_i_multilevel_link*)a)->from - ((igraph_i_multilevel_link*)b)->from;
|
||||
|
||||
if (diff < 0) {
|
||||
return -1;
|
||||
} else if (diff > 0) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
diff = ((igraph_i_multilevel_link*)a)->to - ((igraph_i_multilevel_link*)b)->to;
|
||||
|
||||
if (diff < 0) {
|
||||
return -1;
|
||||
} else if (diff > 0) {
|
||||
return 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* removes multiple edges and returns new edge IDs for each edge in |E|log|E| */
|
||||
static igraph_error_t igraph_i_multilevel_simplify_multiple(igraph_t *graph, igraph_vector_int_t *eids) {
|
||||
igraph_int_t ecount = igraph_ecount(graph);
|
||||
igraph_int_t l = -1, last_from = -1, last_to = -1;
|
||||
igraph_bool_t directed = igraph_is_directed(graph);
|
||||
igraph_vector_int_t edges;
|
||||
igraph_i_multilevel_link *links;
|
||||
|
||||
/* Make sure there's enough space in eids to store the new edge IDs */
|
||||
IGRAPH_CHECK(igraph_vector_int_resize(eids, ecount));
|
||||
|
||||
links = IGRAPH_CALLOC(ecount, igraph_i_multilevel_link);
|
||||
IGRAPH_CHECK_OOM(links, "Multi-level community structure detection failed.");
|
||||
IGRAPH_FINALLY(igraph_free, links);
|
||||
|
||||
for (igraph_int_t i = 0; i < ecount; i++) {
|
||||
links[i].from = IGRAPH_FROM(graph, i);
|
||||
links[i].to = IGRAPH_TO(graph, i);
|
||||
links[i].id = i;
|
||||
}
|
||||
|
||||
igraph_qsort(links, (size_t) ecount, sizeof(igraph_i_multilevel_link),
|
||||
igraph_i_multilevel_link_cmp);
|
||||
|
||||
IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0);
|
||||
for (igraph_int_t i = 0; i < ecount; i++) {
|
||||
if (links[i].from == last_from && links[i].to == last_to) {
|
||||
VECTOR(*eids)[links[i].id] = l;
|
||||
continue;
|
||||
}
|
||||
|
||||
last_from = links[i].from;
|
||||
last_to = links[i].to;
|
||||
|
||||
IGRAPH_CHECK(igraph_vector_int_push_back(&edges, last_from));
|
||||
IGRAPH_CHECK(igraph_vector_int_push_back(&edges, last_to));
|
||||
|
||||
l++;
|
||||
|
||||
VECTOR(*eids)[links[i].id] = l;
|
||||
}
|
||||
|
||||
IGRAPH_FREE(links);
|
||||
IGRAPH_FINALLY_CLEAN(1);
|
||||
|
||||
igraph_destroy(graph);
|
||||
IGRAPH_CHECK(igraph_create(graph, &edges, igraph_vcount(graph), directed));
|
||||
|
||||
igraph_vector_int_destroy(&edges);
|
||||
IGRAPH_FINALLY_CLEAN(1);
|
||||
|
||||
return IGRAPH_SUCCESS;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
igraph_int_t community;
|
||||
igraph_real_t weight;
|
||||
} igraph_i_multilevel_community_link;
|
||||
|
||||
static int igraph_i_multilevel_community_link_cmp(const void *a, const void *b) {
|
||||
igraph_int_t diff = (
|
||||
((igraph_i_multilevel_community_link*)a)->community -
|
||||
((igraph_i_multilevel_community_link*)b)->community
|
||||
);
|
||||
return diff < 0 ? -1 : diff > 0 ? 1 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a graph, a community structure and a vertex ID, this method
|
||||
* calculates:
|
||||
*
|
||||
* - edges: the list of edge IDs that are incident on the vertex
|
||||
* - weight_all: the total weight of these edges
|
||||
* - weight_inside: the total weight of edges that stay within the same
|
||||
* community where the given vertex is right now, excluding loop edges
|
||||
* - weight_loop: the total weight of loop edges
|
||||
* - links_community and links_weight: together these two vectors list the
|
||||
* communities incident on this vertex and the total weight of edges
|
||||
* pointing to these communities
|
||||
*/
|
||||
static igraph_error_t igraph_i_multilevel_community_links(
|
||||
const igraph_t *graph,
|
||||
const igraph_i_multilevel_community_list *communities,
|
||||
igraph_int_t vertex, igraph_vector_int_t *edges,
|
||||
igraph_real_t *weight_all, igraph_real_t *weight_inside, igraph_real_t *weight_loop,
|
||||
igraph_vector_int_t *links_community, igraph_vector_t *links_weight) {
|
||||
|
||||
igraph_int_t n, last = -1, c = -1;
|
||||
igraph_real_t weight = 1;
|
||||
igraph_int_t to, to_community;
|
||||
igraph_int_t community = VECTOR(*(communities->membership))[vertex];
|
||||
igraph_i_multilevel_community_link *links;
|
||||
|
||||
*weight_all = *weight_inside = *weight_loop = 0;
|
||||
|
||||
igraph_vector_int_clear(links_community);
|
||||
igraph_vector_clear(links_weight);
|
||||
|
||||
/* Get the list of incident edges */
|
||||
IGRAPH_CHECK(igraph_incident(graph, edges, vertex, IGRAPH_ALL, IGRAPH_LOOPS));
|
||||
|
||||
n = igraph_vector_int_size(edges);
|
||||
links = IGRAPH_CALLOC(n, igraph_i_multilevel_community_link);
|
||||
IGRAPH_CHECK_OOM(links, "Multi-level community structure detection failed.");
|
||||
IGRAPH_FINALLY(igraph_free, links);
|
||||
|
||||
for (igraph_int_t i = 0; i < n; i++) {
|
||||
igraph_int_t eidx = VECTOR(*edges)[i];
|
||||
weight = VECTOR(*communities->weights)[eidx];
|
||||
|
||||
to = IGRAPH_OTHER(graph, eidx, vertex);
|
||||
|
||||
*weight_all += weight;
|
||||
if (to == vertex) {
|
||||
*weight_loop += weight;
|
||||
|
||||
links[i].community = community;
|
||||
links[i].weight = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
to_community = VECTOR(*(communities->membership))[to];
|
||||
if (community == to_community) {
|
||||
*weight_inside += weight;
|
||||
}
|
||||
|
||||
/* debug("Link %ld (C: %ld) <-> %ld (C: %ld)\n", vertex, community, to, to_community); */
|
||||
|
||||
links[i].community = to_community;
|
||||
links[i].weight = weight;
|
||||
}
|
||||
|
||||
/* Sort links by community ID and merge the same */
|
||||
igraph_qsort((void*)links, (size_t) n, sizeof(igraph_i_multilevel_community_link),
|
||||
igraph_i_multilevel_community_link_cmp);
|
||||
for (igraph_int_t i = 0; i < n; i++) {
|
||||
to_community = links[i].community;
|
||||
if (to_community != last) {
|
||||
IGRAPH_CHECK(igraph_vector_int_push_back(links_community, to_community));
|
||||
IGRAPH_CHECK(igraph_vector_push_back(links_weight, links[i].weight));
|
||||
last = to_community;
|
||||
c++;
|
||||
} else {
|
||||
VECTOR(*links_weight)[c] += links[i].weight;
|
||||
}
|
||||
}
|
||||
|
||||
igraph_free(links);
|
||||
IGRAPH_FINALLY_CLEAN(1);
|
||||
|
||||
return IGRAPH_SUCCESS;
|
||||
}
|
||||
|
||||
static igraph_real_t igraph_i_multilevel_community_modularity_gain(
|
||||
const igraph_i_multilevel_community_list *communities,
|
||||
igraph_int_t community, igraph_int_t vertex,
|
||||
igraph_real_t weight_all, igraph_real_t weight_inside,
|
||||
const igraph_real_t resolution) {
|
||||
IGRAPH_UNUSED(vertex);
|
||||
return weight_inside -
|
||||
resolution * communities->item[community].weight_all * weight_all / communities->weight_sum;
|
||||
}
|
||||
|
||||
/* Shrinks communities into single vertices, keeping all the edges.
|
||||
* This method is internal because it destroys the graph in-place and
|
||||
* creates a new one -- this is fine for the multilevel community
|
||||
* detection where a copy of the original graph is used anyway.
|
||||
* The membership vector will also be rewritten by the underlying
|
||||
* igraph_membership_reindex call */
|
||||
static igraph_error_t igraph_i_multilevel_shrink(igraph_t *graph, igraph_vector_int_t *membership) {
|
||||
igraph_vector_int_t edges;
|
||||
igraph_int_t no_of_nodes = igraph_vcount(graph);
|
||||
igraph_int_t no_of_edges = igraph_ecount(graph);
|
||||
igraph_bool_t directed = igraph_is_directed(graph);
|
||||
|
||||
IGRAPH_ASSERT(igraph_vector_int_size(membership) == no_of_nodes);
|
||||
|
||||
if (no_of_nodes == 0) {
|
||||
return IGRAPH_SUCCESS;
|
||||
}
|
||||
|
||||
IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 2*no_of_edges);
|
||||
|
||||
IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, NULL));
|
||||
|
||||
/* Create the new edgelist */
|
||||
IGRAPH_CHECK(igraph_get_edgelist(graph, &edges, /* bycol= */ false));
|
||||
for (igraph_int_t i=0; i < 2*no_of_edges; i++) {
|
||||
VECTOR(edges)[i] = VECTOR(*membership)[ VECTOR(edges)[i] ];
|
||||
}
|
||||
|
||||
/* Create the new graph */
|
||||
igraph_destroy(graph);
|
||||
no_of_nodes = igraph_vector_int_max(membership) + 1;
|
||||
IGRAPH_CHECK(igraph_create(graph, &edges, no_of_nodes, directed));
|
||||
|
||||
igraph_vector_int_destroy(&edges);
|
||||
IGRAPH_FINALLY_CLEAN(1);
|
||||
|
||||
return IGRAPH_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \ingroup communities
|
||||
* \function igraph_i_community_multilevel_step
|
||||
* \brief Performs a single step of the multi-level modularity optimization method.
|
||||
*
|
||||
* This function implements a single step of the multi-level modularity optimization
|
||||
* algorithm for finding community structure, see VD Blondel, J-L Guillaume,
|
||||
* R Lambiotte and E Lefebvre: Fast unfolding of community hierarchies in large
|
||||
* networks, http://arxiv.org/abs/0803.0476 for the details.
|
||||
*
|
||||
* This function was contributed by Tom Gregorovic.
|
||||
*
|
||||
* \param graph The input graph. It must be an undirected graph.
|
||||
* \param weights Numeric vector containing edge weights. If \c NULL,
|
||||
* every edge has equal weight. The weights are expected
|
||||
* to be non-negative.
|
||||
* \param membership The membership vector, the result is returned here.
|
||||
* For each vertex it gives the ID of its community.
|
||||
* \param modularity The modularity of the partition is returned here.
|
||||
* \c NULL means that the modularity is not needed.
|
||||
* \param resolution Resolution parameter. Must be greater than or equal to 0.
|
||||
* Default is 1. Lower values favor fewer, larger communities;
|
||||
* higher values favor more, smaller communities.
|
||||
* \return Error code.
|
||||
*
|
||||
* Time complexity: in average near linear on sparse graphs.
|
||||
*/
|
||||
static igraph_error_t igraph_i_community_multilevel_step(
|
||||
igraph_t *graph,
|
||||
igraph_vector_t *weights,
|
||||
igraph_vector_int_t *membership,
|
||||
igraph_real_t *modularity,
|
||||
const igraph_real_t resolution) {
|
||||
|
||||
igraph_int_t vcount = igraph_vcount(graph);
|
||||
igraph_int_t ecount = igraph_ecount(graph);
|
||||
igraph_real_t q, pass_q;
|
||||
/* int pass; // used only for debugging */
|
||||
igraph_bool_t changed;
|
||||
igraph_vector_int_t links_community;
|
||||
igraph_vector_t links_weight;
|
||||
igraph_vector_int_t edges;
|
||||
igraph_vector_int_t temp_membership;
|
||||
igraph_i_multilevel_community_list communities;
|
||||
igraph_vector_int_t node_order;
|
||||
|
||||
IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, vcount));
|
||||
IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order);
|
||||
igraph_vector_int_shuffle(&node_order);
|
||||
|
||||
/* Initialize data structures */
|
||||
IGRAPH_VECTOR_INT_INIT_FINALLY(&links_community, 0);
|
||||
IGRAPH_VECTOR_INIT_FINALLY(&links_weight, 0);
|
||||
IGRAPH_VECTOR_INT_INIT_FINALLY(&edges, 0);
|
||||
IGRAPH_VECTOR_INT_INIT_FINALLY(&temp_membership, vcount);
|
||||
IGRAPH_CHECK(igraph_vector_int_resize(membership, vcount));
|
||||
|
||||
/* Initialize list of communities from graph vertices */
|
||||
communities.vertices_no = vcount;
|
||||
communities.communities_no = vcount;
|
||||
communities.weights = weights;
|
||||
communities.weight_sum = 2.0 * igraph_vector_sum(weights);
|
||||
communities.membership = membership;
|
||||
communities.item = IGRAPH_CALLOC(vcount, igraph_i_multilevel_community);
|
||||
IGRAPH_CHECK_OOM(communities.item, "Multi-level community structure detection failed.");
|
||||
IGRAPH_FINALLY(igraph_free, communities.item);
|
||||
|
||||
/* Still initializing the communities data structure */
|
||||
for (igraph_int_t i = 0; i < vcount; i++) {
|
||||
VECTOR(*communities.membership)[i] = i;
|
||||
communities.item[i].size = 1;
|
||||
communities.item[i].weight_inside = 0;
|
||||
communities.item[i].weight_all = 0;
|
||||
}
|
||||
|
||||
/* Some more initialization :) */
|
||||
for (igraph_int_t i = 0; i < ecount; i++) {
|
||||
igraph_int_t ffrom = IGRAPH_FROM(graph, i), fto = IGRAPH_TO(graph, i);
|
||||
igraph_real_t weight = 1;
|
||||
|
||||
weight = VECTOR(*weights)[i];
|
||||
communities.item[ffrom].weight_all += weight;
|
||||
communities.item[fto].weight_all += weight;
|
||||
if (ffrom == fto) {
|
||||
communities.item[ffrom].weight_inside += 2 * weight;
|
||||
}
|
||||
}
|
||||
|
||||
q = igraph_i_multilevel_community_modularity(&communities, resolution);
|
||||
/* pass = 1; */
|
||||
|
||||
do { /* Pass begin */
|
||||
igraph_int_t temp_communities_no = communities.communities_no;
|
||||
|
||||
pass_q = q;
|
||||
changed = false;
|
||||
|
||||
/* Save the current membership, it will be restored in case of worse result */
|
||||
IGRAPH_CHECK(igraph_vector_int_update(&temp_membership, communities.membership));
|
||||
|
||||
/* Apply a random inversion to the node_order permutation vector to help escape
|
||||
* rare situations of an infinite loop. A full re-shuffling of node_order would
|
||||
* have a measurable performance impact, hence the single inversion.
|
||||
* See https://github.com/igraph/igraph/issues/2650 for details. */
|
||||
if (vcount > 1) {
|
||||
igraph_int_t i1 = RNG_INTEGER(0, vcount-1);
|
||||
igraph_int_t i2 = RNG_INTEGER(0, vcount-1);
|
||||
igraph_int_t tmp = VECTOR(node_order)[i1];
|
||||
VECTOR(node_order)[i1] = VECTOR(node_order)[i2];
|
||||
VECTOR(node_order)[i2] = tmp;
|
||||
}
|
||||
|
||||
for (igraph_int_t i = 0; i < vcount; i++) {
|
||||
/* Exclude vertex from its current community */
|
||||
igraph_real_t weight_all = 0;
|
||||
igraph_real_t weight_inside = 0;
|
||||
igraph_real_t weight_loop = 0;
|
||||
igraph_real_t max_q_gain = 0;
|
||||
igraph_real_t max_weight;
|
||||
igraph_int_t old_id, new_id, n, ni;
|
||||
|
||||
ni = VECTOR(node_order)[i];
|
||||
|
||||
igraph_i_multilevel_community_links(graph, &communities,
|
||||
ni, &edges,
|
||||
&weight_all, &weight_inside,
|
||||
&weight_loop, &links_community,
|
||||
&links_weight);
|
||||
|
||||
old_id = VECTOR(*(communities.membership))[ni];
|
||||
new_id = old_id;
|
||||
|
||||
/* Update old community */
|
||||
VECTOR(*communities.membership)[ni] = -1;
|
||||
communities.item[old_id].size--;
|
||||
if (communities.item[old_id].size == 0) {
|
||||
communities.communities_no--;
|
||||
}
|
||||
communities.item[old_id].weight_all -= weight_all;
|
||||
communities.item[old_id].weight_inside -= 2 * weight_inside + weight_loop;
|
||||
|
||||
/* debug("Remove %ld all: %lf Inside: %lf\n", ni, -weight_all, -2*weight_inside + weight_loop); */
|
||||
|
||||
/* Find new community to join with the best modification gain */
|
||||
max_q_gain = 0;
|
||||
max_weight = weight_inside;
|
||||
n = igraph_vector_int_size(&links_community);
|
||||
|
||||
for (igraph_int_t j = 0; j < n; j++) {
|
||||
igraph_int_t c = VECTOR(links_community)[j];
|
||||
igraph_real_t w = VECTOR(links_weight)[j];
|
||||
|
||||
igraph_real_t q_gain =
|
||||
igraph_i_multilevel_community_modularity_gain(&communities, c, ni,
|
||||
weight_all, w, resolution);
|
||||
/* debug("Link %ld -> %ld weight: %lf gain: %lf\n", ni, c, (double) w, (double) q_gain); */
|
||||
if (q_gain > max_q_gain) {
|
||||
new_id = c;
|
||||
max_q_gain = q_gain;
|
||||
max_weight = w;
|
||||
}
|
||||
}
|
||||
|
||||
/* debug("Added vertex %ld to community %ld (gain %lf).\n", ni, new_id, (double) max_q_gain); */
|
||||
|
||||
/* Add vertex to "new" community and update it */
|
||||
VECTOR(*communities.membership)[ni] = new_id;
|
||||
if (communities.item[new_id].size == 0) {
|
||||
communities.communities_no++;
|
||||
}
|
||||
communities.item[new_id].size++;
|
||||
communities.item[new_id].weight_all += weight_all;
|
||||
communities.item[new_id].weight_inside += 2 * max_weight + weight_loop;
|
||||
|
||||
if (new_id != old_id) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
|
||||
q = igraph_i_multilevel_community_modularity(&communities, resolution);
|
||||
|
||||
if (changed && (q > pass_q)) {
|
||||
/* debug("Pass %d (changed: %d) Communities: %ld Modularity from %lf to %lf\n",
|
||||
pass, changed, communities.communities_no, (double) pass_q, (double) q); */
|
||||
/* pass++; */
|
||||
} else {
|
||||
/* No changes or the modularity became worse, restore last membership */
|
||||
IGRAPH_CHECK(igraph_vector_int_update(communities.membership, &temp_membership));
|
||||
communities.communities_no = temp_communities_no;
|
||||
break;
|
||||
}
|
||||
|
||||
IGRAPH_ALLOW_INTERRUPTION();
|
||||
} while (changed && (q > pass_q)); /* Pass end */
|
||||
|
||||
if (modularity) {
|
||||
*modularity = q;
|
||||
}
|
||||
|
||||
/* debug("Result Communities: %ld Modularity: %lf\n",
|
||||
communities.communities_no, (double) q); */
|
||||
|
||||
IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, NULL));
|
||||
|
||||
/* Shrink the nodes of the graph according to the present community structure
|
||||
* and simplify the resulting graph */
|
||||
|
||||
/* TODO: check if we really need to copy temp_membership */
|
||||
IGRAPH_CHECK(igraph_vector_int_update(&temp_membership, membership));
|
||||
IGRAPH_CHECK(igraph_i_multilevel_shrink(graph, &temp_membership));
|
||||
igraph_vector_int_destroy(&temp_membership);
|
||||
IGRAPH_FINALLY_CLEAN(1);
|
||||
|
||||
/* Update edge weights after shrinking and simplification */
|
||||
/* Here we reuse the edges vector as we don't need the previous contents anymore */
|
||||
/* TODO: can we use igraph_simplify here? */
|
||||
IGRAPH_CHECK(igraph_i_multilevel_simplify_multiple(graph, &edges));
|
||||
|
||||
/* We reuse the links_weight vector to store the old edge weights */
|
||||
IGRAPH_CHECK(igraph_vector_update(&links_weight, weights));
|
||||
igraph_vector_null(weights);
|
||||
|
||||
for (igraph_int_t i = 0; i < ecount; i++) {
|
||||
VECTOR(*weights)[VECTOR(edges)[i]] += VECTOR(links_weight)[i];
|
||||
}
|
||||
|
||||
igraph_free(communities.item);
|
||||
igraph_vector_int_destroy(&links_community);
|
||||
igraph_vector_destroy(&links_weight);
|
||||
igraph_vector_int_destroy(&edges);
|
||||
igraph_vector_int_destroy(&node_order);
|
||||
IGRAPH_FINALLY_CLEAN(5);
|
||||
|
||||
return IGRAPH_SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* \ingroup communities
|
||||
* \function igraph_community_multilevel
|
||||
* \brief Finding community structure by multi-level optimization of modularity (Louvain).
|
||||
*
|
||||
* This function implements a multi-level modularity optimization algorithm
|
||||
* for finding community structure, sometimes known as the Louvain algorithm.
|
||||
*
|
||||
* </para><para>
|
||||
* The algorithm is based on the modularity measure and a hierarchical approach.
|
||||
* Initially, each vertex is assigned to a community on its own. In every step,
|
||||
* vertices are re-assigned to communities in a local, greedy way: in a random
|
||||
* order, each vertex is moved to the community with which it achieves the highest
|
||||
* contribution to modularity. When no vertices can be reassigned, each community
|
||||
* is considered a vertex on its own, and the process starts again with the merged
|
||||
* communities. The process stops when there is only a single vertex left or when
|
||||
* the modularity cannot be increased any more in a step.
|
||||
*
|
||||
* </para><para>
|
||||
* The resolution parameter \c γ allows finding communities at different
|
||||
* resolutions. Higher values of the resolution parameter typically result in
|
||||
* more, smaller communities. Lower values typically result in fewer, larger
|
||||
* communities. The original definition of modularity is retrieved when setting
|
||||
* <code>γ=1</code>. Note that the returned modularity value is calculated using
|
||||
* the indicated resolution parameter. See \ref igraph_modularity() for more details.
|
||||
*
|
||||
* </para><para>
|
||||
* The original version of this function was contributed by Tom Gregorovic.
|
||||
*
|
||||
* </para><para>
|
||||
* Reference:
|
||||
*
|
||||
* </para><para>
|
||||
* Blondel, V. D., Guillaume, J.-L., Lambiotte, R., & Lefebvre, E.:
|
||||
* Fast unfolding of communities in large networks.
|
||||
* Journal of Statistical Mechanics: Theory and Experiment, 10008(10), 6 (2008).
|
||||
* https://doi.org/10.1088/1742-5468/2008/10/P10008
|
||||
*
|
||||
* \param graph The input graph. It must be an undirected graph.
|
||||
* \param weights Numeric vector containing edge weights. If \c NULL, every edge
|
||||
* has equal weight. The weights are expected to be non-negative.
|
||||
* \param resolution Resolution parameter. Must be greater than or equal to 0.
|
||||
* Lower values favor fewer, larger communities;
|
||||
* higher values favor more, smaller communities.
|
||||
* Set it to 1 to use the classical definition of modularity.
|
||||
* \param membership The membership vector, the result is returned here.
|
||||
* For each vertex it gives the ID of its community. The vector
|
||||
* must be initialized and it will be resized accordingly.
|
||||
* \param memberships Numeric matrix that will contain the membership vector after
|
||||
* each level, if not \c NULL. It must be initialized and
|
||||
* it will be resized accordingly.
|
||||
* \param modularity Numeric vector that will contain the modularity score
|
||||
* after each level, if not \c NULL. It must be initialized
|
||||
* and it will be resized accordingly.
|
||||
* \return Error code.
|
||||
*
|
||||
* Time complexity: in average near linear on sparse graphs.
|
||||
*
|
||||
* \example examples/simple/igraph_community_multilevel.c
|
||||
*/
|
||||
|
||||
igraph_error_t igraph_community_multilevel(const igraph_t *graph,
|
||||
const igraph_vector_t *weights,
|
||||
const igraph_real_t resolution,
|
||||
igraph_vector_int_t *membership,
|
||||
igraph_matrix_int_t *memberships,
|
||||
igraph_vector_t *modularity) {
|
||||
|
||||
igraph_t g;
|
||||
igraph_vector_t w;
|
||||
igraph_vector_int_t m;
|
||||
igraph_vector_int_t level_membership;
|
||||
igraph_real_t prev_q = -1, q = -1;
|
||||
igraph_int_t level = 1;
|
||||
igraph_int_t vcount = igraph_vcount(graph);
|
||||
igraph_int_t ecount = igraph_ecount(graph);
|
||||
|
||||
/* Initial sanity checks on the input parameters */
|
||||
if (igraph_is_directed(graph)) {
|
||||
IGRAPH_ERROR("Multi-level community detection works for undirected graphs only.",
|
||||
IGRAPH_UNIMPLEMENTED);
|
||||
}
|
||||
if (weights) {
|
||||
if (igraph_vector_size(weights) != ecount) {
|
||||
IGRAPH_ERROR("Weight vector length must agree with number of edges.", IGRAPH_EINVAL);
|
||||
}
|
||||
if (ecount > 0) {
|
||||
igraph_real_t minweight = igraph_vector_min(weights);
|
||||
if (minweight < 0) {
|
||||
IGRAPH_ERROR("Weight vector must not be negative.", IGRAPH_EINVAL);
|
||||
} else if (isnan(minweight)) {
|
||||
IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (resolution < 0.0) {
|
||||
IGRAPH_ERROR("The resolution parameter must be non-negative.", IGRAPH_EINVAL);
|
||||
}
|
||||
|
||||
/* Make a copy of the original graph, we will do the merges on the copy */
|
||||
IGRAPH_CHECK(igraph_copy(&g, graph));
|
||||
IGRAPH_FINALLY(igraph_destroy, &g);
|
||||
|
||||
if (weights) {
|
||||
IGRAPH_CHECK(igraph_vector_init_copy(&w, weights));
|
||||
IGRAPH_FINALLY(igraph_vector_destroy, &w);
|
||||
} else {
|
||||
IGRAPH_VECTOR_INIT_FINALLY(&w, igraph_ecount(&g));
|
||||
igraph_vector_fill(&w, 1);
|
||||
}
|
||||
|
||||
IGRAPH_VECTOR_INT_INIT_FINALLY(&m, vcount);
|
||||
IGRAPH_VECTOR_INT_INIT_FINALLY(&level_membership, vcount);
|
||||
|
||||
if (memberships || membership) {
|
||||
/* Put each vertex in its own community */
|
||||
for (igraph_int_t i = 0; i < vcount; i++) {
|
||||
VECTOR(level_membership)[i] = i;
|
||||
}
|
||||
}
|
||||
if (memberships) {
|
||||
/* Resize the membership matrix to have vcount columns and no rows */
|
||||
IGRAPH_CHECK(igraph_matrix_int_resize(memberships, 0, vcount));
|
||||
}
|
||||
if (modularity) {
|
||||
/* Clear the modularity vector */
|
||||
igraph_vector_clear(modularity);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
/* Remember the previous modularity and vertex count, do a single step */
|
||||
igraph_int_t step_vcount = igraph_vcount(&g);
|
||||
|
||||
prev_q = q;
|
||||
IGRAPH_CHECK(igraph_i_community_multilevel_step(&g, &w, &m, &q, resolution));
|
||||
|
||||
/* Were there any merges? If not, we have to stop the process */
|
||||
if (igraph_vcount(&g) == step_vcount || q < prev_q) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (memberships || membership) {
|
||||
for (igraph_int_t i = 0; i < vcount; i++) {
|
||||
/* Readjust the membership vector */
|
||||
VECTOR(level_membership)[i] = VECTOR(m)[ VECTOR(level_membership)[i] ];
|
||||
}
|
||||
}
|
||||
|
||||
if (modularity) {
|
||||
/* If we have to return the modularity scores, add it to the modularity vector */
|
||||
IGRAPH_CHECK(igraph_vector_push_back(modularity, q));
|
||||
}
|
||||
|
||||
if (memberships) {
|
||||
/* If we have to return the membership vectors at each level, store the new
|
||||
* membership vector */
|
||||
IGRAPH_CHECK(igraph_matrix_int_add_rows(memberships, 1));
|
||||
IGRAPH_CHECK(igraph_matrix_int_set_row(memberships, &level_membership, level - 1));
|
||||
}
|
||||
|
||||
/* debug("Level: %d Communities: %ld Modularity: %f\n", level, igraph_vcount(&g),
|
||||
(double) q); */
|
||||
|
||||
/* Increase the level counter */
|
||||
level++;
|
||||
}
|
||||
|
||||
/* It might happen that there are no merges, so every vertex is in its
|
||||
own community. We still might want the modularity score for that. */
|
||||
if (modularity && igraph_vector_size(modularity) == 0) {
|
||||
igraph_vector_int_t tmp;
|
||||
igraph_real_t mod;
|
||||
|
||||
IGRAPH_CHECK(igraph_vector_int_init_range(&tmp, 0, vcount));
|
||||
IGRAPH_FINALLY(igraph_vector_int_destroy, &tmp);
|
||||
|
||||
IGRAPH_CHECK(igraph_modularity(graph, &tmp, weights, resolution,
|
||||
/* only undirected */ false, &mod));
|
||||
|
||||
igraph_vector_int_destroy(&tmp);
|
||||
IGRAPH_FINALLY_CLEAN(1);
|
||||
|
||||
IGRAPH_CHECK(igraph_vector_resize(modularity, 1));
|
||||
VECTOR(*modularity)[0] = mod;
|
||||
}
|
||||
|
||||
/* If we need the final membership vector, copy it to the output */
|
||||
if (membership) {
|
||||
IGRAPH_CHECK(igraph_vector_int_resize(membership, vcount));
|
||||
for (igraph_int_t i = 0; i < vcount; i++) {
|
||||
VECTOR(*membership)[i] = VECTOR(level_membership)[i];
|
||||
}
|
||||
}
|
||||
|
||||
/* Destroy the copy of the graph */
|
||||
igraph_destroy(&g);
|
||||
|
||||
/* Destroy the temporary vectors */
|
||||
igraph_vector_int_destroy(&m);
|
||||
igraph_vector_destroy(&w);
|
||||
igraph_vector_int_destroy(&level_membership);
|
||||
IGRAPH_FINALLY_CLEAN(4);
|
||||
|
||||
return IGRAPH_SUCCESS;
|
||||
}
|
||||
Reference in New Issue
Block a user