/* 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. * * * 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. * * * 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 * γ=1. Note that the returned modularity value is calculated using * the indicated resolution parameter. See \ref igraph_modularity() for more details. * * * The original version of this function was contributed by Tom Gregorovic. * * * Reference: * * * 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; }