Add graph references

This commit is contained in:
Abdelrahman Said
2026-06-28 13:49:01 +01:00
parent 0a9807e448
commit a11edf0c53
2578 changed files with 868045 additions and 0 deletions
@@ -0,0 +1,34 @@
/*
igraph library.
Copyright (C) 2025 The igraph development team <igraph@igraph.org>
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, see <https://www.gnu.org/licenses/>.
*/
#ifndef IGRAPH_COMMUNITY_INTERNAL_H
#define IGRAPH_COMMUNITY_INTERNAL_H
#include "igraph_decls.h"
#include "igraph_vector.h"
IGRAPH_BEGIN_C_DECLS
igraph_error_t igraph_i_reindex_membership_large(
igraph_vector_int_t *membership,
igraph_vector_int_t *new_to_old,
igraph_int_t *nb_clusters);
IGRAPH_END_C_DECLS
#endif /* IGRAPH_COMMUNITY_INTERNAL_H */
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,809 @@
/*
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_adjlist.h"
#include "igraph_bitset.h"
#include "igraph_components.h"
#include "igraph_dqueue.h"
#include "igraph_interface.h"
#include "igraph_memory.h"
#include "igraph_nongraph.h"
#include "igraph_progress.h"
#include "igraph_stack.h"
#include "core/indheap.h"
#include "core/interruption.h"
#include <string.h>
static igraph_error_t igraph_i_rewrite_membership_vector(igraph_vector_int_t *membership) {
const igraph_int_t no = igraph_vector_int_max(membership) + 1;
igraph_vector_int_t idx;
igraph_int_t realno = 0;
const igraph_int_t len = igraph_vector_int_size(membership);
IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, no);
for (igraph_int_t i = 0; i < len; i++) {
const igraph_int_t t = VECTOR(*membership)[i];
if (VECTOR(idx)[t]) {
VECTOR(*membership)[i] = VECTOR(idx)[t] - 1;
} else {
VECTOR(idx)[t] = ++realno;
VECTOR(*membership)[i] = VECTOR(idx)[t] - 1;
}
}
igraph_vector_int_destroy(&idx);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
static igraph_error_t igraph_i_community_eb_get_merges2(const igraph_t *graph,
const igraph_bool_t directed,
const igraph_vector_int_t *edges,
const igraph_vector_t *weights,
igraph_matrix_int_t *res,
igraph_vector_int_t *bridges,
igraph_vector_t *modularity,
igraph_vector_int_t *membership) {
igraph_vector_int_t mymembership;
const igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_real_t maxmod = -1;
igraph_int_t midx = 0;
igraph_int_t no_comps;
const igraph_bool_t use_directed = directed && igraph_is_directed(graph);
igraph_int_t max_merges;
if (membership) {
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
}
if (modularity || res || bridges) {
IGRAPH_CHECK(igraph_connected_components(graph, NULL, NULL, &no_comps, IGRAPH_WEAK));
max_merges = no_of_nodes - no_comps;
if (modularity) {
IGRAPH_CHECK(igraph_vector_resize(modularity,
max_merges + 1));
}
if (res) {
IGRAPH_CHECK(igraph_matrix_int_resize(res, max_merges,
2));
}
if (bridges) {
IGRAPH_CHECK(igraph_vector_int_resize(bridges, max_merges));
}
}
IGRAPH_CHECK(igraph_vector_int_init_range(&mymembership, 0, no_of_nodes));
IGRAPH_FINALLY(igraph_vector_int_destroy, &mymembership);
if (membership) {
IGRAPH_CHECK(igraph_vector_int_update(membership, &mymembership));
}
IGRAPH_CHECK(igraph_modularity(graph, &mymembership, weights,
/* resolution */ 1,
use_directed, &maxmod));
if (modularity) {
VECTOR(*modularity)[0] = maxmod;
}
for (igraph_int_t i = igraph_vector_int_size(edges) - 1; i >= 0; i--) {
igraph_int_t edge = VECTOR(*edges)[i];
igraph_int_t from = IGRAPH_FROM(graph, edge);
igraph_int_t to = IGRAPH_TO(graph, edge);
igraph_int_t c1 = VECTOR(mymembership)[from];
igraph_int_t c2 = VECTOR(mymembership)[to];
igraph_real_t actmod;
if (c1 != c2) { /* this is a merge */
if (res) {
MATRIX(*res, midx, 0) = c1;
MATRIX(*res, midx, 1) = c2;
}
if (bridges) {
VECTOR(*bridges)[midx] = i;
}
/* The new cluster has id no_of_nodes+midx+1 */
for (igraph_int_t j = 0; j < no_of_nodes; j++) {
if (VECTOR(mymembership)[j] == c1 ||
VECTOR(mymembership)[j] == c2) {
VECTOR(mymembership)[j] = no_of_nodes + midx;
}
}
IGRAPH_CHECK(igraph_modularity(graph, &mymembership, weights,
/* resolution */ 1,
use_directed, &actmod));
if (modularity) {
VECTOR(*modularity)[midx + 1] = actmod;
if (actmod > maxmod) {
maxmod = actmod;
if (membership) {
IGRAPH_CHECK(igraph_vector_int_update(membership, &mymembership));
}
}
}
midx++;
}
}
if (membership) {
IGRAPH_CHECK(igraph_i_rewrite_membership_vector(membership));
}
igraph_vector_int_destroy(&mymembership);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
/**
* \function igraph_community_eb_get_merges
* \brief Calculating the merges, i.e. the dendrogram for an edge betweenness community structure.
*
* This function is handy if you have a sequence of edges which are
* gradually removed from the network and you would like to know how
* the network falls apart into separate components. The edge sequence
* may come from the \ref igraph_community_edge_betweenness()
* function, but this is not necessary. Note that \ref
* igraph_community_edge_betweenness() can also calculate the
* dendrogram, via its \p merges argument. Merges happen when the
* edge removal process is run backwards and two components become
* connected.
*
* \param graph The input graph.
* \param directed Whether to use the directed or undirected version
* of modularity. Will be ignored for undirected graphs.
* \param edges Vector containing the edges to be removed from the
* network, all edges are expected to appear exactly once in the
* vector.
* \param weights An optional vector containing edge weights. If null,
* the unweighted modularity scores will be calculated. If not null,
* the weighted modularity scores will be calculated. Ignored if both
* \p modularity and \p membership are \c NULL pointers.
* \param res Pointer to an initialized matrix, if not \c NULL then the
* dendrogram will be stored here, in the same form as for the
* \ref igraph_community_walktrap() function: the matrix has two columns
* and each line is a merge given by the IDs of the merged
* components. The component IDs are numbered from zero and
* component IDs smaller than the number of vertices in the graph
* belong to individual vertices. The non-trivial components
* containing at least two vertices are numbered from \c n, where \c n is
* the number of vertices in the graph. So if the first line
* contains \c a and \c b that means that components \c a and \c b
* are merged into component \c n, the second line creates
* component <code>n + 1</code>, etc. The matrix will be resized as needed.
* \param bridges Pointer to an initialized vector of \c NULL. If not
* \c NULL then the indices into \p edges of all edges which caused
* one of the merges will be put here. This is equal to all edge removals
* which separated the network into more components, in reverse order.
* \param modularity If not a null pointer, then the modularity values
* for the different divisions, corresponding to the merges matrix,
* will be stored here.
* \param membership If not a null pointer, then the membership vector
* for the best division (in terms of modularity) will be stored
* here.
* \return Error code.
*
* \sa \ref igraph_community_edge_betweenness().
*
* Time complexity: O(|E|+|V|log|V|), |V| is the number of vertices,
* |E| is the number of edges.
*/
igraph_error_t igraph_community_eb_get_merges(const igraph_t *graph,
const igraph_bool_t directed,
const igraph_vector_int_t *edges,
const igraph_vector_t *weights,
igraph_matrix_int_t *res,
igraph_vector_int_t *bridges,
igraph_vector_t *modularity,
igraph_vector_int_t *membership) {
const igraph_int_t no_of_nodes = igraph_vcount(graph);
const igraph_int_t no_of_edges = igraph_ecount(graph);
igraph_vector_int_t ptr;
igraph_int_t midx = 0;
igraph_int_t no_comps;
const igraph_int_t no_removed_edges = igraph_vector_int_size(edges);
igraph_int_t max_merges;
if (! igraph_vector_int_isininterval(edges, 0, no_of_edges-1)) {
IGRAPH_ERROR(
"Cannot calculate merges of edge betweenness community detection.",
IGRAPH_EINVEID
);
}
if (no_removed_edges < no_of_edges) {
IGRAPH_ERRORF("Number of removed edges (%" IGRAPH_PRId ") should be equal to "
"number of edges in graph (%" IGRAPH_PRId ").", IGRAPH_EINVAL,
no_removed_edges, no_of_edges);
}
/* catch null graph early */
if (no_of_nodes == 0) {
if (res) {
IGRAPH_CHECK(igraph_matrix_int_resize(res, 0, 2));
}
if (bridges) {
igraph_vector_int_clear(bridges);
}
if (modularity) {
IGRAPH_CHECK(igraph_vector_resize(modularity, 1));
VECTOR(*modularity)[0] = IGRAPH_NAN;
}
if (membership) {
igraph_vector_int_clear(membership);
}
return IGRAPH_SUCCESS;
}
if (membership || modularity) {
return igraph_i_community_eb_get_merges2(graph,
directed && igraph_is_directed(graph),
edges, weights,
res, bridges, modularity, membership);
}
IGRAPH_CHECK(igraph_connected_components(graph, NULL, NULL, &no_comps, IGRAPH_WEAK));
max_merges = no_of_nodes - no_comps;
IGRAPH_VECTOR_INT_INIT_FINALLY(&ptr, no_of_nodes * 2 - 1);
if (res) {
IGRAPH_CHECK(igraph_matrix_int_resize(res, max_merges, 2));
}
if (bridges) {
IGRAPH_CHECK(igraph_vector_int_resize(bridges, max_merges));
}
for (igraph_int_t i = igraph_vector_int_size(edges) - 1; i >= 0; i--) {
igraph_int_t edge = VECTOR(*edges)[i];
igraph_int_t from, to, c1, c2, idx;
IGRAPH_CHECK(igraph_edge(graph, edge, &from, &to));
idx = from + 1;
while (VECTOR(ptr)[idx - 1] != 0) {
idx = VECTOR(ptr)[idx - 1];
}
c1 = idx - 1;
idx = to + 1;
while (VECTOR(ptr)[idx - 1] != 0) {
idx = VECTOR(ptr)[idx - 1];
}
c2 = idx - 1;
if (c1 != c2) { /* this is a merge */
if (res) {
MATRIX(*res, midx, 0) = c1;
MATRIX(*res, midx, 1) = c2;
}
if (bridges) {
VECTOR(*bridges)[midx] = i;
}
VECTOR(ptr)[c1] = no_of_nodes + midx + 1;
VECTOR(ptr)[c2] = no_of_nodes + midx + 1;
VECTOR(ptr)[from] = no_of_nodes + midx + 1;
VECTOR(ptr)[to] = no_of_nodes + midx + 1;
midx++;
}
}
igraph_vector_int_destroy(&ptr);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
/* Find the index i for which v[i] / w[i] is the largest
* and the corresponding element is active (i.e. !passive[i]).
* If w is a null pointer then all w[i] are assumed to be 1,
* and the ranking is done solely based on v.
*
* This function requires that at least one element is active. */
static igraph_int_t igraph_i_which_max_active_ratio(
const igraph_vector_t *v,
const igraph_vector_t *w,
igraph_bitset_t *passive) {
const igraph_int_t size = igraph_vector_size(v);
igraph_int_t which = igraph_bitset_countr_one(passive); /* start with first active element */
igraph_real_t max = VECTOR(*v)[which] / (w ? VECTOR(*w)[which] : 1.0);
for (igraph_int_t i = which+1; i < size; i++) {
igraph_real_t elem = VECTOR(*v)[i] / (w ? VECTOR(*w)[i] : 1.0);
if (! IGRAPH_BIT_TEST(*passive, i) && elem > max) {
max = elem;
which = i;
}
}
return which;
}
/**
* \function igraph_community_edge_betweenness
* \brief Community finding based on edge betweenness.
*
* Community structure detection based on the betweenness of the edges
* in the network. This method is also known as the Girvan-Newman
* algorithm.
*
* </para><para>
* The idea behind this method is that the betweenness of the edges connecting
* two communities is typically high, as many of the shortest paths between
* vertices in separate communities pass through them. The algorithm
* successively removes edges with the highest betweenness, recalculating
* betweenness values after each removal. This way eventually the network splits
* into two components, then one of these components splits again, and so on,
* until all edges are removed. The resulting hierarhical partitioning of the
* vertices can be encoded as a dendrogram.
*
* </para><para>
* In directed graphs, when \p directed is set to true, the directed version
* of betweenness and modularity are used, however, only splits into
* \em weakly connected components are detected.
*
* </para><para>
* When edge weights are given, the ratio of betweenness and weight values
* is used to choose which edges to remove first, as described in
* M. E. J. Newman: Analysis of Weighted Networks (2004), Section C.
* Thus, edges with large weights are treated as strong connections,
* and will be removed later than weak connections having similar betweenness.
* Weights are also used for calculating modularity.
*
* </para><para>
* If lengths are given, they will be considered for shortest path length
* calculations while computing betweenness values.
*
* </para><para>
* Note: In igraph 0.10, this function interpreted weights in a different,
* erroneous way, and issued a warning when weights were used. Please
* see https://github.com/igraph/igraph/issues/2229 for additional details.
*
* </para><para>
* References:
*
* </para><para>
* M. Girvan and M. E. J. Newman,
* Community Structure in Social and Biological Networks, PNAS 99, 7821 (2002).
* https://doi.org/10.1073/pnas.122653799
*
* </para><para>
* M. E. J. Newman,
* Analysis of Weighted Networks, Phys. Rev. E 70, 9 (2004).
* https://doi.org/10.1103/PhysRevE.70.056131
*
* \param graph The input graph.
* \param removed_edges Pointer to an initialized integer vector, which will
* be resized as needed. The IDs of the removed edges in the order of their
* removal will be stored here. This vector is suitable as input to
* \ref igraph_community_eb_get_merges(). This parameter may be \c NULL if
* the edge IDs are not needed by the caller.
* \param edge_betweenness Pointer to an initialized vector or
* \c NULL. In the former case the edge betweenness of the removed
* edges is stored here. The vector will be resized as needed.
* Note that the betweenness values stored here are \em not divided
* by weights.
* \param merges Pointer to an initialized matrix or \c NULL. If not \c NULL
* then merges performed by the algorithm are stored here. Even if
* this is a divisive algorithm, we can replay it backwards and
* note which two clusters were merged. Clusters are numbered from
* zero. See \ref igraph_community_to_membership() for details. The
* matrix will be resized as needed.
* \param bridges Pointer to an initialized vector of \c NULL. If not
* \c NULL then the indices into \p result of all edges which caused
* one of the \p merges will be put here. This is equivalent to all edge removals
* which separated the network into more components, in reverse order.
* \param modularity If not a null pointer, then the modularity values
* of the different divisions are stored here, in the order
* corresponding to the merge matrix. The modularity values will
* take weights into account if \p weights is not null.
* \param membership If not a null pointer, then the membership vector,
* corresponding to the highest modularity value, is stored here.
* \param directed Boolean constant. Controls whether to calculate directed
* betweenness (i.e. directed paths) for directed graphs, and whether
* to use the directed version of modularity. It is ignored for undirected
* graphs.
* \param weights An optional vector containing edge weights. If not \c NULL,
* the weights will be used to divide the edge betweenness scores,
* as well as for the calculation of modularity.
* \param lengths An optional vector containing edge lengths. If not \c NULL,
* path lengths used in the betweenness calculation will take these
* lengths into account.
* \return Error code.
*
* \sa \ref igraph_community_eb_get_merges(), \ref
* igraph_community_spinglass(), \ref igraph_community_walktrap().
*
* Time complexity: O(|V||E|^2), as the betweenness calculation requires
* O(|V||E|) and we do it |E|-1 times.
*
* \example examples/simple/igraph_community_edge_betweenness.c
*/
igraph_error_t igraph_community_edge_betweenness(const igraph_t *graph,
igraph_vector_int_t *removed_edges,
igraph_vector_t *edge_betweenness,
igraph_matrix_int_t *merges,
igraph_vector_int_t *bridges,
igraph_vector_t *modularity,
igraph_vector_int_t *membership,
igraph_bool_t directed,
const igraph_vector_t *weights,
const igraph_vector_t *lengths) {
const igraph_int_t no_of_nodes = igraph_vcount(graph);
const igraph_int_t no_of_edges = igraph_ecount(graph);
double *distance, *tmpscore;
double *nrgeo;
igraph_inclist_t elist_out, elist_in, parents;
igraph_inclist_t *elist_out_p, *elist_in_p;
igraph_vector_int_t *neip;
igraph_int_t neino;
igraph_vector_t eb;
igraph_int_t maxedge, pos;
igraph_int_t from, to;
igraph_bool_t result_owned = false;
igraph_stack_int_t stack;
igraph_real_t steps, steps_done;
igraph_bitset_t passive;
/* Needed only for the unweighted case */
igraph_dqueue_int_t q;
/* Needed only for the weighted case */
igraph_2wheap_t heap;
if (removed_edges == NULL) {
removed_edges = IGRAPH_CALLOC(1, igraph_vector_int_t);
IGRAPH_CHECK_OOM(removed_edges, "Insufficient memory for edge betweenness-based community detection.");
IGRAPH_FINALLY(igraph_free, removed_edges);
IGRAPH_VECTOR_INT_INIT_FINALLY(removed_edges, 0);
result_owned = true;
}
directed = directed && igraph_is_directed(graph);
if (directed) {
IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_OUT, IGRAPH_LOOPS_ONCE));
IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out);
IGRAPH_CHECK(igraph_inclist_init(graph, &elist_in, IGRAPH_IN, IGRAPH_LOOPS_ONCE));
IGRAPH_FINALLY(igraph_inclist_destroy, &elist_in);
elist_out_p = &elist_out;
elist_in_p = &elist_in;
} else {
IGRAPH_CHECK(igraph_inclist_init(graph, &elist_out, IGRAPH_ALL, IGRAPH_LOOPS_TWICE));
IGRAPH_FINALLY(igraph_inclist_destroy, &elist_out);
elist_out_p = elist_in_p = &elist_out;
}
distance = IGRAPH_CALLOC(no_of_nodes, double);
IGRAPH_CHECK_OOM(distance, "Insufficient memory for edge betweenness-based community detection.");
IGRAPH_FINALLY(igraph_free, distance);
nrgeo = IGRAPH_CALLOC(no_of_nodes, double);
IGRAPH_CHECK_OOM(nrgeo, "Insufficient memory for edge betweenness-based community detection.");
IGRAPH_FINALLY(igraph_free, nrgeo);
tmpscore = IGRAPH_CALLOC(no_of_nodes, double);
IGRAPH_CHECK_OOM(tmpscore, "Insufficient memory for edge betweenness-based community detection.");
IGRAPH_FINALLY(igraph_free, tmpscore);
if (weights) {
if (igraph_vector_size(weights) != no_of_edges) {
IGRAPH_ERROR("Weight vector length must agree with number of edges.", IGRAPH_EINVAL);
}
if (no_of_edges > 0) {
/* Must not call vector_min on empty vector */
igraph_real_t minweight = igraph_vector_min(weights);
if (minweight <= 0) {
IGRAPH_ERROR("Weights must be strictly positive.", IGRAPH_EINVAL);
}
if (isnan(minweight)) {
IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL);
}
}
}
if (lengths == NULL) {
IGRAPH_DQUEUE_INT_INIT_FINALLY(&q, 100);
} else {
if (igraph_vector_size(lengths) != no_of_edges) {
IGRAPH_ERROR("Edge length vector size must agree with number of edges.", IGRAPH_EINVAL);
}
if (no_of_edges > 0) {
/* Must not call vector_min on empty vector */
igraph_real_t minlength = igraph_vector_min(lengths);
if (minlength <= 0) {
IGRAPH_ERROR("Edge lengths must be strictly positive.", IGRAPH_EINVAL);
}
if (isnan(minlength)) {
IGRAPH_ERROR("Edge lengths must not be NaN.", IGRAPH_EINVAL);
}
}
IGRAPH_CHECK(igraph_2wheap_init(&heap, no_of_nodes));
IGRAPH_FINALLY(igraph_2wheap_destroy, &heap);
IGRAPH_CHECK(igraph_inclist_init_empty(&parents, no_of_nodes));
IGRAPH_FINALLY(igraph_inclist_destroy, &parents);
}
IGRAPH_STACK_INT_INIT_FINALLY(&stack, no_of_nodes);
IGRAPH_CHECK(igraph_vector_int_resize(removed_edges, no_of_edges));
if (edge_betweenness) {
IGRAPH_CHECK(igraph_vector_resize(edge_betweenness, no_of_edges));
if (no_of_edges > 0) {
VECTOR(*edge_betweenness)[no_of_edges - 1] = 0;
}
}
IGRAPH_VECTOR_INIT_FINALLY(&eb, no_of_edges);
IGRAPH_BITSET_INIT_FINALLY(&passive, no_of_edges);
/* Estimate the number of steps to be taken.
* It is assumed that one iteration is O(|E||V|), but |V| is constant
* anyway, so we will have approximately |E|^2 / 2 steps, and one
* iteration of the outer loop advances the step counter by the number
* of remaining edges at that iteration.
*/
steps = no_of_edges / 2.0 * (no_of_edges + 1);
steps_done = 0;
for (igraph_int_t e = 0; e < no_of_edges; steps_done += no_of_edges - e, e++) {
IGRAPH_PROGRESS("Edge betweenness community detection: ",
100.0 * steps_done / steps, NULL);
igraph_vector_null(&eb);
if (lengths == NULL) {
/* Unweighted variant follows */
/* The following for loop is copied almost intact from
* igraph_edge_betweenness_cutoff */
for (igraph_int_t source = 0; source < no_of_nodes; source++) {
IGRAPH_ALLOW_INTERRUPTION();
memset(distance, 0, (size_t) no_of_nodes * sizeof(double));
memset(nrgeo, 0, (size_t) no_of_nodes * sizeof(double));
memset(tmpscore, 0, (size_t) no_of_nodes * sizeof(double));
igraph_stack_int_clear(&stack); /* it should be empty anyway... */
IGRAPH_CHECK(igraph_dqueue_int_push(&q, source));
nrgeo[source] = 1;
distance[source] = 0;
while (!igraph_dqueue_int_empty(&q)) {
igraph_int_t actnode = igraph_dqueue_int_pop(&q);
neip = igraph_inclist_get(elist_out_p, actnode);
neino = igraph_vector_int_size(neip);
for (igraph_int_t i = 0; i < neino; i++) {
igraph_int_t edge = VECTOR(*neip)[i];
igraph_int_t neighbor = IGRAPH_OTHER(graph, edge, actnode);
if (nrgeo[neighbor] != 0) {
/* we've already seen this node, another shortest path? */
if (distance[neighbor] == distance[actnode] + 1) {
nrgeo[neighbor] += nrgeo[actnode];
}
} else {
/* we haven't seen this node yet */
nrgeo[neighbor] += nrgeo[actnode];
distance[neighbor] = distance[actnode] + 1;
IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor));
IGRAPH_CHECK(igraph_stack_int_push(&stack, neighbor));
}
}
} /* while !igraph_dqueue_int_empty */
/* Ok, we've the distance of each node and also the number of
shortest paths to them. Now we do an inverse search, starting
with the farthest nodes. */
while (!igraph_stack_int_empty(&stack)) {
igraph_int_t actnode = igraph_stack_int_pop(&stack);
if (distance[actnode] < 1) {
continue; /* skip source node */
}
/* set the temporary score of the friends */
neip = igraph_inclist_get(elist_in_p, actnode);
neino = igraph_vector_int_size(neip);
for (igraph_int_t i = 0; i < neino; i++) {
igraph_int_t edge = VECTOR(*neip)[i];
igraph_int_t neighbor = IGRAPH_OTHER(graph, edge, actnode);
if (distance[neighbor] == distance[actnode] - 1 &&
nrgeo[neighbor] != 0) {
tmpscore[neighbor] +=
(tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode];
VECTOR(eb)[edge] +=
(tmpscore[actnode] + 1) * nrgeo[neighbor] / nrgeo[actnode];
}
}
}
/* Ok, we've the scores for this source */
} /* for source <= no_of_nodes */
} else {
/* Weighted variant follows */
const igraph_real_t eps = IGRAPH_SHORTEST_PATH_EPSILON;
int cmp_result;
/* The following for loop is copied almost intact from
* igraph_i_edge_betweenness_cutoff_weighted */
for (igraph_int_t source = 0; source < no_of_nodes; source++) {
/* This will contain the edge betweenness in the current step */
IGRAPH_ALLOW_INTERRUPTION();
memset(distance, 0, (size_t) no_of_nodes * sizeof(double));
memset(nrgeo, 0, (size_t) no_of_nodes * sizeof(double));
memset(tmpscore, 0, (size_t) no_of_nodes * sizeof(double));
IGRAPH_CHECK(igraph_2wheap_push_with_index(&heap, source, 0));
distance[source] = 1.0;
nrgeo[source] = 1;
while (!igraph_2wheap_empty(&heap)) {
igraph_int_t minnei = igraph_2wheap_max_index(&heap);
igraph_real_t mindist = -igraph_2wheap_delete_max(&heap);
IGRAPH_CHECK(igraph_stack_int_push(&stack, minnei));
neip = igraph_inclist_get(elist_out_p, minnei);
neino = igraph_vector_int_size(neip);
for (igraph_int_t i = 0; i < neino; i++) {
igraph_int_t edge = VECTOR(*neip)[i];
igraph_int_t to = IGRAPH_OTHER(graph, edge, minnei);
igraph_real_t altdist = mindist + VECTOR(*lengths)[edge];
igraph_real_t curdist = distance[to];
igraph_vector_int_t *v;
/* Note: curdist == 0 means infinity, and for this case
* cmp_result should be -1. However, this case is handled
* specially below, without referring to cmp_result. */
cmp_result = igraph_cmp_epsilon(altdist, curdist - 1, eps);
if (curdist == 0) {
/* This is the first finite distance to 'to' */
v = igraph_inclist_get(&parents, to);
igraph_vector_int_resize(v, 1);
VECTOR(*v)[0] = edge;
nrgeo[to] = nrgeo[minnei];
distance[to] = altdist + 1.0;
IGRAPH_CHECK(igraph_2wheap_push_with_index(&heap, to, -altdist));
} else if (cmp_result < 0) {
/* This is a shorter path */
v = igraph_inclist_get(&parents, to);
igraph_vector_int_resize(v, 1);
VECTOR(*v)[0] = edge;
nrgeo[to] = nrgeo[minnei];
distance[to] = altdist + 1.0;
igraph_2wheap_modify(&heap, to, -altdist);
} else if (cmp_result == 0) {
/* Another path with the same length */
v = igraph_inclist_get(&parents, to);
IGRAPH_CHECK(igraph_vector_int_push_back(v, edge));
nrgeo[to] += nrgeo[minnei];
}
}
} /* igraph_2wheap_empty(&Q) */
while (!igraph_stack_int_empty(&stack)) {
igraph_int_t w = igraph_stack_int_pop(&stack);
igraph_vector_int_t *parv = igraph_inclist_get(&parents, w);
igraph_int_t parv_len = igraph_vector_int_size(parv);
for (igraph_int_t i = 0; i < parv_len; i++) {
igraph_int_t fedge = VECTOR(*parv)[i];
igraph_int_t neighbor = IGRAPH_OTHER(graph, fedge, w);
tmpscore[neighbor] += (tmpscore[w] + 1) * nrgeo[neighbor] / nrgeo[w];
VECTOR(eb)[fedge] += (tmpscore[w] + 1) * nrgeo[neighbor] / nrgeo[w];
}
tmpscore[w] = 0;
distance[w] = 0;
nrgeo[w] = 0;
igraph_vector_int_clear(parv);
}
} /* source < no_of_nodes */
}
/* Now look for the smallest edge betweenness */
/* and eliminate that edge from the network */
maxedge = igraph_i_which_max_active_ratio(&eb, weights, &passive);
VECTOR(*removed_edges)[e] = maxedge;
if (edge_betweenness) {
VECTOR(*edge_betweenness)[e] = VECTOR(eb)[maxedge];
if (!directed) {
VECTOR(*edge_betweenness)[e] /= 2.0;
}
}
IGRAPH_BIT_SET(passive, maxedge);
IGRAPH_CHECK(igraph_edge(graph, maxedge, &from, &to));
neip = igraph_inclist_get(elist_in_p, to);
neino = igraph_vector_int_size(neip);
igraph_vector_int_search(neip, 0, maxedge, &pos);
VECTOR(*neip)[pos] = VECTOR(*neip)[neino - 1];
igraph_vector_int_pop_back(neip);
neip = igraph_inclist_get(elist_out_p, from);
neino = igraph_vector_int_size(neip);
igraph_vector_int_search(neip, 0, maxedge, &pos);
VECTOR(*neip)[pos] = VECTOR(*neip)[neino - 1];
igraph_vector_int_pop_back(neip);
}
IGRAPH_PROGRESS("Edge betweenness community detection: ", 100.0, NULL);
igraph_bitset_destroy(&passive);
igraph_vector_destroy(&eb);
igraph_stack_int_destroy(&stack);
IGRAPH_FINALLY_CLEAN(3);
if (lengths == NULL) {
igraph_dqueue_int_destroy(&q);
IGRAPH_FINALLY_CLEAN(1);
} else {
igraph_2wheap_destroy(&heap);
igraph_inclist_destroy(&parents);
IGRAPH_FINALLY_CLEAN(2);
}
igraph_free(tmpscore);
igraph_free(nrgeo);
igraph_free(distance);
IGRAPH_FINALLY_CLEAN(3);
if (directed) {
igraph_inclist_destroy(&elist_out);
igraph_inclist_destroy(&elist_in);
IGRAPH_FINALLY_CLEAN(2);
} else {
igraph_inclist_destroy(&elist_out);
IGRAPH_FINALLY_CLEAN(1);
}
if (merges || bridges || modularity || membership) {
IGRAPH_CHECK(igraph_community_eb_get_merges(graph, directed, removed_edges, weights, merges,
bridges, modularity,
membership));
}
if (result_owned) {
igraph_vector_int_destroy(removed_edges);
IGRAPH_FREE(removed_edges);
IGRAPH_FINALLY_CLEAN(2);
}
return IGRAPH_SUCCESS;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,247 @@
/*
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_adjlist.h"
#include "igraph_components.h"
#include "igraph_interface.h"
#include "igraph_random.h"
#include "igraph_structural.h"
/**
* \ingroup communities
* \function igraph_community_fluid_communities
* \brief Community detection based on fluids interacting on the graph.
*
* The algorithm is based on the simple idea of
* several fluids interacting in a non-homogeneous environment
* (the graph topology), expanding and contracting based on their
* interaction and density. Weighted graphs are not supported.
*
* </para><para>
* This function implements the community detection method described in:
* Parés F, Gasulla DG, et. al. (2018) Fluid Communities: A Competitive,
* Scalable and Diverse Community Detection Algorithm. In: Complex Networks
* &amp; Their Applications VI: Proceedings of Complex Networks 2017 (The Sixth
* International Conference on Complex Networks and Their Applications),
* Springer, vol 689, p 229. https://doi.org/10.1007/978-3-319-72150-7_19
*
* \param graph The input graph. The graph must be simple and connected.
* Edge directions will be ignored.
* \param no_of_communities The number of communities to be found. Must be
* greater than 0 and fewer than number of vertices in the graph.
* \param membership The result vector mapping vertices to the communities
* they are assigned to.
* \return Error code.
*
* Time complexity: O(|E|)
*/
igraph_error_t igraph_community_fluid_communities(
const igraph_t *graph,
igraph_int_t no_of_communities,
igraph_vector_int_t *membership) {
const igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t i, j, k, kv1;
igraph_adjlist_t al;
igraph_real_t max_density;
igraph_bool_t is_simple, is_connected, running;
igraph_vector_t density, label_counters;
igraph_vector_int_t dominant_labels, node_order, com_to_numvertices;
/* Checking input values */
if (igraph_is_directed(graph)) {
IGRAPH_WARNING("Edge directions are ignored by fluid community detection.");
}
IGRAPH_CHECK(igraph_is_simple(graph, &is_simple, IGRAPH_UNDIRECTED));
if (!is_simple) {
IGRAPH_ERROR("Fluid community detection supports only simple graphs.", IGRAPH_EINVAL);
}
/* This must come before the connectedness check so we can support the null
* graph (considered disconnected) for purposes of convenience. */
if (no_of_nodes < 2) {
if (membership) {
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
igraph_vector_int_null(membership);
}
return IGRAPH_SUCCESS;
}
if (no_of_communities < 1) {
IGRAPH_ERROR("Number of requested communities must be positive.", IGRAPH_EINVAL);
}
if (no_of_communities > no_of_nodes) {
IGRAPH_ERROR("Number of requested communities must not be greater than the number of nodes.",
IGRAPH_EINVAL);
}
IGRAPH_CHECK(igraph_is_connected(graph, &is_connected, IGRAPH_WEAK));
if (!is_connected) {
IGRAPH_ERROR("Fluid community detection supports only connected graphs.", IGRAPH_EINVAL);
}
/* Internal variables initialization */
max_density = 1.0;
/* Resize membership vector (number of nodes) */
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
/* Initialize density and com_to_numvertices vectors */
IGRAPH_CHECK(igraph_vector_init(&density, no_of_communities));
IGRAPH_FINALLY(igraph_vector_destroy, &density);
IGRAPH_CHECK(igraph_vector_int_init(&com_to_numvertices, no_of_communities));
IGRAPH_FINALLY(igraph_vector_int_destroy, &com_to_numvertices);
/* Initialize node ordering vector */
IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes));
IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order);
/* Initialize the membership vector with 0 values */
igraph_vector_int_null(membership);
/* Initialize densities to max_density */
igraph_vector_fill(&density, max_density);
/* Initialize com_to_numvertices and initialize communities into membership vector */
igraph_vector_int_shuffle(&node_order);
for (i = 0; i < no_of_communities; i++) {
/* Initialize membership at initial nodes for each community
* where 0 refers to have no label*/
VECTOR(*membership)[VECTOR(node_order)[i]] = i + 1;
/* Initialize com_to_numvertices list: Number of vertices for each community */
VECTOR(com_to_numvertices)[i] = 1;
}
/* Create an adjacency list representation for efficiency. */
IGRAPH_CHECK(igraph_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE));
IGRAPH_FINALLY(igraph_adjlist_destroy, &al);
/* Create storage space for counting distinct labels and dominant ones */
IGRAPH_VECTOR_INT_INIT_FINALLY(&dominant_labels, no_of_communities);
IGRAPH_CHECK(igraph_vector_init(&label_counters, no_of_communities));
IGRAPH_FINALLY(igraph_vector_destroy, &label_counters);
/* running is the convergence boolean variable */
running = true;
while (running) {
/* Declarations of variables used inside main loop */
igraph_int_t v1, size, rand_idx;
igraph_real_t max_count, label_counter_diff;
igraph_vector_int_t *neis;
igraph_bool_t same_label_in_dominant;
running = false;
/* Shuffle the node ordering vector */
igraph_vector_int_shuffle(&node_order);
/* In the prescribed order, loop over the vertices and reassign labels */
for (i = 0; i < no_of_nodes; i++) {
/* Clear dominant_labels and nonzero_labels vectors */
igraph_vector_int_clear(&dominant_labels);
igraph_vector_null(&label_counters);
/* Obtain actual node index */
v1 = VECTOR(node_order)[i];
/* Take into account same label in updating rule */
kv1 = VECTOR(*membership)[v1];
max_count = 0.0;
if (kv1 != 0) {
VECTOR(label_counters)[kv1 - 1] += VECTOR(density)[kv1 - 1];
/* Set up max_count */
max_count = VECTOR(density)[kv1 - 1];
/* Initialize dominant_labels */
IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1));
VECTOR(dominant_labels)[0] = kv1;
}
/* Count the weights corresponding to different labels */
neis = igraph_adjlist_get(&al, v1);
size = igraph_vector_int_size(neis);
for (j = 0; j < size; j++) {
k = VECTOR(*membership)[VECTOR(*neis)[j]];
/* skip if it has no label yet */
if (k == 0) {
continue;
}
/* Update label counter and evaluate diff against max_count*/
VECTOR(label_counters)[k - 1] += VECTOR(density)[k - 1];
label_counter_diff = VECTOR(label_counters)[k - 1] - max_count;
/* Check if this label must be included in dominant_labels vector */
if (label_counter_diff > 0.0001) {
max_count = VECTOR(label_counters)[k - 1];
IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1));
VECTOR(dominant_labels)[0] = k;
} else if (-0.0001 < label_counter_diff && label_counter_diff < 0.0001) {
IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k));
}
}
if (!igraph_vector_int_empty(&dominant_labels)) {
/* Maintain same label if it exists in dominant_labels */
same_label_in_dominant = igraph_vector_int_contains(&dominant_labels, kv1);
if (!same_label_in_dominant) {
/* We need at least one more iteration */
running = true;
/* Select randomly from the dominant labels */
rand_idx = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1);
k = VECTOR(dominant_labels)[rand_idx];
if (kv1 != 0) {
/* Subtract 1 vertex from corresponding community in com_to_numvertices */
VECTOR(com_to_numvertices)[kv1 - 1] -= 1;
/* Re-calculate density for community kv1 */
VECTOR(density)[kv1 - 1] = max_density / VECTOR(com_to_numvertices)[kv1 - 1];
}
/* Update vertex new label */
VECTOR(*membership)[v1] = k;
/* Add 1 vertex to corresponding new community in com_to_numvertices */
VECTOR(com_to_numvertices)[k - 1] += 1;
/* Re-calculate density for new community k */
VECTOR(density)[k - 1] = max_density / VECTOR(com_to_numvertices)[k - 1];
}
}
}
}
/* Shift back the membership vector */
/* There must be no 0 labels in membership vector at this point */
for (i = 0; i < no_of_nodes; i++) {
VECTOR(*membership)[i] -= 1;
IGRAPH_ASSERT(VECTOR(*membership)[i] >= 0); /* all vertices must have a community assigned */
}
igraph_adjlist_destroy(&al);
igraph_vector_int_destroy(&node_order);
igraph_vector_destroy(&density);
igraph_vector_int_destroy(&com_to_numvertices);
igraph_vector_destroy(&label_counters);
igraph_vector_int_destroy(&dominant_labels);
IGRAPH_FINALLY_CLEAN(6);
return IGRAPH_SUCCESS;
}
@@ -0,0 +1,275 @@
/*
igraph library.
Copyright (C) 2011-2025 The igraph development team <igraph@igraph.org>
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, see <https://www.gnu.org/licenses/>.
*/
#include "igraph_community.h"
#include "config.h"
#ifdef HAVE_INFOMAP
#include "igraph_interface.h"
#include "igraph_interrupt.h"
#include "core/exceptions.h"
#include <climits>
#include <cmath>
#include "Infomap.h"
static igraph_error_t infomap_get_membership(infomap::InfomapBase &infomap, igraph_vector_int_t *membership) {
igraph_int_t n = infomap.numLeafNodes();
IGRAPH_CHECK(igraph_vector_int_resize(membership, n));
for (auto it(infomap.iterTreePhysical(1)); !it.isEnd(); ++it) {
infomap::InfoNode &node = *it;
if (node.isLeaf()) {
// Note: We must use moduleIndex() and not moduleId(), as the latter
// may be >= vcount, which causes igraph_reindex_membership() to fail.
VECTOR(*membership)[node.physicalId] = it.moduleIndex();
}
}
// Re-index membership
IGRAPH_CHECK(igraph_reindex_membership(membership, NULL, NULL));
return IGRAPH_SUCCESS;
}
static igraph_error_t convert_igraph_to_infomap(const igraph_t *graph,
const igraph_vector_t *edge_weights,
const igraph_vector_t *vertex_weights,
infomap::Network &network) {
igraph_int_t vcount = igraph_vcount(graph);
igraph_int_t ecount = igraph_ecount(graph);
if (vcount > UINT_MAX) {
IGRAPH_ERROR("Graph has too many vertices for Infomap.", IGRAPH_EINVAL);
}
for (igraph_int_t v = 0; v < vcount; v++) {
if (vertex_weights) {
double weight = VECTOR(*vertex_weights)[v];
if (weight < 0) {
IGRAPH_ERRORF("Vertex weights must not be negative, got %g.",
IGRAPH_EINVAL, weight);
}
if (! std::isfinite(weight)) {
IGRAPH_ERRORF("Vertex weights must not be infinite or NaN, got %g.",
IGRAPH_EINVAL, weight);
}
network.addNode(v, weight);
} else {
network.addNode(v);
}
}
for (igraph_int_t e = 0; e < ecount; e++) {
igraph_int_t v1 = IGRAPH_FROM(graph, e);
igraph_int_t v2 = IGRAPH_TO(graph, e);
if (edge_weights) {
double weight = VECTOR(*edge_weights)[e];
if (weight < 0) {
IGRAPH_ERRORF("Edge weights must not be negative, got %g.",
IGRAPH_EINVAL, weight);
}
if (! std::isfinite(weight)) {
IGRAPH_ERRORF("Edge weights must not be infinite or NaN, got %g.",
IGRAPH_EINVAL, weight);
}
network.addLink(v1, v2, weight);
} else {
network.addLink(v1, v2);
}
}
return IGRAPH_SUCCESS;
}
// Needed in case C++'s bool is not compatible with igraph's igraph_bool_t
// which may happen in some configurations on some platforms, notable with R/igraph.
static bool infomap_allow_interruption() {
return igraph_allow_interruption();
}
#endif // HAVE_INFOMAP
/**
* \function igraph_community_infomap
* \brief Community structure that minimizes the expected description length of a random walker trajectory.
*
* Implementation of the Infomap community detection algorithm of
* Martin Rosvall and Carl T. Bergstrom. This algorithm takes edge directions
* into account. For more details, see the visualization of the math and the
* map generator at https://www.mapequation.org.
*
* </para><para>
* Infomap is based on a random walker model similar to PageRank: the walker
* either chooses out-edges to follow with probabilities proportional to edge
* weights, or teleports to a random vertex with probability 0.15. Vertex weights
* can be given to control the probability of choosing different vertices as
* the target of the teleportation. In addition, Infomap can be regularized to
* account for potential missing links.
*
* </para><para>
* As of igraph 1.0, the Infomap library written by Daniel Edler, Anton Holmgren
* and Martin Rosvall is used. See https://github.com/mapequation/infomap/.
*
* </para><para>
* If you want to specify a random seed (as in the original
* implementation) you can use \ref igraph_rng_seed().
*
* </para><para>
* References:
*
* </para><para>
* M. Rosvall and C. T. Bergstrom:
* Maps of information flow reveal community structure in complex networks,
* PNAS 105, 1118 (2008).
* https://dx.doi.org/10.1073/pnas.0706851105, https://arxiv.org/abs/0707.0609
*
* </para><para>
* M. Rosvall, D. Axelsson, and C. T. Bergstrom:
* The map equation,
* Eur. Phys. J. Special Topics 178, 13 (2009).
* https://dx.doi.org/10.1140/epjst/e2010-01179-1, https://arxiv.org/abs/0906.1405
*
* </para><para>
* Smiljanić, Jelena, Daniel Edler, and Martin Rosvall: Mapping Flows on
* Sparse Networks with Missing Links. Phys Rev E 102 (11): 012302 (2020).
* https://doi.org/10.1103/PhysRevE.102.012302, https://arxiv.org/abs/2106.14798
*
* \param graph The input graph. Edge directions are taken into account.
* \param edge_weights Numeric vector giving the weights of the edges.
* The random walker will favour edges with high weights over
* edges with low weights; the probability of picking a particular
* outbound edge from a node is directly proportional to its weight.
* If it is \c NULL then all edges will have equal
* weights. The weights are expected to be non-negative.
* \param vertex_weights Numeric vector giving the weights of the vertices.
* Vertices with higher weights are favoured by the random walker
* when it teleports to a new vertex. The probability of picking a vertex
* when the random walker teleports is directly proportional to the weight
* of the vertex. If this argument is \c NULL then all vertices will have
* equal weights. Weights are expected to be positive.
* \param nb_trials The number of attempts to partition the network
* (can be any integer value equal to or larger than 1).
* \param is_regularized If true, adds a fully connected Bayesian prior network
* to avoid overfitting due to missing links.
* \param regularization_strength Adjust relative strength of the Bayesian prior
* network used for regularization. This multiplies the default strength, a
* parameter of 1 hence uses the default regularization strength. Ignored
* when \p is_regularized is set to \c false.
* \param membership Pointer to a vector. The membership vector is
* stored here. \c NULL means that the caller is not interested in the
* membership vector.
* \param codelength Pointer to a real. If not \c NULL the code length of the
* partition is stored here.
* \return Error code.
* When Infomap is not available, \c IGRAPH_UNIMPLEMENTED is returned.
*
* \sa \ref igraph_community_spinglass(), \ref
* igraph_community_edge_betweenness(), \ref igraph_community_walktrap().
*
* Time complexity: TODO.
*/
igraph_error_t igraph_community_infomap(
const igraph_t *graph,
const igraph_vector_t *edge_weights,
const igraph_vector_t *vertex_weights,
igraph_int_t nb_trials,
igraph_bool_t is_regularized,
igraph_real_t regularization_strength,
igraph_vector_int_t *membership,
igraph_real_t *codelength) {
#ifndef HAVE_INFOMAP
IGRAPH_ERROR("Infomap is not available.", IGRAPH_UNIMPLEMENTED);
#else
const igraph_int_t vcount = igraph_vcount(graph);
const igraph_int_t ecount = igraph_ecount(graph);
if (edge_weights) {
if (igraph_vector_size(edge_weights) != ecount) {
IGRAPH_ERROR("Length of edge weight vector does not match edge count.",
IGRAPH_EINVAL);
}
}
if (vertex_weights) {
if (igraph_vector_size(vertex_weights) != vcount) {
IGRAPH_ERROR("Length of vertex weight vector does not match edge count.",
IGRAPH_EINVAL);
}
}
if (nb_trials < 1) {
IGRAPH_ERRORF("Number of trials must be at least 1, got %" IGRAPH_PRId ".",
IGRAPH_EINVAL,
nb_trials);
}
// Handle null graph
if (vcount == 0) {
if (membership) {
IGRAPH_CHECK(igraph_vector_int_resize(membership, 0));
}
if (codelength) {
*codelength = IGRAPH_NAN;
}
return IGRAPH_SUCCESS;
}
IGRAPH_HANDLE_EXCEPTIONS_BEGIN;
// Configure infomap
infomap::Config conf;
conf.twoLevel = true;
conf.numTrials = nb_trials;
conf.silent = true;
conf.directed = igraph_is_directed(graph);
conf.interruptionHandler = &infomap_allow_interruption;
conf.regularized = is_regularized;
conf.regularizationStrength = regularization_strength;
infomap::InfomapBase infomap(conf);
IGRAPH_CHECK(convert_igraph_to_infomap(graph, edge_weights, vertex_weights, infomap.network()));
infomap.run();
if (membership) {
IGRAPH_CHECK(infomap_get_membership(infomap, membership));
}
if (codelength) {
*codelength = infomap.codelength();
}
IGRAPH_HANDLE_EXCEPTIONS_END;
return IGRAPH_SUCCESS;
#endif
}
@@ -0,0 +1,765 @@
/*
igraph library.
Copyright (C) 2007-2022 The igraph development team <igraph@igraph.org>
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, see <https://www.gnu.org/licenses/>.
*/
#include "igraph_community.h"
#include "igraph_adjlist.h"
#include "igraph_dqueue.h"
#include "igraph_interface.h"
#include "igraph_memory.h"
#include "igraph_random.h"
#include "core/interruption.h"
static igraph_error_t community_label_propagation(
const igraph_t *graph,
igraph_vector_int_t *membership,
igraph_neimode_t mode,
const igraph_vector_t *weights,
const igraph_vector_bool_t *fixed,
igraph_bool_t retention) {
const igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t no_of_not_fixed_nodes = 0;
igraph_int_t i, j, k;
igraph_adjlist_t al;
igraph_inclist_t il;
igraph_bool_t running, control_iteration;
igraph_vector_t label_weights;
igraph_vector_int_t dominant_labels, nonzero_labels, node_order;
igraph_neimode_t reverse_mode;
int iter = 0; /* interruption counter */
reverse_mode = IGRAPH_REVERSE_MODE(mode);
/* Create an adjacency/incidence list representation for efficiency.
* For the unweighted case, the adjacency list is enough. For the
* weighted case, we need the incidence list */
if (weights) {
IGRAPH_CHECK(igraph_inclist_init(graph, &il, reverse_mode, IGRAPH_LOOPS_ONCE));
IGRAPH_FINALLY(igraph_inclist_destroy, &il);
} else {
IGRAPH_CHECK(igraph_adjlist_init(graph, &al, reverse_mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
IGRAPH_FINALLY(igraph_adjlist_destroy, &al);
}
/* Create storage space for counting distinct labels and dominant ones */
IGRAPH_VECTOR_INIT_FINALLY(&label_weights, no_of_nodes);
IGRAPH_VECTOR_INT_INIT_FINALLY(&dominant_labels, 0);
IGRAPH_VECTOR_INT_INIT_FINALLY(&nonzero_labels, 0);
IGRAPH_CHECK(igraph_vector_int_reserve(&dominant_labels, 2));
/* Initialize node ordering vector with only the not fixed nodes */
if (fixed) {
IGRAPH_VECTOR_INT_INIT_FINALLY(&node_order, no_of_nodes);
for (i = 0; i < no_of_nodes; i++) {
if (!VECTOR(*fixed)[i]) {
VECTOR(node_order)[no_of_not_fixed_nodes] = i;
no_of_not_fixed_nodes++;
}
}
IGRAPH_CHECK(igraph_vector_int_resize(&node_order, no_of_not_fixed_nodes));
} else {
IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes));
IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order);
no_of_not_fixed_nodes = no_of_nodes;
}
/* There are two modes of operation in this implementation: retention or
* dominance. When using retention, we prefer to keep the current label of a node.
* Only if the current label is not among the dominant labels will we
* update the label. If a label changes, we will continue to iterate
* over all nodes.
*
* When not using retention we check for dominance after each iteration. This
* is implemented as two alternating types of iterations, one for changing
* labels and the other one for checking the end condition - every vertex in the
* graph has a label to which the maximum number of its neighbors belongs. If
* control_iteration is true, we are just checking the end condition and not
* relabeling nodes.
*/
control_iteration = true;
running = true;
while (running) {
igraph_int_t v1, num_neis;
igraph_real_t max_count;
igraph_vector_int_t *neis;
igraph_vector_int_t *ineis;
igraph_bool_t was_zero;
IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8);
if (retention) {
/* We stop in this iteration by default, unless a label changes */
running = false;
/* Shuffle the node ordering vector */
igraph_vector_int_shuffle(&node_order);
} else {
if (control_iteration) {
/* If we are in the control iteration, we expect in the beginning of
the iteration that all vertices meet the end condition, so 'running' is false.
If some of them does not, 'running' is set to true later in the code. */
running = false;
} else {
/* Shuffle the node ordering vector if we are in the label updating iteration */
igraph_vector_int_shuffle(&node_order);
}
}
/* In the prescribed order, loop over the vertices and reassign labels */
for (i = 0; i < no_of_not_fixed_nodes; i++) {
v1 = VECTOR(node_order)[i];
/* Count the weights corresponding to different labels */
igraph_vector_int_clear(&dominant_labels);
igraph_vector_int_clear(&nonzero_labels);
max_count = 0.0;
if (weights) {
ineis = igraph_inclist_get(&il, v1);
num_neis = igraph_vector_int_size(ineis);
for (j = 0; j < num_neis; j++) {
k = VECTOR(*membership)[IGRAPH_OTHER(graph, VECTOR(*ineis)[j], v1)];
if (k < 0) {
continue; /* skip if it has no label yet */
}
was_zero = (VECTOR(label_weights)[k] == 0);
VECTOR(label_weights)[k] += VECTOR(*weights)[VECTOR(*ineis)[j]];
if (was_zero && VECTOR(label_weights)[k] != 0) {
/* weights just became nonzero */
IGRAPH_CHECK(igraph_vector_int_push_back(&nonzero_labels, k));
}
if (max_count < VECTOR(label_weights)[k]) {
max_count = VECTOR(label_weights)[k];
IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1));
VECTOR(dominant_labels)[0] = k;
} else if (max_count == VECTOR(label_weights)[k]) {
IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k));
}
}
} else {
neis = igraph_adjlist_get(&al, v1);
num_neis = igraph_vector_int_size(neis);
for (j = 0; j < num_neis; j++) {
k = VECTOR(*membership)[VECTOR(*neis)[j]];
if (k < 0) {
continue; /* skip if it has no label yet */
}
VECTOR(label_weights)[k]++;
if (VECTOR(label_weights)[k] == 1) {
/* weights just became nonzero */
IGRAPH_CHECK(igraph_vector_int_push_back(&nonzero_labels, k));
}
if (max_count < VECTOR(label_weights)[k]) {
max_count = VECTOR(label_weights)[k];
IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1));
VECTOR(dominant_labels)[0] = k;
} else if (max_count == VECTOR(label_weights)[k]) {
IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k));
}
}
}
if (igraph_vector_int_size(&dominant_labels) > 0) {
if (retention) {
/* If we are using retention, we first check if the current label
is among the maximum label. */
j = (long)VECTOR(*membership)[v1];
if (j < 0 || /* Doesn't have a label yet */
VECTOR(label_weights)[j] == 0 || /* Label not present in neighbors */
VECTOR(label_weights)[j] < max_count /* Label not dominant */) {
/* Select randomly from the dominant labels */
k = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1);
k = VECTOR(dominant_labels)[(long int)k];
/* If label changes, we will continue running */
if (k != j) {
running = true;
}
/* Actually change label */
VECTOR(*membership)[v1] = k;
}
} else {
/* We are not using retention, so check if we should do a control iteration
or an update iteration. */
if (control_iteration) {
/* Check if the _current_ label of the node is also dominant */
k = VECTOR(*membership)[v1];
if (k < 0 || /* No label assigned yet or */
VECTOR(label_weights)[k] < max_count /* Label is not maximum */
) {
/* Nope, we need at least one more iteration */
running = true;
}
} else {
/* Select randomly from the dominant labels */
k = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1);
VECTOR(*membership)[v1] = VECTOR(dominant_labels)[k];
}
}
}
/* Clear the nonzero elements in label_weights */
num_neis = igraph_vector_int_size(&nonzero_labels);
for (j = 0; j < num_neis; j++) {
VECTOR(label_weights)[VECTOR(nonzero_labels)[j]] = 0;
}
}
/* Alternating between control iterations and label updating iterations */
if (!retention) {
control_iteration = !control_iteration;
}
}
if (weights) {
igraph_inclist_destroy(&il);
} else {
igraph_adjlist_destroy(&al);
}
IGRAPH_FINALLY_CLEAN(1);
igraph_vector_int_destroy(&node_order);
igraph_vector_int_destroy(&nonzero_labels);
igraph_vector_int_destroy(&dominant_labels);
igraph_vector_destroy(&label_weights);
IGRAPH_FINALLY_CLEAN(4);
return IGRAPH_SUCCESS;
}
static igraph_error_t community_fast_label_propagation(
const igraph_t *graph,
igraph_vector_int_t *membership,
igraph_neimode_t mode,
const igraph_vector_t *weights,
const igraph_vector_bool_t *fixed) {
const igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t no_of_not_fixed_nodes = 0;
igraph_int_t i, j, k;
igraph_inclist_t il;
igraph_adjlist_t al;
igraph_vector_t label_weights;
igraph_vector_int_t dominant_labels, nonzero_labels, node_order;
igraph_dqueue_int_t queue;
igraph_vector_bool_t in_queue;
igraph_neimode_t reverse_mode;
int iter = 0; /* interruption counter */
reverse_mode = IGRAPH_REVERSE_MODE(mode);
if (weights) {
IGRAPH_CHECK(igraph_inclist_init(graph, &il, reverse_mode, IGRAPH_LOOPS_ONCE));
IGRAPH_FINALLY(igraph_inclist_destroy, &il);
} else {
IGRAPH_CHECK(igraph_adjlist_init(graph, &al, reverse_mode, IGRAPH_LOOPS_ONCE, IGRAPH_MULTIPLE));
IGRAPH_FINALLY(igraph_adjlist_destroy, &al);
}
/* Create storage space for counting distinct labels and dominant ones */
IGRAPH_VECTOR_INIT_FINALLY(&label_weights, no_of_nodes);
IGRAPH_VECTOR_INT_INIT_FINALLY(&dominant_labels, 0);
IGRAPH_VECTOR_INT_INIT_FINALLY(&nonzero_labels, 0);
IGRAPH_CHECK(igraph_vector_int_reserve(&dominant_labels, 2));
/* Initialize node ordering vector with only the not fixed nodes */
IGRAPH_DQUEUE_INT_INIT_FINALLY(&queue, no_of_nodes);
IGRAPH_VECTOR_BOOL_INIT_FINALLY(&in_queue, no_of_nodes);
/* Initialize node ordering vector with only the not fixed nodes */
if (fixed) {
IGRAPH_VECTOR_INT_INIT_FINALLY(&node_order, no_of_nodes);
for (i = 0; i < no_of_nodes; i++) {
if (!VECTOR(*fixed)[i]) {
VECTOR(node_order)[no_of_not_fixed_nodes] = i;
no_of_not_fixed_nodes++;
}
}
IGRAPH_CHECK(igraph_vector_int_resize(&node_order, no_of_not_fixed_nodes));
} else {
IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes));
IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order);
no_of_not_fixed_nodes = no_of_nodes;
}
for (i = 0; i < no_of_not_fixed_nodes; i++) {
IGRAPH_CHECK(igraph_dqueue_int_push(&queue, VECTOR(node_order)[i]));
VECTOR(in_queue)[VECTOR(node_order)[i]] = true;
}
igraph_vector_int_destroy(&node_order);
IGRAPH_FINALLY_CLEAN(1);
while (!igraph_dqueue_int_empty(&queue)) {
igraph_int_t v1, v2, e = -1, num_neis;
igraph_real_t max_count;
igraph_vector_int_t *neis;
igraph_bool_t was_zero;
IGRAPH_ALLOW_INTERRUPTION_LIMITED(iter, 1 << 8);
v1 = igraph_dqueue_int_pop(&queue);
VECTOR(in_queue)[v1] = false;
/* Count the weights corresponding to different labels */
igraph_vector_int_clear(&dominant_labels);
igraph_vector_int_clear(&nonzero_labels);
max_count = 0.0;
if (weights) {
neis = igraph_inclist_get(&il, v1);
} else {
neis = igraph_adjlist_get(&al, v1);
}
num_neis = igraph_vector_int_size(neis);
for (j = 0; j < num_neis; j++) {
if (weights) {
e = VECTOR(*neis)[j];
v2 = IGRAPH_OTHER(graph, e, v1);
} else {
v2 = VECTOR(*neis)[j];
}
k = VECTOR(*membership)[v2];
if (k < 0) {
continue; /* skip if it has no label yet */
}
was_zero = (VECTOR(label_weights)[k] == 0);
VECTOR(label_weights)[k] += (weights ? VECTOR(*weights)[e] : 1);
if (was_zero && VECTOR(label_weights)[k] >= 0) {
/* counter just became non-negative */
IGRAPH_CHECK(igraph_vector_int_push_back(&nonzero_labels, k));
}
if (max_count < VECTOR(label_weights)[k]) {
max_count = VECTOR(label_weights)[k];
IGRAPH_CHECK(igraph_vector_int_resize(&dominant_labels, 1));
VECTOR(dominant_labels)[0] = k;
} else if (max_count == VECTOR(label_weights)[k]) {
IGRAPH_CHECK(igraph_vector_int_push_back(&dominant_labels, k));
}
}
if (igraph_vector_int_size(&dominant_labels) > 0) {
igraph_int_t current_label = VECTOR(*membership)[v1];
/* Select randomly from the dominant labels */
k = RNG_INTEGER(0, igraph_vector_int_size(&dominant_labels) - 1);
igraph_int_t new_label = VECTOR(dominant_labels)[k]; /* a dominant label */
/* Check if the _current_ label of the node is not the same */
if (new_label != current_label) {
/* We still need to consider its neighbors not in the new community */
for (j = 0; j < num_neis; j++) {
if (weights) {
e = VECTOR(*neis)[j];
v2 = IGRAPH_OTHER(graph, e, v1);
} else {
v2 = VECTOR(*neis)[j];
}
if (!VECTOR(in_queue)[v2]) {
igraph_int_t neigh_label = VECTOR(*membership)[v2]; /* neighbor community */
if (neigh_label != new_label && /* not in new community */
(fixed == NULL || !VECTOR(*fixed)[v2]) ) { /* not fixed */
IGRAPH_CHECK(igraph_dqueue_int_push(&queue, v2));
VECTOR(in_queue)[v2] = true;
}
}
}
}
VECTOR(*membership)[v1] = new_label;
}
/* Clear the nonzero elements in label_weights */
num_neis = igraph_vector_int_size(&nonzero_labels);
for (j = 0; j < num_neis; j++) {
VECTOR(label_weights)[VECTOR(nonzero_labels)[j]] = 0;
}
}
if (weights) {
igraph_inclist_destroy(&il);
} else {
igraph_adjlist_destroy(&al);
}
IGRAPH_FINALLY_CLEAN(1);
igraph_vector_bool_destroy(&in_queue);
igraph_dqueue_int_destroy(&queue);
igraph_vector_destroy(&label_weights);
igraph_vector_int_destroy(&dominant_labels);
igraph_vector_int_destroy(&nonzero_labels);
IGRAPH_FINALLY_CLEAN(5);
return IGRAPH_SUCCESS;
}
/**
* \ingroup communities
* \function igraph_community_label_propagation
* \brief Community detection based on label propagation.
*
* This function implements the label propagation-based community detection
* algorithm described by Raghavan, Albert and Kumara (2007). This version extends
* the original method by the ability to take edge weights into consideration
* and also by allowing some labels to be fixed. In addition, it implements
* the fast label propagation alternative introduced by Traag and Šubelj (2023).
*
* </para><para>
* The algorithm works by iterating over nodes and updating the label of a node
* based on the labels of its neighbors. The labels that are most frequent among
* the neighbors are said to be dominant labels. The label of a node is always
* updated to a dominant label. The algorithm guarantees that the label for each
* is dominant when it terminates.
*
* </para><para>
* There are several variants implemented, which work slightly differently with
* the dominance of labels. Nodes with a dominant label might no longer have a
* dominant label if one of their neighbors change label. In \c
* IGRAPH_LPA_DOMINANCE an additional iteration over all nodes is made after
* updating all labels to double check whether all nodes indeed have a dominant
* label. When updating the label of a node, labels are always sampled from
* among all dominant labels. The algorithm stops when all nodes have dominant
* labels. In \c IGRAPH_LPA_RETENTION instead labels are only updated when they
* are not dominant. That is, they retain their current label whenever the
* current label is already dominant. The algorithm does not make an additional
* iteration to check for dominance. Instead, it simply keeps track whether a
* label has been updated, and terminates if no updates have been made. In \c
* IGRAPH_LPA_FAST labels are sampled from among all dominant labels, similar to
* \c IGRAPH_LPA_DOMINANCE. Instead of iterating over all nodes, it keeps track
* of a queue of nodes that should be considered. Nodes are popped from the
* queue when they are considered for update. When the label of a node is
* updated, the node's neighbors are added to the queue again (if they weren't
* already in the queue). The algorithm terminates when the queue is empty. All
* variants guarantee that the labels for all nodes are dominant.
*
* </para><para>
* Weights are taken into account as follows: when the new label of node
* \c i is determined, the algorithm iterates over all edges incident on
* node \c i and calculate the total weight of edges leading to other
* nodes with label 0, 1, 2, ..., \c k - 1 (where \c k is the number of possible
* labels). The new label of node \c i will then be the label whose edges
* (among the ones incident on node \c i) have the highest total weight.
*
* </para><para>
* For directed graphs, it is important to know that labels can circulate
* freely only within the strongly connected components of the graph and
* may propagate in only one direction (or not at all) \em between strongly
* connected components. You should treat directed edges as directed only
* if you are aware of the consequences.
*
* </para><para>
* References:
*
* </para><para>
* Raghavan, U.N. and Albert, R. and Kumara, S.: Near linear time algorithm to
* detect community structures in large-scale networks. Phys Rev E 76, 036106
* (2007). https://doi.org/10.1103/PhysRevE.76.036106
*
* </para><para>
* Šubelj, L.: Label propagation for clustering. Chapter in "Advances in
* Network Clustering and Blockmodeling" edited by P. Doreian, V. Batagelj
* &amp; A. Ferligoj (Wiley, New York, 2018).
* https://doi.org/10.1002/9781119483298.ch5
* https://arxiv.org/abs/1709.05634
*
* </para><para>
* Traag, V. A., and Šubelj, L.: Large network community detection by fast
* label propagation. Scientific Reports, 13:1, (2023).
* https://doi.org/10.1038/s41598-023-29610-z
* https://arxiv.org/abs/2209.13338
*
* \param graph The input graph. Note that the algorithm was originally
* defined for undirected graphs. You are advised to set \p mode to
* \c IGRAPH_ALL if you pass a directed graph here to treat it as
* undirected.
* \param membership The membership vector, the result is returned here.
* For each vertex it gives the ID of its community (label).
* \param mode Whether to consider edge directions for the label propagation,
* and if so, which direction the labels should propagate. Ignored for
* undirected graphs. \c IGRAPH_ALL means to ignore edge directions (even
* in directed graphs). \c IGRAPH_OUT means to propagate labels along the
* natural direction of the edges. \c IGRAPH_IN means to propagate labels
* \em backwards (i.e. from head to tail). It is advised to set this to
* \c IGRAPH_ALL unless you are specifically interested in the effect of
* edge directions.
* \param weights The weight vector, it should contain a positive
* weight for all the edges.
* \param initial The initial state. If \c NULL, every vertex will have
* a different label at the beginning. Otherwise it must be a vector
* with an entry for each vertex. Non-negative values denote different
* labels, negative entries denote vertices without labels. Unlabeled
* vertices which are not reachable from any labeled ones will remain
* unlabeled at the end of the label propagation process, and will be
* labeled in an additional step to avoid returning negative values in
* \p membership. In undirected graphs, this happens when entire connected
* components are unlabeled. Then, each unlabeled component will receive
* its own separate label. In directed graphs, the outcome of the
* additional labeling should be considered undefined and may change
* in the future; please do not rely on it.
* \param fixed Boolean vector denoting which labels are fixed. Of course
* this makes sense only if you provided an initial state, otherwise
* this element will be ignored. Note that vertices without labels
* cannot be fixed. The fixed status will be ignored for these with a
* warning. Also note that label numbers by themselves have no meaning,
* and igraph may renumber labels. However, co-membership constraints
* will be respected: two vertices can be fixed to be in the same or in
* different communities.
* \param lpa_variant Which variant of the label propagation algorithm to run.
* \clist
* \cli IGRAPH_LPA_DOMINANCE
* check for dominance of all nodes after each iteration.
* \cli IGRAPH_LPA_RETENTION
* keep current label if among dominant labels, only check if labels changed.
* \cli IGRAPH_LPA_FAST
* sample from dominant labels, only check neighbors.
* \endclist
* \return Error code.
*
* Time complexity: O(m+n)
*
* \example examples/simple/igraph_community_label_propagation.c
*/
igraph_error_t igraph_community_label_propagation(const igraph_t *graph,
igraph_vector_int_t *membership,
igraph_neimode_t mode,
const igraph_vector_t *weights,
const igraph_vector_int_t *initial,
const igraph_vector_bool_t *fixed,
igraph_lpa_variant_t lpa_variant) {
const igraph_int_t no_of_nodes = igraph_vcount(graph);
const igraph_int_t no_of_edges = igraph_ecount(graph);
igraph_int_t no_of_not_fixed_nodes = no_of_nodes;
igraph_int_t i, j, k;
igraph_bool_t unlabelled_left;
/* We make a copy of 'fixed' as a pointer into 'fixed_copy' after casting
* away the constness, and promise ourselves that we will make a proper
* copy of 'fixed' into 'fixed_copy' as soon as we start mutating it */
igraph_vector_bool_t *fixed_copy = (igraph_vector_bool_t *) fixed;
/* Unlabelled nodes are represented with -1. */
#define IS_UNLABELLED(x) (VECTOR(*membership)[x] < 0)
/* Do some initial checks */
if (fixed && igraph_vector_bool_size(fixed) != no_of_nodes) {
IGRAPH_ERROR("Fixed labeling vector length must agree with number of nodes.", IGRAPH_EINVAL);
}
if (weights) {
if (igraph_vector_size(weights) != no_of_edges) {
IGRAPH_ERROR("Length of weight vector must agree with number of edges.", IGRAPH_EINVAL);
}
if (no_of_edges > 0) {
igraph_real_t minweight = igraph_vector_min(weights);
if (minweight < 0) {
IGRAPH_ERROR("Weights must not be negative.", IGRAPH_EINVAL);
}
if (isnan(minweight)) {
IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL);
}
}
}
if (fixed && !initial) {
IGRAPH_WARNING("Ignoring fixed vertices as no initial labeling given.");
}
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
if (initial) {
if (igraph_vector_int_size(initial) != no_of_nodes) {
IGRAPH_ERROR("Initial labeling vector length must agree with number of nodes.", IGRAPH_EINVAL);
}
/* Check if the labels used are valid, initialize membership vector */
for (i = 0; i < no_of_nodes; i++) {
if (VECTOR(*initial)[i] < 0) {
VECTOR(*membership)[i] = -1;
} else {
VECTOR(*membership)[i] = VECTOR(*initial)[i];
}
}
if (fixed) {
for (i = 0; i < no_of_nodes; i++) {
if (VECTOR(*fixed)[i]) {
if (IS_UNLABELLED(i)) {
IGRAPH_WARNING("Fixed nodes cannot be unlabeled, ignoring them.");
/* We cannot modify 'fixed' because it is const, so we make a copy and
* modify 'fixed_copy' instead */
if (fixed_copy == fixed) {
fixed_copy = IGRAPH_CALLOC(1, igraph_vector_bool_t);
IGRAPH_CHECK_OOM(fixed_copy, "Insufficient memory for label propagation.");
IGRAPH_FINALLY(igraph_free, fixed_copy);
IGRAPH_CHECK(igraph_vector_bool_init_copy(fixed_copy, fixed));
IGRAPH_FINALLY(igraph_vector_bool_destroy, fixed_copy);
}
VECTOR(*fixed_copy)[i] = false;
} else {
no_of_not_fixed_nodes--;
}
}
}
}
i = igraph_vector_int_max(membership);
if (i > no_of_nodes) {
IGRAPH_ERROR("Elements of the initial labeling vector must be between 0 and |V|-1.", IGRAPH_EINVAL);
}
} else {
for (i = 0; i < no_of_nodes; i++) {
VECTOR(*membership)[i] = i;
}
}
/* From this point onwards we use 'fixed_copy' instead of 'fixed' */
switch (lpa_variant) {
case IGRAPH_LPA_FAST:
IGRAPH_CHECK(community_fast_label_propagation(graph, membership, mode, weights, fixed_copy));
break;
case IGRAPH_LPA_RETENTION:
IGRAPH_CHECK(community_label_propagation(graph, membership, mode, weights, fixed_copy, /* retention */ true ));
break;
case IGRAPH_LPA_DOMINANCE:
IGRAPH_CHECK(community_label_propagation(graph, membership, mode, weights, fixed_copy, /* retention */ false));
break;
default:
IGRAPH_ERROR("Invalid igraph_lpa_variant_t.", IGRAPH_EINVAL);
}
/* Permute labels in increasing order */
igraph_vector_int_t relabel_label;
IGRAPH_CHECK(igraph_vector_int_init(&relabel_label, no_of_nodes));
igraph_vector_int_fill(&relabel_label, -1);
IGRAPH_FINALLY(igraph_vector_int_destroy, &relabel_label);
j = 0;
unlabelled_left = false;
for (i = 0; i < no_of_nodes; i++) {
k = VECTOR(*membership)[i];
if (k >= 0) {
if (VECTOR(relabel_label)[k] == -1) {
/* We have seen this label for the first time */
VECTOR(relabel_label)[k] = j;
k = j;
j++;
} else {
k = VECTOR(relabel_label)[k];
}
} else {
/* This is an unlabeled vertex */
unlabelled_left = true;
}
VECTOR(*membership)[i] = k;
}
/* If any nodes are left unlabelled, we assign the remaining labels to them,
* as well as to all unlabelled nodes reachable from them.
*
* Note that only those nodes could remain unlabelled which were unreachable
* from any labelled ones. Thus, in the undirected case, fully unlabelled
* connected components remain unlabelled. Here we label each such component
* with the same label.
*/
if (unlabelled_left) {
igraph_dqueue_int_t q;
igraph_vector_int_t neis;
igraph_vector_int_t node_order;
/* Initialize node ordering vector with only the not fixed nodes */
if (fixed) {
no_of_not_fixed_nodes = 0;
IGRAPH_VECTOR_INT_INIT_FINALLY(&node_order, no_of_nodes);
for (i = 0; i < no_of_nodes; i++) {
if (!VECTOR(*fixed)[i]) {
VECTOR(node_order)[no_of_not_fixed_nodes] = i;
no_of_not_fixed_nodes++;
}
}
IGRAPH_CHECK(igraph_vector_int_resize(&node_order, no_of_not_fixed_nodes));
} else {
IGRAPH_CHECK(igraph_vector_int_init_range(&node_order, 0, no_of_nodes));
IGRAPH_FINALLY(igraph_vector_int_destroy, &node_order);
no_of_not_fixed_nodes = no_of_nodes;
}
/* Shuffle the node ordering vector */
igraph_vector_int_shuffle(&node_order);
IGRAPH_VECTOR_INT_INIT_FINALLY(&neis, 0);
IGRAPH_CHECK(igraph_dqueue_int_init(&q, 0));
IGRAPH_FINALLY(igraph_dqueue_int_destroy, &q);
for (i=0; i < no_of_not_fixed_nodes; ++i) {
igraph_int_t v = VECTOR(node_order)[i];
/* Is this node unlabelled? */
if (IS_UNLABELLED(v)) {
/* If yes, we label it, and do a BFS to apply the same label
* to all other unlabelled nodes reachable from it */
IGRAPH_CHECK(igraph_dqueue_int_push(&q, v));
VECTOR(*membership)[v] = j;
while (!igraph_dqueue_int_empty(&q)) {
igraph_int_t ni, num_neis;
igraph_int_t actnode = igraph_dqueue_int_pop(&q);
IGRAPH_CHECK(igraph_neighbors(graph, &neis, actnode, mode, IGRAPH_LOOPS, IGRAPH_MULTIPLE));
num_neis = igraph_vector_int_size(&neis);
for (ni = 0; ni < num_neis; ++ni) {
igraph_int_t neighbor = VECTOR(neis)[ni];
if (IS_UNLABELLED(neighbor)) {
VECTOR(*membership)[neighbor] = j;
IGRAPH_CHECK(igraph_dqueue_int_push(&q, neighbor));
}
}
}
j++;
}
}
igraph_vector_int_destroy(&neis);
igraph_dqueue_int_destroy(&q);
igraph_vector_int_destroy(&node_order);
IGRAPH_FINALLY_CLEAN(3);
}
igraph_vector_int_destroy(&relabel_label);
IGRAPH_FINALLY_CLEAN(1);
if (fixed != fixed_copy) {
igraph_vector_bool_destroy(fixed_copy);
IGRAPH_FREE(fixed_copy);
IGRAPH_FINALLY_CLEAN(2);
}
return IGRAPH_SUCCESS;
}
@@ -0,0 +1,866 @@
/*
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_adjlist.h"
#include "igraph_components.h"
#include "igraph_dqueue.h"
#include "igraph_interface.h"
#include "igraph_iterators.h"
#include "igraph_random.h"
#include "igraph_structural.h"
#include "core/interruption.h"
#include <limits.h>
/**
* \section about_leading_eigenvector_methods
*
* <para>
* The function documented in these section implements the
* <quote>leading eigenvector</quote> method developed by Mark Newman and
* published in MEJ Newman: Finding community structure using the
* eigenvectors of matrices, Phys Rev E 74:036104 (2006).</para>
*
* <para>
* The heart of the method is the definition of the modularity matrix
* <code>B = A - P</code>, \c A being the adjacency matrix of the (undirected)
* network, and \c P contains the probability that certain edges are
* present according to the <quote>configuration model</quote>. In
* other words, a \c P_ij element of \c P is the probability that there is an
* edge between vertices \c i and \c j in a random network in which the
* degrees of all vertices are the same as in the input graph. See
* \ref igraph_modularity_matrix() for more details.</para>
*
* <para>
* The leading eigenvector method works by calculating the eigenvector
* of the modularity matrix for the largest positive eigenvalue and
* then separating vertices into two community based on the sign of
* the corresponding element in the eigenvector. If all elements in
* the eigenvector are of the same sign that means that the network
* has no underlying community structure.
* Check Newman's paper to understand why this is a good method for
* detecting community structure. </para>
*
* <para>
* The leading eigenvector community structure detection method is
* implemented in \ref igraph_community_leading_eigenvector(). After
* the initial split, the following splits are done in a way to
* optimize modularity regarding to the original network. Note that
* any further refinement, for example using Kernighan-Lin, as
* proposed in Section V.A of Newman (2006), is not implemented here.
* </para>
*
* <para>
* \example examples/simple/igraph_community_leading_eigenvector.c
* </para>
*/
typedef struct igraph_i_community_leading_eigenvector_data_t {
igraph_vector_int_t *idx;
igraph_vector_int_t *idx2;
igraph_adjlist_t *adjlist;
igraph_inclist_t *inclist;
igraph_vector_t *tmp;
igraph_int_t no_of_edges;
igraph_vector_int_t *mymembership;
igraph_int_t comm;
const igraph_vector_t *weights;
const igraph_t *graph;
igraph_vector_t *strength;
igraph_real_t sumweights;
} igraph_i_community_leading_eigenvector_data_t;
static igraph_error_t igraph_i_community_leading_eigenvector(
igraph_real_t *to,
const igraph_real_t *from,
int n, void *extra) {
igraph_i_community_leading_eigenvector_data_t *data = extra;
igraph_int_t size = n;
igraph_vector_int_t *idx = data->idx;
igraph_vector_int_t *idx2 = data->idx2;
igraph_vector_t *tmp = data->tmp;
igraph_adjlist_t *adjlist = data->adjlist;
igraph_real_t ktx, ktx2;
igraph_int_t no_of_edges = data->no_of_edges;
igraph_vector_int_t *mymembership = data->mymembership;
igraph_int_t comm = data->comm;
/* Ax */
for (igraph_int_t j = 0; j < size; j++) {
igraph_int_t oldid = VECTOR(*idx)[j];
igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid);
igraph_int_t nlen = igraph_vector_int_size(neis);
to[j] = 0.0;
VECTOR(*tmp)[j] = 0.0;
for (igraph_int_t k = 0; k < nlen; k++) {
igraph_int_t nei = VECTOR(*neis)[k];
igraph_int_t neimemb = VECTOR(*mymembership)[nei];
if (neimemb == comm) {
to[j] += from[ VECTOR(*idx2)[nei] ];
VECTOR(*tmp)[j] += 1;
}
}
}
/* Now calculate k^Tx/2m */
ktx = 0.0; ktx2 = 0.0;
for (igraph_int_t j = 0; j < size; j++) {
igraph_int_t oldid = VECTOR(*idx)[j];
igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid);
igraph_int_t degree = igraph_vector_int_size(neis);
ktx += from[j] * degree;
ktx2 += degree;
}
ktx = ktx / no_of_edges / 2.0;
ktx2 = ktx2 / no_of_edges / 2.0;
/* Now calculate Bx */
for (igraph_int_t j = 0; j < size; j++) {
igraph_int_t oldid = VECTOR(*idx)[j];
igraph_vector_int_t *neis = igraph_adjlist_get(adjlist, oldid);
igraph_real_t degree = igraph_vector_int_size(neis);
to[j] = to[j] - ktx * degree;
VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * degree;
}
/* -d_ij summa l in G B_il */
for (igraph_int_t j = 0; j < size; j++) {
to[j] -= VECTOR(*tmp)[j] * from[j];
}
return IGRAPH_SUCCESS;
}
static igraph_error_t igraph_i_community_leading_eigenvector_weighted(
igraph_real_t *to,
const igraph_real_t *from,
int n, void *extra) {
igraph_i_community_leading_eigenvector_data_t *data = extra;
igraph_int_t size = n;
igraph_vector_int_t *idx = data->idx;
igraph_vector_int_t *idx2 = data->idx2;
igraph_vector_t *tmp = data->tmp;
igraph_inclist_t *inclist = data->inclist;
igraph_real_t ktx, ktx2;
igraph_vector_int_t *mymembership = data->mymembership;
igraph_int_t comm = data->comm;
const igraph_vector_t *weights = data->weights;
const igraph_t *graph = data->graph;
igraph_vector_t *strength = data->strength;
igraph_real_t sw = data->sumweights;
/* Ax */
for (igraph_int_t j = 0; j < size; j++) {
igraph_int_t oldid = VECTOR(*idx)[j];
igraph_vector_int_t *inc = igraph_inclist_get(inclist, oldid);
igraph_int_t nlen = igraph_vector_int_size(inc);
to[j] = 0.0;
VECTOR(*tmp)[j] = 0.0;
for (igraph_int_t k = 0; k < nlen; k++) {
igraph_int_t edge = VECTOR(*inc)[k];
igraph_real_t w = VECTOR(*weights)[edge];
igraph_int_t nei = IGRAPH_OTHER(graph, edge, oldid);
igraph_int_t neimemb = VECTOR(*mymembership)[nei];
if (neimemb == comm) {
to[j] += from[ VECTOR(*idx2)[nei] ] * w;
VECTOR(*tmp)[j] += w;
}
}
}
/* k^Tx/2m */
ktx = 0.0; ktx2 = 0.0;
for (igraph_int_t j = 0; j < size; j++) {
igraph_int_t oldid = VECTOR(*idx)[j];
igraph_real_t str = VECTOR(*strength)[oldid];
ktx += from[j] * str;
ktx2 += str;
}
ktx = ktx / sw / 2.0;
ktx2 = ktx2 / sw / 2.0;
/* Bx */
for (igraph_int_t j = 0; j < size; j++) {
igraph_int_t oldid = VECTOR(*idx)[j];
igraph_real_t str = VECTOR(*strength)[oldid];
to[j] = to[j] - ktx * str;
VECTOR(*tmp)[j] = VECTOR(*tmp)[j] - ktx2 * str;
}
/* -d_ij summa l in G B_il */
for (igraph_int_t j = 0; j < size; j++) {
to[j] -= VECTOR(*tmp)[j] * from[j];
}
return IGRAPH_SUCCESS;
}
static void igraph_i_error_handler_none(const char *reason, const char *file,
int line, igraph_error_t igraph_errno) {
IGRAPH_UNUSED(reason);
IGRAPH_UNUSED(file);
IGRAPH_UNUSED(line);
IGRAPH_UNUSED(igraph_errno);
/* do nothing */
}
/**
* \ingroup communities
* \function igraph_community_leading_eigenvector
* \brief Leading eigenvector community finding (proper version).
*
* Newman's leading eigenvector method for detecting community
* structure. This is the proper implementation of the recursive,
* divisive algorithm: each split is done by maximizing the modularity
* regarding the original network, see MEJ Newman: Finding community
* structure in networks using the eigenvectors of matrices,
* Phys Rev E 74:036104 (2006).
* https://doi.org/10.1103/PhysRevE.74.036104
*
* \param graph The input graph. Edge directions will be ignored.
* \param weights The weights of the edges, or \c NULL for unweighted graphs.
* \param merges The result of the algorithm, a matrix containing the
* information about the splits performed. The matrix is built in
* the opposite way however, it is like the result of an
* agglomerative algorithm. Unlike with most other hierarchical
* community detection functions in igraph, the integers in this matrix
* represent community indices, not vertex indices. If at the end of
* the algorithm (after \p steps steps was done) there are <quote>p</quote>
* communities, then these are numbered from zero to <code>p-1</code>.
* The first line of the matrix contains the first <quote>merge</quote>
* (which is in reality the last split) of two communities into
* community <code>p</code>, the merge in the second line forms
* community <code>p+1</code>, etc. The matrix should be
* initialized before calling and will be resized as needed.
* This argument is ignored if it is \c NULL.
* \param membership The membership of the vertices after all the
* splits were performed will be stored here. The vector must be
* initialized before calling and will be resized as needed.
* This argument is ignored if it is \c NULL. This argument can
* also be used to supply a starting configuration for the community
* finding, in the format of a membership vector. In this case the
* \p start argument must be set to \c true.
* \param steps The maximum number of steps to perform. It might
* happen that some component (or the whole network) has no
* underlying community structure and no further steps can be
* done. If you want as many steps as possible then supply the
* number of vertices in the network here.
* \param options The options for ARPACK. Supply \c NULL here to use the
* defaults. \c n is always overwritten. \c ncv is set to at least 4.
* \param modularity If not a null pointer, then it must be a pointer
* to a real number and the modularity score of the final division
* is stored here.
* \param start Boolean, whether to use the community structure given
* in the \p membership argument as a starting point.
* \param eigenvalues Pointer to an initialized vector or a null
* pointer. If not a null pointer, then the eigenvalues calculated
* along the community structure detection are stored here. The
* non-positive eigenvalues, that do not result a split, are stored
* as well.
* \param eigenvectors If not a null pointer, then the eigenvectors
* that are calculated in each step of the algorithm are stored here,
* in a list of vectors. Each eigenvector is stored in an
* \ref igraph_vector_t object.
* \param history Pointer to an initialized vector or a null pointer.
* If not a null pointer, then a trace of the algorithm is stored
* here, encoded numerically. The various operations:
* \clist
* \cli IGRAPH_LEVC_HIST_START_FULL
* Start the algorithm from an initial state where each connected
* component is a separate community.
* \cli IGRAPH_LEVC_HIST_START_GIVEN
* Start the algorithm from a given community structure. The next
* value in the vector contains the initial number of
* communities.
* \cli IGRAPH_LEVC_HIST_SPLIT
* Split a community into two communities. The id of the splitted
* community is given in the next element of the history vector.
* The id of the first new community is the same as the id of the
* splitted community. The id of the second community equals to
* the number of communities before the split.
* \cli IGRAPH_LEVC_HIST_FAILED
* Tried to split a community, but it was not worth it, as it
* does not result in a bigger modularity value. The id of the
* community is given in the next element of the vector.
* \endclist
* \param callback A null pointer or a function of type \ref
* igraph_community_leading_eigenvector_callback_t. If given, this
* callback function is called after each eigenvector/eigenvalue
* calculation. If the callback returns \c IGRAPH_STOP, then the
* community finding algorithm stops. If it returns \c IGRAPH_SUCCESS,
* the algorithm continues normally. Any other return value is considered
* an igraph error code and will terminete the algorithm with the same
* error code. See the arguments passed to the callback at the documentation
* of \ref igraph_community_leading_eigenvector_callback_t.
* \param callback_extra Extra argument to pass to the callback
* function.
* \return Error code.
*
* \sa \ref igraph_community_walktrap() and \ref
* igraph_community_spinglass() for other community structure
* detection methods.
*
* Time complexity: O(|E|+|V|^2*steps), |V| is the number of vertices,
* |E| the number of edges, <quote>steps</quote> the number of splits
* performed.
*/
igraph_error_t igraph_community_leading_eigenvector(
const igraph_t *graph,
const igraph_vector_t *weights,
igraph_matrix_int_t *merges,
igraph_vector_int_t *membership,
igraph_int_t steps,
igraph_arpack_options_t *options,
igraph_real_t *modularity,
igraph_bool_t start,
igraph_vector_t *eigenvalues,
igraph_vector_list_t *eigenvectors,
igraph_vector_int_t *history,
igraph_community_leading_eigenvector_callback_t *callback,
void *callback_extra) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t no_of_edges = igraph_ecount(graph);
igraph_dqueue_int_t tosplit;
igraph_vector_int_t idx, idx2;
igraph_vector_t mymerges;
igraph_vector_t strength, tmp;
igraph_vector_t start_vec;
igraph_int_t staken = 0;
igraph_adjlist_t adjlist;
igraph_inclist_t inclist;
igraph_int_t i, j, k, l;
igraph_int_t communities;
igraph_vector_int_t vmembership, *mymembership = membership;
igraph_i_community_leading_eigenvector_data_t extra;
igraph_arpack_storage_t storage;
igraph_real_t mod = 0;
igraph_arpack_function_t *arpcb1 =
weights ? igraph_i_community_leading_eigenvector_weighted :
igraph_i_community_leading_eigenvector;
igraph_real_t sumweights = 0.0;
if (no_of_nodes > INT_MAX) {
IGRAPH_ERROR("Graph too large for ARPACK.", IGRAPH_EOVERFLOW);
}
if (weights && no_of_edges != igraph_vector_size(weights)) {
IGRAPH_ERROR("Weight vector length does not match number of edges.", IGRAPH_EINVAL);
}
if (start && !membership) {
IGRAPH_ERROR("Cannot start from given configuration if memberships missing.", IGRAPH_EINVAL);
}
if (start && membership &&
igraph_vector_int_size(membership) != no_of_nodes) {
IGRAPH_ERROR("Supplied membership vector length does not match number of vertices.",
IGRAPH_EINVAL);
}
if (start && membership && igraph_vector_int_max(membership) >= no_of_nodes) {
IGRAPH_WARNING("Too many communities in membership start vector.");
}
if (igraph_is_directed(graph)) {
IGRAPH_WARNING("Directed graph supplied, edge directions will be ignored.");
}
if (steps < 0 || steps > no_of_nodes - 1) {
steps = no_of_nodes > 0 ? no_of_nodes - 1 : 0;
}
if (!membership) {
mymembership = &vmembership;
IGRAPH_VECTOR_INT_INIT_FINALLY(mymembership, 0);
}
IGRAPH_VECTOR_INIT_FINALLY(&mymerges, 0);
IGRAPH_CHECK(igraph_vector_reserve(&mymerges, steps * 2));
IGRAPH_VECTOR_INT_INIT_FINALLY(&idx, 0);
if (eigenvalues) {
igraph_vector_clear(eigenvalues);
}
if (eigenvectors) {
igraph_vector_list_clear(eigenvectors);
}
if (!start) {
/* Calculate the weakly connected components in the graph and use them as
* an initial split */
IGRAPH_CHECK(igraph_connected_components(graph, mymembership, &idx, NULL, IGRAPH_WEAK));
communities = igraph_vector_int_size(&idx);
if (history) {
IGRAPH_CHECK(igraph_vector_int_push_back(history,
IGRAPH_LEVC_HIST_START_FULL));
}
} else {
/* Just create the idx vector for the given membership vector */
communities = igraph_vector_int_max(mymembership) + 1;
if (history) {
IGRAPH_CHECK(igraph_vector_int_push_back(history,
IGRAPH_LEVC_HIST_START_GIVEN));
IGRAPH_CHECK(igraph_vector_int_push_back(history, communities));
}
IGRAPH_CHECK(igraph_vector_int_resize(&idx, communities));
igraph_vector_int_null(&idx);
for (i = 0; i < no_of_nodes; i++) {
igraph_int_t t = VECTOR(*mymembership)[i];
VECTOR(idx)[t] += 1;
}
}
IGRAPH_DQUEUE_INT_INIT_FINALLY(&tosplit, 100);
for (i = 0; i < communities; i++) {
if (VECTOR(idx)[i] > 2) {
IGRAPH_CHECK(igraph_dqueue_int_push(&tosplit, i));
}
}
for (i = 1; i < communities; i++) {
/* Record merge */
IGRAPH_CHECK(igraph_vector_push_back(&mymerges, i - 1));
IGRAPH_CHECK(igraph_vector_push_back(&mymerges, i));
if (eigenvalues) {
IGRAPH_CHECK(igraph_vector_push_back(eigenvalues, IGRAPH_NAN));
}
if (eigenvectors) {
/* There are no eigenvectors associated to these steps because the
* splits were given by the user (or by the components of the graph)
* so we push empty vectors */
IGRAPH_CHECK(igraph_vector_list_push_back_new(eigenvectors, NULL));
}
if (history) {
IGRAPH_CHECK(igraph_vector_int_push_back(history, IGRAPH_LEVC_HIST_SPLIT));
IGRAPH_CHECK(igraph_vector_int_push_back(history, i - 1));
}
}
staken = communities - 1;
IGRAPH_VECTOR_INIT_FINALLY(&tmp, no_of_nodes);
IGRAPH_CHECK(igraph_vector_int_resize(&idx, no_of_nodes));
igraph_vector_int_null(&idx);
IGRAPH_VECTOR_INT_INIT_FINALLY(&idx2, no_of_nodes);
if (!weights) {
IGRAPH_CHECK(igraph_adjlist_init(graph, &adjlist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE, IGRAPH_MULTIPLE));
IGRAPH_FINALLY(igraph_adjlist_destroy, &adjlist);
} else {
IGRAPH_CHECK(igraph_inclist_init(graph, &inclist, IGRAPH_ALL, IGRAPH_LOOPS_TWICE));
IGRAPH_FINALLY(igraph_inclist_destroy, &inclist);
IGRAPH_VECTOR_INIT_FINALLY(&strength, no_of_nodes);
IGRAPH_CHECK(igraph_strength(graph, &strength, igraph_vss_all(),
IGRAPH_ALL, IGRAPH_LOOPS, weights));
sumweights = igraph_vector_sum(weights);
}
if (options == NULL) {
options = igraph_arpack_options_get_default();
}
options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */
options->which[0] = 'L'; options->which[1] = 'A';
/* Memory for ARPACK */
/* We are allocating memory for 20 eigenvectors since options->ncv won't be
* larger than 20 when using automatic mode in igraph_arpack_rssolve */
IGRAPH_CHECK(igraph_arpack_storage_init(&storage, (int) no_of_nodes, 20,
(int) no_of_nodes, 1));
IGRAPH_FINALLY(igraph_arpack_storage_destroy, &storage);
extra.idx = &idx;
extra.idx2 = &idx2;
extra.tmp = &tmp;
extra.adjlist = &adjlist;
extra.inclist = &inclist;
extra.weights = weights;
extra.sumweights = sumweights;
extra.graph = graph;
extra.strength = &strength;
extra.no_of_edges = no_of_edges;
extra.mymembership = mymembership;
while (!igraph_dqueue_int_empty(&tosplit) && staken < steps) {
igraph_int_t comm = igraph_dqueue_int_pop_back(&tosplit);
/* depth first search */
igraph_int_t size = 0;
IGRAPH_ALLOW_INTERRUPTION();
for (i = 0; i < no_of_nodes; i++) {
if (VECTOR(*mymembership)[i] == comm) {
VECTOR(idx)[size] = i;
VECTOR(idx2)[i] = size++;
}
}
staken++;
if (size <= 2) {
continue;
}
options->n = (int) size;
options->info = 0;
options->nev = 1;
options->ldv = 0;
options->ncv = 0; /* 0 means "automatic" in igraph_arpack_rssolve */
options->nconv = 0;
options->lworkl = 0; /* we surely have enough space */
extra.comm = comm;
/* Use a random start vector, but don't let ARPACK generate the
* start vector -- we want to use our own RNG. Also, we want to generate
* values close to +1 and -1 as this is what the eigenvector should
* look like if there _is_ some kind of a community structure at this
* step to discover. Experiments showed that shuffling a vector
* containing equal number of slightly perturbed +/-1 values yields
* convergence in most cases. */
options->start = 1;
options->mxiter = options->mxiter > 10000 ? options->mxiter : 10000; /* use more iterations, we've had convergence problems with 3000 */
for (i = 0; i < options->n; i++) {
storage.resid[i] = (i % 2 ? 1 : -1) + RNG_UNIF(-0.1, 0.1);
}
start_vec = igraph_vector_view(storage.resid, options->n);
igraph_vector_shuffle(&start_vec);
{
igraph_error_t retval;
igraph_error_handler_t *errh =
igraph_set_error_handler(igraph_i_error_handler_none);
retval = igraph_arpack_rssolve(arpcb1, &extra, options, &storage, /*values=*/ NULL, /*vectors=*/ NULL);
igraph_set_error_handler(errh);
if (retval == IGRAPH_EARPACK) {
/* TODO(ntamas): get last ARPACK error code. Some errors are OK. */
igraph_arpack_error_t arpack_error = igraph_arpack_get_last_error();
if (arpack_error != IGRAPH_ARPACK_MAXIT && arpack_error != IGRAPH_ARPACK_NOSHIFT) {
IGRAPH_ERROR(igraph_arpack_error_to_string(arpack_error), IGRAPH_EARPACK);
}
} else if (retval != IGRAPH_SUCCESS) {
IGRAPH_ERROR("Leading eigenvector calculation failed.", retval);
}
}
if (options->nconv < 1) {
IGRAPH_ERROR(igraph_arpack_error_to_string(IGRAPH_ARPACK_FAILED), IGRAPH_EARPACK);
}
/* Ok, we have the leading eigenvector of the modularity matrix */
/* ---------------------------------------------------------------*/
/* To avoid numeric errors */
if (fabs(storage.d[0]) < 1e-8) {
storage.d[0] = 0;
}
/* We replace very small (in absolute value) elements of the
leading eigenvector with zero, to get the same result,
consistently.*/
for (i = 0; i < size; i++) {
if (fabs(storage.v[i]) < 1e-8) {
storage.v[i] = 0;
}
}
/* Just to have the always the same result, we multiply by -1
if the first (nonzero) element is not positive. */
for (i = 0; i < size; i++) {
if (storage.v[i] != 0) {
break;
}
}
if (i < size && storage.v[i] < 0) {
for (i = 0; i < size; i++) {
storage.v[i] = - storage.v[i];
}
}
/* ---------------------------------------------------------------*/
if (callback) {
const igraph_vector_t vv = igraph_vector_view(storage.v, size);;
igraph_error_t ret;
IGRAPH_CHECK_CALLBACK(
callback(
mymembership, comm, storage.d[0], &vv, arpcb1,
&extra, callback_extra
), &ret
);
if (ret == IGRAPH_STOP) {
break;
}
}
if (eigenvalues) {
IGRAPH_CHECK(igraph_vector_push_back(eigenvalues, storage.d[0]));
}
if (eigenvectors) {
igraph_vector_t *v;
/* TODO: this would be faster if we had an igraph_vector_list_push_back_new_with_size_hint */
IGRAPH_CHECK(igraph_vector_list_push_back_new(eigenvectors, &v));
IGRAPH_CHECK(igraph_vector_resize(v, size));
for (i = 0; i < size; i++) {
VECTOR(*v)[i] = storage.v[i];
}
}
if (storage.d[0] <= 0) {
if (history) {
IGRAPH_CHECK(igraph_vector_int_push_back(history,
IGRAPH_LEVC_HIST_FAILED));
IGRAPH_CHECK(igraph_vector_int_push_back(history, comm));
}
continue;
}
/* Count the number of vertices in each community after the split */
l = 0;
for (j = 0; j < size; j++) {
if (storage.v[j] < 0) {
storage.v[j] = -1;
l++;
} else {
storage.v[j] = 1;
}
}
if (l == 0 || l == size) {
if (history) {
IGRAPH_CHECK(igraph_vector_int_push_back(history,
IGRAPH_LEVC_HIST_FAILED));
IGRAPH_CHECK(igraph_vector_int_push_back(history, comm));
}
continue;
}
/* Check that Q increases with our choice of split */
arpcb1(storage.v + size, storage.v, (int) size, &extra);
mod = 0;
for (i = 0; i < size; i++) {
mod += storage.v[size + i] * storage.v[i];
}
if (mod <= 1e-8) {
if (history) {
IGRAPH_CHECK(igraph_vector_int_push_back(history,
IGRAPH_LEVC_HIST_FAILED));
IGRAPH_CHECK(igraph_vector_int_push_back(history, comm));
}
continue;
}
communities++;
/* Rewrite the mymembership vector */
for (j = 0; j < size; j++) {
if (storage.v[j] < 0) {
igraph_int_t oldid = VECTOR(idx)[j];
VECTOR(*mymembership)[oldid] = communities - 1;
}
}
/* Record merge */
IGRAPH_CHECK(igraph_vector_push_back(&mymerges, comm));
IGRAPH_CHECK(igraph_vector_push_back(&mymerges, communities - 1));
if (history) {
IGRAPH_CHECK(igraph_vector_int_push_back(history, IGRAPH_LEVC_HIST_SPLIT));
IGRAPH_CHECK(igraph_vector_int_push_back(history, comm));
}
/* Store the resulting communities in the queue if needed */
if (l > 1) {
IGRAPH_CHECK(igraph_dqueue_int_push(&tosplit, communities - 1));
}
if (size - l > 1) {
IGRAPH_CHECK(igraph_dqueue_int_push(&tosplit, comm));
}
}
igraph_arpack_storage_destroy(&storage);
IGRAPH_FINALLY_CLEAN(1);
if (!weights) {
igraph_adjlist_destroy(&adjlist);
IGRAPH_FINALLY_CLEAN(1);
} else {
igraph_inclist_destroy(&inclist);
igraph_vector_destroy(&strength);
IGRAPH_FINALLY_CLEAN(2);
}
igraph_dqueue_int_destroy(&tosplit);
igraph_vector_destroy(&tmp);
igraph_vector_int_destroy(&idx2);
IGRAPH_FINALLY_CLEAN(3);
/* reform the mymerges vector */
if (merges) {
igraph_vector_int_null(&idx);
l = igraph_vector_size(&mymerges);
k = communities;
j = 0;
IGRAPH_CHECK(igraph_matrix_int_resize(merges, l / 2, 2));
for (i = l; i > 0; i -= 2) {
igraph_int_t from = VECTOR(mymerges)[i - 1];
igraph_int_t to = VECTOR(mymerges)[i - 2];
MATRIX(*merges, j, 0) = VECTOR(mymerges)[i - 2];
MATRIX(*merges, j, 1) = VECTOR(mymerges)[i - 1];
if (VECTOR(idx)[from] != 0) {
MATRIX(*merges, j, 1) = VECTOR(idx)[from] - 1;
}
if (VECTOR(idx)[to] != 0) {
MATRIX(*merges, j, 0) = VECTOR(idx)[to] - 1;
}
VECTOR(idx)[to] = ++k;
j++;
}
}
igraph_vector_int_destroy(&idx);
igraph_vector_destroy(&mymerges);
IGRAPH_FINALLY_CLEAN(2);
if (modularity) {
IGRAPH_CHECK(igraph_modularity(graph, mymembership, weights,
/* resolution */ 1,
IGRAPH_UNDIRECTED, modularity));
}
if (!membership) {
igraph_vector_int_destroy(mymembership);
IGRAPH_FINALLY_CLEAN(1);
}
return IGRAPH_SUCCESS;
}
/**
* \function igraph_le_community_to_membership
* \brief Cut an incomplete dendrogram after a given number of merges, starting with an initial cluster assignment.
*
* This function takes a dendrogram whose leaves are cluster IDs given in an
* initial cluster assignment provided in \p membership. Then it updates
* the cluster assignment by performing the specified number of mergers,
* as given by the dendrogram encoded in \p merges. It is a more general
* version of \ref igraph_community_to_membership(), which assumes that
* the dendrogram leaves are singleton clusters corresponding to individual
* vertices.
*
* </para><para>
* This dendrogram format is suitable for divise hierarchical community
* detection algorithms that stop before dividing the graph into individual
* vertices, such as \ref igraph_community_leading_eigenvector().
*
* </para><para>
* Initially, \p membership is expected to contain \c m contiguous cluster
* indices, numbered from zero. These correspond to the leaf nodes of the
* dendrogram. Row \c i of the two-column \p merges matrix contains the IDs of
* clusters that are merged together into dendrogram node <code>m + i</code>.
* It may have up to <code>m - 1</code> rows.
*
* </para><para>
* This function performs \p steps merge operations as prescribed by the
* \p merges matrix and updates \p membership to the resulting partitioning
* into <code>m - steps</code> communities.
*
* \param merges The two-column matrix containing the merge operations.
* See \ref igraph_community_leading_eigenvector() for the
* detailed syntax. This is usually from the output of the
* leading eigenvector community structure detection routines.
* \param steps The number of steps to make according to \c merges.
* \param membership Initially the starting membership vector,
* on output the resulting membership vector, after performing \c steps merges.
* \param csize Optionally the sizes of the communities are stored here,
* if this is not a null pointer, but an initialized vector.
* \return Error code.
*
* \sa \ref igraph_community_to_membership() for a simpler interface that
* starts by merging individual vertices.
*
* Time complexity: O(|V|), the number of vertices.
*/
igraph_error_t igraph_le_community_to_membership(const igraph_matrix_int_t *merges,
igraph_int_t steps,
igraph_vector_int_t *membership,
igraph_vector_int_t *csize) {
igraph_int_t no_of_nodes = igraph_vector_int_size(membership);
igraph_vector_int_t fake_memb;
igraph_int_t components, i;
if (no_of_nodes > 0) {
components = igraph_vector_int_max(membership) + 1;
} else {
components = 0;
}
if (components > no_of_nodes) {
IGRAPH_ERRORF("Invalid membership vector: number of components (%" IGRAPH_PRId ") must "
"not be greater than the number of nodes (%" IGRAPH_PRId ").",
IGRAPH_EINVAL, components, no_of_nodes);
}
if (steps >= components) {
IGRAPH_ERRORF("Number of steps (%" IGRAPH_PRId ") must be smaller than number of components (%" IGRAPH_PRId ").",
IGRAPH_EINVAL, steps, components);
}
IGRAPH_VECTOR_INT_INIT_FINALLY(&fake_memb, components);
/* Check membership vector */
for (i = 0; i < no_of_nodes; i++) {
if (VECTOR(*membership)[i] < 0) {
IGRAPH_ERRORF("Invalid membership vector, negative ID found: %" IGRAPH_PRId ".", IGRAPH_EINVAL, VECTOR(*membership)[i]);
}
VECTOR(fake_memb)[ VECTOR(*membership)[i] ] += 1;
}
for (i = 0; i < components; i++) {
if (VECTOR(fake_memb)[i] == 0) {
/* Ideally the empty cluster's index would be reported.
However, doing so would be confusing as some high-level interfaces
use 1-based indexing, some 0-based. */
IGRAPH_ERROR("Invalid membership vector, empty cluster found.", IGRAPH_EINVAL);
}
}
IGRAPH_CHECK(igraph_community_to_membership(merges, components, steps, &fake_memb, NULL));
/* Ok, now we have the membership of the initial components,
rewrite the original membership vector. */
if (csize) {
IGRAPH_CHECK(igraph_vector_int_resize(csize, components - steps));
igraph_vector_int_null(csize);
}
for (i = 0; i < no_of_nodes; i++) {
VECTOR(*membership)[i] = VECTOR(fake_memb)[ VECTOR(*membership)[i] ];
if (csize) {
VECTOR(*csize)[ VECTOR(*membership)[i] ] += 1;
}
}
igraph_vector_int_destroy(&fake_memb);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
File diff suppressed because it is too large Load Diff
@@ -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., &amp; 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;
}
@@ -0,0 +1,393 @@
/*
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_interface.h"
#include "igraph_structural.h"
#include "community/community_internal.h"
/**
* \function igraph_modularity
* \brief Calculates the modularity of a graph with respect to some clusters or vertex types.
*
* The modularity of a graph with respect to some clustering of the vertices
* (or assignment of vertex types)
* measures how strongly separated the different clusters are from each
* other compared to a random null model. It is defined as
*
* </para><para>
* <code>Q = 1/(2m) sum_ij (A_ij - γ k_i k_j / (2m)) δ(c_i,c_j)</code>,
*
* </para><para>
* where \c m is the number of edges, <code>A_ij</code> is the adjacency matrix,
* \c k_i is the degree of vertex \c i, \c c_i is the cluster that vertex \c i belongs to
* (or its vertex type), <code>δ(i,j)=1</code> if <code>i=j</code> and 0 otherwise,
* and the sum goes over all \c i, \c j pairs of vertices. Note that in this formula,
* the diagonal of the adjacency matrix contains twice the number of self-loops.
*
* </para><para>
* The resolution parameter \c γ allows weighting the random null model, which
* might be useful when finding partitions with a high modularity. Maximizing modularity
* with higher values of the resolution parameter typically results in more, smaller clusters
* when finding partitions with a high modularity. Lower values typically results in
* fewer, larger clusters. The original definition of modularity is retrieved
* when setting <code>γ = 1</code>.
*
* </para><para>
* Modularity can also be calculated on directed graphs. This only requires a relatively
* modest change,
*
* </para><para>
* <code>Q = 1/m sum_ij (A_ij - γ k^out_i k^in_j / m) δ(c_i,c_j)</code>,
*
* </para><para>
* where \c k^out_i is the out-degree of node \c i and \c k^in_j is the in-degree of node \c j.
*
* </para><para>
* Modularity on weighted graphs is also meaningful. When taking
* edge weights into account, \c A_ij equals the weight of the corresponding edge
* (or 0 if there is no edge), \c k_i is the strength (i.e. the weighted degree) of
* vertex \c i, with similar counterparts for a directed graph, and \c m is the total
* weight of all edges.
*
* </para><para>
* Note that the modularity is not well-defined for graphs with no edges.
* igraph returns \c NaN for graphs with no edges; see
* https://github.com/igraph/igraph/issues/1539 for
* a detailed discussion.
*
* </para><para>
* For the original definition of modularity, see Newman, M. E. J., and Girvan, M.
* (2004). Finding and evaluating community structure in networks.
* Physical Review E 69, 026113. https://doi.org/10.1103/PhysRevE.69.026113
*
* </para><para>
* For the directed definition of modularity, see Leicht, E. A., and Newman, M. E.
* J. (2008). Community Structure in Directed Networks. Physical Review Letters 100,
* 118703. https://doi.org/10.1103/PhysRevLett.100.118703
*
* </para><para>
* For the introduction of the resolution parameter \c γ, see Reichardt, J., and
* Bornholdt, S. (2006). Statistical mechanics of community detection. Physical
* Review E 74, 016110. https://doi.org/10.1103/PhysRevE.74.016110
*
* \param graph The input graph.
* \param membership Numeric vector of integer values which gives the type of each
* vertex, i.e. the cluster to which it belongs.
* It does not have to be consecutive, i.e. empty communities
* are allowed. For better performance, ensure that community
* indices are nonnegative and smaller than the vertex count.
* This can be ensured using \ref igraph_reindex_membership().
* \param weights Weight vector or \c NULL if no weights are specified.
* \param resolution The resolution parameter \c γ. Must not be negative.
* Set it to 1 to use the classical definition of modularity.
* \param directed Whether to use the directed or undirected version of modularity.
* Ignored for undirected graphs.
* \param modularity Pointer to a real number, the result will be
* stored here.
* \return Error code.
*
* \sa \ref igraph_modularity_matrix()
*
* Time complexity: O(|V|+|E|), the number of vertices plus the number
* of edges, assuming that community indices are nonnegative and smaller
* than the vertex count. Otherwise, O(|V| log |V| + |E|).
*/
igraph_error_t igraph_modularity(const igraph_t *graph,
const igraph_vector_int_t *membership,
const igraph_vector_t *weights,
const igraph_real_t resolution,
const igraph_bool_t directed,
igraph_real_t *modularity) {
const igraph_int_t vcount = igraph_vcount(graph);
const igraph_int_t ecount = igraph_ecount(graph);
const igraph_vector_int_t *p_membership;
igraph_vector_int_t i_membership;
igraph_bool_t using_i_membership = false;
igraph_vector_t k_out, k_in;
igraph_int_t min_cluster_id, max_cluster_id, no_of_partitions;
igraph_real_t e; /* count/fraction of edges/weights within partitions */
igraph_real_t m; /* edge count / weight sum */
igraph_int_t c1, c2;
/* Only consider the graph as directed if it actually is directed */
igraph_bool_t use_directed = directed && igraph_is_directed(graph);
igraph_real_t directed_multiplier = (use_directed ? 1 : 2);
if (igraph_vector_int_size(membership) != vcount) {
IGRAPH_ERROR("Membership vector size differs from number of vertices.",
IGRAPH_EINVAL);
}
if (resolution < 0.0) {
IGRAPH_ERROR("The resolution parameter must not be negative.", IGRAPH_EINVAL);
}
if (ecount == 0) {
/* Special case: the modularity of graphs with no edges is not
* well-defined */
*modularity = IGRAPH_NAN;
return IGRAPH_SUCCESS;
}
/* At this point, the 'membership' vector does not have length zero,
thus it is safe to call igraph_vector_int_minmax(). */
/* If community indices are outside of the standard range, automatically
* reindex them. */
igraph_vector_int_minmax(membership, &min_cluster_id, &max_cluster_id);
if (min_cluster_id < 0 || max_cluster_id >= vcount) {
IGRAPH_CHECK(igraph_vector_int_init_copy(&i_membership, membership));
IGRAPH_FINALLY(igraph_vector_int_destroy, &i_membership);
IGRAPH_CHECK(igraph_i_reindex_membership_large(&i_membership, NULL, &no_of_partitions));
p_membership = &i_membership;
using_i_membership = true;
} else {
no_of_partitions = max_cluster_id + 1;
p_membership = membership;
}
IGRAPH_VECTOR_INIT_FINALLY(&k_out, no_of_partitions);
IGRAPH_VECTOR_INIT_FINALLY(&k_in, no_of_partitions);
e = 0.0;
if (weights) {
if (igraph_vector_size(weights) != ecount)
IGRAPH_ERROR("Weight vector size differs from number of edges.",
IGRAPH_EINVAL);
m = 0.0;
for (igraph_int_t i = 0; i < ecount; i++) {
igraph_real_t w = VECTOR(*weights)[i];
if (w < 0) {
IGRAPH_ERROR("Negative weight in weight vector.", IGRAPH_EINVAL);
}
c1 = VECTOR(*p_membership)[ IGRAPH_FROM(graph, i) ];
c2 = VECTOR(*p_membership)[ IGRAPH_TO(graph, i) ];
if (c1 == c2) {
e += directed_multiplier * w;
}
VECTOR(k_out)[c1] += w;
VECTOR(k_in)[c2] += w;
m += w;
}
} else {
m = ecount;
for (igraph_int_t i = 0; i < ecount; i++) {
c1 = VECTOR(*p_membership)[ IGRAPH_FROM(graph, i) ];
c2 = VECTOR(*p_membership)[ IGRAPH_TO(graph, i) ];
if (c1 == c2) {
e += directed_multiplier;
}
VECTOR(k_out)[c1] += 1;
VECTOR(k_in)[c2] += 1;
}
}
if (!use_directed) {
/* Graph is undirected, simply add vectors */
igraph_vector_add(&k_out, &k_in);
igraph_vector_update(&k_in, &k_out);
}
/* Divide all vectors by total weight. */
igraph_vector_scale(&k_out, 1.0/( directed_multiplier * m ) );
igraph_vector_scale(&k_in, 1.0/( directed_multiplier * m ) );
e /= directed_multiplier * m;
if (m > 0) {
*modularity = e;
for (igraph_int_t i = 0; i < no_of_partitions; i++) {
*modularity -= resolution * VECTOR(k_out)[i] * VECTOR(k_in)[i];
}
} else {
*modularity = IGRAPH_NAN;
}
igraph_vector_destroy(&k_out);
igraph_vector_destroy(&k_in);
IGRAPH_FINALLY_CLEAN(2);
if (using_i_membership) {
igraph_vector_int_destroy(&i_membership);
IGRAPH_FINALLY_CLEAN(1);
}
return IGRAPH_SUCCESS;
}
static igraph_error_t igraph_i_modularity_matrix_get_adjacency(
const igraph_t *graph, igraph_matrix_t *res,
const igraph_vector_t *weights, igraph_bool_t directed) {
/* Specifically used to handle weights and/or ignore direction */
igraph_eit_t edgeit;
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t from, to;
IGRAPH_CHECK(igraph_matrix_resize(res, no_of_nodes, no_of_nodes));
igraph_matrix_null(res);
IGRAPH_CHECK(igraph_eit_create(graph, igraph_ess_all(IGRAPH_EDGEORDER_ID), &edgeit));
IGRAPH_FINALLY(igraph_eit_destroy, &edgeit);
if (weights) {
for (; !IGRAPH_EIT_END(edgeit); IGRAPH_EIT_NEXT(edgeit)) {
igraph_int_t edge = IGRAPH_EIT_GET(edgeit);
from = IGRAPH_FROM(graph, edge);
to = IGRAPH_TO(graph, edge);
MATRIX(*res, from, to) += VECTOR(*weights)[edge];
if (!directed) {
MATRIX(*res, to, from) += VECTOR(*weights)[edge];
}
}
} else {
for (; !IGRAPH_EIT_END(edgeit); IGRAPH_EIT_NEXT(edgeit)) {
igraph_int_t edge = IGRAPH_EIT_GET(edgeit);
from = IGRAPH_FROM(graph, edge);
to = IGRAPH_TO(graph, edge);
MATRIX(*res, from, to) += 1;
if (!directed) {
MATRIX(*res, to, from) += 1;
}
}
}
igraph_eit_destroy(&edgeit);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
/**
* \function igraph_modularity_matrix
* \brief Calculates the modularity matrix.
*
* This function returns the modularity matrix, which is defined as
*
* </para><para>
* <code>B_ij = A_ij - γ k_i k_j / (2m)</code>
*
* </para><para>
* for undirected graphs, where \c A_ij is the adjacency matrix, \c γ is the
* resolution parameter, \c k_i is the degree of vertex \c i, and \c m is the
* number of edges in the graph. When there are no edges, or the weights add up
* to zero, the result is undefined.
*
* </para><para>
* For directed graphs the modularity matrix is changed to
*
* </para><para>
* <code>B_ij = A_ij - γ k^out_i k^in_j / m</code>
*
* </para><para>
* where <code>k^out_i</code> is the out-degree of node \c i and <code>k^in_j</code> is the
* in-degree of node \c j.
*
* </para><para>
* Note that self-loops in undirected graphs are multiplied by 2 in this
* implementation. If weights are specified, the weighted counterparts of the adjacency
* matrix and degrees are used.
*
* \param graph The input graph.
* \param weights Edge weights, pointer to a vector. If this is a null pointer
* then every edge is assumed to have a weight of 1.
* \param resolution The resolution parameter \c γ. Must not be negative.
* Default is 1. Lower values favor fewer, larger communities;
* higher values favor more, smaller communities.
* \param modmat Pointer to an initialized matrix in which the modularity
* matrix is stored.
* \param directed For directed graphs: if the edges should be treated as
* undirected. For undirected graphs this is ignored.
* \return Error code.
*
* \sa \ref igraph_modularity()
*/
igraph_error_t igraph_modularity_matrix(const igraph_t *graph,
const igraph_vector_t *weights,
const igraph_real_t resolution,
igraph_matrix_t *modmat,
igraph_bool_t directed) {
const igraph_int_t no_of_nodes = igraph_vcount(graph);
const igraph_int_t no_of_edges = igraph_ecount(graph);
const igraph_real_t sw = weights ? igraph_vector_sum(weights) : no_of_edges;
igraph_vector_t deg, in_deg, out_deg;
igraph_real_t scaling_factor;
if (weights && igraph_vector_size(weights) != no_of_edges) {
IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL);
}
if (resolution < 0.0) {
IGRAPH_ERROR("The resolution parameter must not be negative.", IGRAPH_EINVAL);
}
if (!igraph_is_directed(graph)) {
directed = false;
}
IGRAPH_CHECK(igraph_i_modularity_matrix_get_adjacency(graph, modmat, weights, directed));
/* Performance notes:
* - Iterating in column-major order makes a large difference.
* - Applying the scaling_factor to in_deg (or out_deg) first to reduce the
* number of multiplications does not make an appreciable performance
* difference. However, doing this in the undirected case causes the result
* matrix to sometimes not be strictly symmetric due to the non-associativity
* of floating point multiplication.
*/
if (directed) {
IGRAPH_VECTOR_INIT_FINALLY(&in_deg, no_of_nodes);
IGRAPH_VECTOR_INIT_FINALLY(&out_deg, no_of_nodes);
IGRAPH_CHECK(igraph_strength(graph, &in_deg, igraph_vss_all(), IGRAPH_IN,
IGRAPH_LOOPS, weights));
IGRAPH_CHECK(igraph_strength(graph, &out_deg, igraph_vss_all(), IGRAPH_OUT,
IGRAPH_LOOPS, weights));
scaling_factor = resolution / sw;
for (igraph_int_t j = 0; j < no_of_nodes; j++) {
for (igraph_int_t i = 0; i < no_of_nodes; i++) {
MATRIX(*modmat, i, j) -= VECTOR(out_deg)[i] * VECTOR(in_deg)[j] * scaling_factor;
}
}
igraph_vector_destroy(&in_deg);
igraph_vector_destroy(&out_deg);
IGRAPH_FINALLY_CLEAN(2);
} else {
IGRAPH_VECTOR_INIT_FINALLY(&deg, no_of_nodes);
IGRAPH_CHECK(igraph_strength(graph, &deg, igraph_vss_all(), IGRAPH_ALL,
IGRAPH_LOOPS, weights));
scaling_factor = resolution / 2.0 / sw;
for (igraph_int_t j = 0; j < no_of_nodes; j++) {
for (igraph_int_t i = 0; i < no_of_nodes; i++) {
MATRIX(*modmat, i, j) -= VECTOR(deg)[i] * VECTOR(deg)[j] * scaling_factor;
}
}
igraph_vector_destroy(&deg);
IGRAPH_FINALLY_CLEAN(1);
}
return IGRAPH_SUCCESS;
}
@@ -0,0 +1,316 @@
/*
igraph library.
Copyright (C) 2010-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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_error.h"
#include "igraph_interface.h"
#include "igraph_structural.h"
#include "core/interruption.h"
#include "internal/glpk_support.h"
#include "math/safe_intop.h"
#include <limits.h>
/**
* \function igraph_community_optimal_modularity
* \brief Calculate the community structure with the highest modularity value.
*
* This function calculates the optimal community structure for a graph, in
* terms of maximal modularity score. Both undirected and directed graphs
* are supported.
*
* </para><para>
* The calculation is done by transforming the modularity maximization
* into an integer programming problem, and then calling the GLPK
* library to solve that. Please see Ulrik Brandes et al.: On
* Modularity Clustering, IEEE Transactions on Knowledge and Data
* Engineering 20(2):172-188, 2008
* https://doi.org/10.1109/TKDE.2007.190689.
*
* </para><para>
* Note that exact modularity optimization is an NP-complete problem, and
* all known algorithms for it have exponential time complexity. This
* means that you probably don't want to run this function on larger
* graphs. Graphs with up to fifty vertices should be fine, graphs
* with a couple of hundred vertices might be possible.
*
* \param graph The input graph. It may be undirected or directed.
* \param weights Vector giving the weights of the edges. If it is
* \c NULL then each edge is supposed to have the same weight.
* \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 modularity Pointer to a real number, or a null pointer.
* If it is not a null pointer, then a optimal modularity value
* is returned here.
* \param membership Pointer to a vector, or a null pointer. If not a
* null pointer, then the membership vector of the optimal
* community structure is stored here.
* \return Error code.
* When GLPK is not available, \c IGRAPH_UNIMPLEMENTED is returned.
*
* \sa \ref igraph_modularity(), \ref igraph_community_fastgreedy()
* for an algorithm that finds a local optimum in a greedy way.
*
* Time complexity: exponential in the number of vertices.
*
* \example examples/simple/igraph_community_optimal_modularity.c
*/
igraph_error_t igraph_community_optimal_modularity(const igraph_t *graph,
const igraph_vector_t *weights,
const igraph_real_t resolution,
igraph_real_t *modularity,
igraph_vector_int_t *membership) {
#ifndef HAVE_GLPK
IGRAPH_ERROR("GLPK is not available.", IGRAPH_UNIMPLEMENTED);
#else
const igraph_int_t no_of_nodes = igraph_vcount(graph);
const igraph_int_t no_of_edges = igraph_ecount(graph);
const igraph_bool_t directed = igraph_is_directed(graph);
igraph_int_t no_of_variables;
igraph_int_t i, j, k, l;
int st;
int idx[] = { 0, 0, 0, 0 };
double coef[] = { 0.0, 1.0, 1.0, -2.0 };
igraph_real_t total_weight;
igraph_vector_t indegree;
igraph_vector_t outdegree;
glp_prob *ip;
glp_iocp parm;
if (resolution < 0.0) {
IGRAPH_ERRORF("Resolution must be positive, got %g.", IGRAPH_EINVAL, resolution);
}
if (weights) {
if (igraph_vector_size(weights) != no_of_edges) {
IGRAPH_ERROR("Weight vector length must agree with number of edges.", IGRAPH_EINVAL);
}
if (no_of_edges > 0) {
/* Must not call vector_min on empty vector */
igraph_real_t minweight = igraph_vector_min(weights);
if (minweight < 0) {
IGRAPH_ERROR("Negative weights are not allowed in weight vector.", IGRAPH_EINVAL);
}
if (isnan(minweight)) {
IGRAPH_ERROR("Weights must not be NaN.", IGRAPH_EINVAL);
}
}
}
/* Avoid problems with the null graph */
if (no_of_nodes < 2) {
/* Cater for the case when membership was not given, but modularity was requested. */
igraph_vector_int_t imembership, *pmembership;
if (membership) {
pmembership = membership;
} else {
IGRAPH_VECTOR_INT_INIT_FINALLY(&imembership, no_of_nodes);
pmembership = &imembership;
}
IGRAPH_CHECK(igraph_vector_int_resize(pmembership, no_of_nodes));
igraph_vector_int_null(pmembership);
if (modularity) {
IGRAPH_CHECK(igraph_modularity(graph, pmembership, NULL, resolution, igraph_is_directed(graph), modularity));
}
if (! membership) {
igraph_vector_int_destroy(&imembership);
IGRAPH_FINALLY_CLEAN(1);
}
return IGRAPH_SUCCESS;
}
/* no_of_variables = no_of_nodes * (no_of_nodes + 1) / 2;
*
* Here we do not use IGRAPH_SAFE_N_CHOOSE_2 because later we rely on
* (no_of_nodes + 1) * no_of_nodes not overflowing even before the
* division by 2. See IDX() macro.
*/
IGRAPH_SAFE_MULT(no_of_nodes + 1, no_of_nodes, &no_of_variables);
no_of_variables /= 2;
if (no_of_variables > INT_MAX) {
IGRAPH_ERROR("Problem too large for GLPK.", IGRAPH_EOVERFLOW);
}
if (weights) {
total_weight = igraph_vector_sum(weights);
} else {
total_weight = no_of_edges;
}
if (!directed) {
total_weight *= 2;
}
/* Special case */
if (no_of_edges == 0 || total_weight == 0) {
if (modularity) {
*modularity = IGRAPH_NAN;
}
if (membership) {
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
igraph_vector_int_null(membership);
}
}
IGRAPH_VECTOR_INIT_FINALLY(&indegree, no_of_nodes);
IGRAPH_VECTOR_INIT_FINALLY(&outdegree, no_of_nodes);
IGRAPH_CHECK(igraph_strength(graph, &indegree, igraph_vss_all(),
IGRAPH_IN, IGRAPH_LOOPS, weights));
IGRAPH_CHECK(igraph_strength(graph, &outdegree, igraph_vss_all(),
IGRAPH_OUT, IGRAPH_LOOPS, weights));
IGRAPH_GLPK_SETUP();
ip = glp_create_prob();
IGRAPH_FINALLY(igraph_i_glp_delete_prob, ip);
glp_set_obj_dir(ip, GLP_MAX);
st = glp_add_cols(ip, (int) no_of_variables);
/* variables are binary */
for (i = 0; i < no_of_variables; i++) {
glp_set_col_kind(ip, (int)(st + i), GLP_BV);
}
#define IDX(a,b) (int)((b)*((b)+1)/2+(a))
/* reflexivity */
for (i = 0; i < no_of_nodes; i++) {
glp_set_col_bnds(ip, (st + IDX(i, i)), GLP_FX, 1.0, 1.0);
}
/* transitivity */
for (i = 0; i < no_of_nodes; i++) {
for (j = i + 1; j < no_of_nodes; j++) {
IGRAPH_ALLOW_INTERRUPTION();
for (k = j + 1; k < no_of_nodes; k++) {
int newrow = glp_add_rows(ip, 3);
glp_set_row_bnds(ip, newrow, GLP_UP, 0.0, 1.0);
idx[1] = (st + IDX(i, j)); idx[2] = (st + IDX(j, k));
idx[3] = (st + IDX(i, k));
glp_set_mat_row(ip, newrow, 3, idx, coef);
glp_set_row_bnds(ip, newrow + 1, GLP_UP, 0.0, 1.0);
idx[1] = st + IDX(i, j); idx[2] = st + IDX(i, k); idx[3] = st + IDX(j, k);
glp_set_mat_row(ip, newrow + 1, 3, idx, coef);
glp_set_row_bnds(ip, newrow + 2, GLP_UP, 0.0, 1.0);
idx[1] = st + IDX(i, k); idx[2] = st + IDX(j, k); idx[3] = st + IDX(i, j);
glp_set_mat_row(ip, newrow + 2, 3, idx, coef);
}
}
}
/* objective function */
{
igraph_real_t c;
/* first part: -strength(i)*strength(j)/total_weight for every node pair */
for (i = 0; i < no_of_nodes; i++) {
for (j = i + 1; j < no_of_nodes; j++) {
c = -VECTOR(indegree)[i] * VECTOR(outdegree)[j] / total_weight \
-VECTOR(outdegree)[i] * VECTOR(indegree)[j] / total_weight;
c *= resolution;
glp_set_obj_coef(ip, st + IDX(i, j), c);
}
/* special case for (i,i) */
c = -VECTOR(indegree)[i] * VECTOR(outdegree)[i] / total_weight;
c *= resolution;
glp_set_obj_coef(ip, st + IDX(i, i), c);
}
/* second part: add the weighted adjacency matrix to the coefficient matrix */
for (k = 0; k < no_of_edges; k++) {
i = IGRAPH_FROM(graph, k);
j = IGRAPH_TO(graph, k);
if (i > j) {
l = i; i = j; j = l;
}
c = weights ? VECTOR(*weights)[k] : 1.0;
if (!directed || i == j) {
c *= 2.0;
}
glp_set_obj_coef(ip, st + IDX(i, j), c + glp_get_obj_coef(ip, st + IDX(i, j)));
}
}
/* solve it */
glp_init_iocp(&parm);
parm.br_tech = GLP_BR_DTH;
parm.bt_tech = GLP_BT_BLB;
parm.presolve = GLP_ON;
parm.binarize = GLP_ON;
parm.cb_func = igraph_i_glpk_interruption_hook;
IGRAPH_GLPK_CHECK(glp_intopt(ip, &parm), "Modularity optimization failed");
/* store the results */
if (modularity) {
*modularity = glp_mip_obj_val(ip) / total_weight;
}
if (membership) {
igraph_int_t comm = 0; /* id of the last community that was found */
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
for (i = 0; i < no_of_nodes; i++) {
IGRAPH_ALLOW_INTERRUPTION();
for (j = 0; j < i; j++) {
int val = (int) glp_mip_col_val(ip, st + IDX(j, i));
if (val == 1) {
VECTOR(*membership)[i] = VECTOR(*membership)[j];
break;
}
}
if (j == i) { /* new community */
VECTOR(*membership)[i] = comm++;
}
}
}
#undef IDX
igraph_vector_destroy(&indegree);
igraph_vector_destroy(&outdegree);
glp_delete_prob(ip);
IGRAPH_FINALLY_CLEAN(3);
return IGRAPH_SUCCESS;
#endif
}
@@ -0,0 +1,101 @@
/*
igraph library.
Copyright (C) 2006-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Jörg Reichardt
The original copyright notice follows here */
/***************************************************************************
NetDataTypes.cpp - description
-------------------
begin : Mon Oct 6 2003
copyright : (C) 2003 by Joerg Reichardt
email : reichardt@mitte
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "NetDataTypes.h"
int NNode::Connect_To(NNode* neighbour, double weight_) {
NLink *link;
//sollen doppelte Links erlaubt sein?? NEIN
if (!neighbour) {
return 0;
}
if (!(neighbours.Is_In_List(neighbour)) && (neighbour != this)) {
neighbours.Push(neighbour); // nachbar hier eintragen
neighbour->neighbours.Push(this); // diesen knoten beim nachbarn eintragen
link = new NLink(this, neighbour, weight_); //link erzeugen
global_link_list->Push(link); // in globaler liste eintragen
n_links.Push(link); // bei diesem Knoten eintragen
neighbour->n_links.Push(link); // beim nachbarn eintragen
return 1;
}
return 0;
}
NLink *NNode::Get_LinkToNeighbour(const NNode* neighbour) {
DLList_Iter<NLink*> iter;
NLink *l_cur, *link = nullptr;
bool found = false;
// finde einen bestimmten Link aus der Liste der links eines Knotens
l_cur = iter.First(&n_links);
while (!iter.End() && !found) {
if (((l_cur->Get_Start() == this) && (l_cur->Get_End() == neighbour)) || ((l_cur->Get_End() == this) && (l_cur->Get_Start() == neighbour))) {
found = true;
link = l_cur;
}
l_cur = iter.Next();
}
if (found) {
return link;
} else {
return nullptr;
}
}
igraph_int_t NNode::Disconnect_From(NNode* neighbour) {
//sollen doppelte Links erlaubt sein?? s.o.
neighbours.fDelete(neighbour);
n_links.fDelete(Get_LinkToNeighbour(neighbour));
neighbour->n_links.fDelete(neighbour->Get_LinkToNeighbour(this));
neighbour->neighbours.fDelete(this);
return 1;
}
igraph_int_t NNode::Disconnect_From_All() {
igraph_int_t number_of_neighbours = 0;
while (neighbours.Size()) {
Disconnect_From(neighbours.Pop());
number_of_neighbours++;
}
return number_of_neighbours ;
}
@@ -0,0 +1,567 @@
/*
igraph library.
Copyright (C) 2006-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Jörg Reichardt
The original copyright notice follows here */
/***************************************************************************
NetDataTypes.h - description
-------------------
begin : Mon Oct 6 2003
copyright : (C) 2003 by Joerg Reichardt
email : reichardt@mitte
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef NETDATATYPES_H
#define NETDATATYPES_H
#include "igraph_types.h"
#include <cassert>
#include <cstring>
// In igraph, we set node names to be a string representation of the one-based
// vertex ID. This takes at most 20 characters. Add one for a potential sign
// (should not happen) and one more for the null terminator.
#define SPINGLASS_MAX_NAME_LEN 22
//###########################################################################################
struct HUGE_INDEX {
unsigned int field_index;
igraph_int_t in_field_index;
};
template <class DATA>
class HugeArray {
igraph_int_t size = 2;
unsigned int highest_field_index = 0;
const igraph_int_t max_bit_left = 1UL << 31; //wir setzen das 31. Bit auf 1
igraph_int_t max_index = 0;
DATA *data;
DATA *fields[32];
public:
HugeArray();
HugeArray(const HugeArray &) = delete;
HugeArray & operator = (const HugeArray &) = delete;
~HugeArray();
HUGE_INDEX get_huge_index(igraph_int_t) const;
DATA &Set(igraph_int_t index);
DATA Get(igraph_int_t index) { return Set(index); }
DATA &operator[](igraph_int_t index) { return Set(index); }
igraph_int_t Size() const { return max_index; }
} ;
//###############################################################################################
template <class L_DATA > class DLList;
template <class L_DATA > class DL_Indexed_List;
template <class L_DATA > using ClusterList= DLList<L_DATA>;
template <class L_DATA > class DLList_Iter;
template <class L_DATA>
class DLItem {
friend class DLList<L_DATA> ;
friend class DL_Indexed_List<L_DATA>;
friend class DLList_Iter<L_DATA>;
L_DATA item;
igraph_int_t index;
DLItem *previous;
DLItem *next;
DLItem(L_DATA i, igraph_int_t ind);
DLItem(L_DATA i, igraph_int_t ind, DLItem<L_DATA> *p, DLItem<L_DATA> *n);
public:
void del() {
delete item;
}
};
template <class L_DATA >
class DLList {
friend class DLList_Iter<L_DATA>;
protected:
DLItem<L_DATA> *head;
DLItem<L_DATA> *tail;
igraph_int_t number_of_items = 0;
virtual DLItem<L_DATA> *pInsert(L_DATA, DLItem<L_DATA>*);
virtual L_DATA pDelete(DLItem<L_DATA>*);
public:
DLList();
DLList(const DLList &) = delete;
DLList & operator = (const DLList &) = delete;
virtual ~DLList();
igraph_int_t Size() const {
return number_of_items;
}
int fDelete(L_DATA);
virtual L_DATA Push(L_DATA);
virtual L_DATA Pop();
virtual L_DATA Get(igraph_int_t);
igraph_int_t Is_In_List(L_DATA);
void delete_items();
};
template <class L_DATA>
class DL_Indexed_List : public DLList<L_DATA> {
DLItem<L_DATA> *pInsert(L_DATA, DLItem<L_DATA>*) final;
L_DATA pDelete(DLItem<L_DATA>*) final;
HugeArray<DLItem<L_DATA>*> array;
igraph_int_t last_index = 0;
public:
DL_Indexed_List() = default;
L_DATA Push(L_DATA) final;
L_DATA Pop() final;
L_DATA Get(igraph_int_t) final;
};
//#####################################################################################################
template <class L_DATA> class DLList_Iter {
const DLList<L_DATA> *list = nullptr;
const DLItem<L_DATA> *current = nullptr;
bool end_reached = true;
public:
L_DATA Next();
L_DATA Previous();
L_DATA First(const DLList<L_DATA> *l);
L_DATA Last(const DLList<L_DATA> *l);
bool End() const {
return end_reached;
}
bool Swap(DLList_Iter<L_DATA>); //swapt die beiden Elemente, wenn sie in der gleichen Liste stehen!!
};
//#####################################################################################################
class NLink;
class NNode {
igraph_int_t index;
igraph_int_t cluster_index;
igraph_int_t marker = 0;
double weight = 0.0;
DLList<NNode*> neighbours; //list with pointers to neighbours
DLList<NLink*> n_links;
DLList<NLink*> *global_link_list;
char name[SPINGLASS_MAX_NAME_LEN];
public :
NNode(igraph_int_t ind, igraph_int_t c_ind, DLList<NLink *> *ll, const char *n) :
index(ind), cluster_index(c_ind), global_link_list(ll)
{
strcpy(name, n);
}
NNode(const NNode &) = delete;
NNode &operator=(const NNode &) = delete;
~NNode() { Disconnect_From_All(); }
igraph_int_t Get_Index() const {
return index;
}
igraph_int_t Get_ClusterIndex() const {
return cluster_index;
}
igraph_int_t Get_Marker() const {
return marker;
}
void Set_Marker(igraph_int_t m) {
marker = m;
}
void Set_ClusterIndex(igraph_int_t ci) {
cluster_index = ci;
}
igraph_int_t Get_Degree() const {
return (neighbours.Size());
}
const char *Get_Name() {
return name;
}
void Set_Name(const char *n) {
strcpy(name, n);
}
double Get_Weight() const {
return weight;
}
void Set_Weight(double w) {
weight = w;
}
int Connect_To(NNode*, double);
const DLList<NNode*> *Get_Neighbours() const {
return &neighbours;
}
const DLList<NLink*> *Get_Links() const {
return &n_links;
}
igraph_int_t Disconnect_From(NNode*);
igraph_int_t Disconnect_From_All();
NLink *Get_LinkToNeighbour(const NNode *neighbour);
};
//#####################################################################################################
class NLink {
NNode *start;
NNode *end;
double weight;
public :
NLink(NNode *s, NNode *e, double w) : start(s), end(e), weight(w) { }
NLink(const NLink &) = delete;
NLink & operator = (const NLink &) = delete;
~NLink() { start->Disconnect_From(end); }
NNode *Get_Start() { return start; }
NNode *Get_End() { return end; }
const NNode *Get_Start() const { return start; }
const NNode *Get_End() const { return end; }
double Get_Weight() const { return weight; }
};
//#####################################################################################################
struct network {
DL_Indexed_List<NNode*> node_list;
DL_Indexed_List<NLink*> link_list;
DL_Indexed_List<ClusterList<NNode*>*> cluster_list;
double sum_weights;
network() = default;
network (const network &) = delete;
network & operator = (const network &) = delete;
~network() {
ClusterList<NNode*> *cl_cur;
while (link_list.Size()) {
delete link_list.Pop();
}
while (node_list.Size()) {
delete node_list.Pop();
}
while (cluster_list.Size()) {
cl_cur = cluster_list.Pop();
while (cl_cur->Size()) {
cl_cur->Pop();
}
delete cl_cur;
}
}
};
template <class DATA>
HugeArray<DATA>::HugeArray() {
data = new DATA[2]; //ein extra Platz fuer das Nullelement
data[0] = 0;
data[1] = 0;
for (auto & field : fields) {
field = nullptr;
}
fields[highest_field_index] = data;
}
template <class DATA> HugeArray<DATA>::~HugeArray() {
for (unsigned int i = 0; i <= highest_field_index; i++) {
data = fields[i];
delete [] data;
}
}
template <class DATA>
HUGE_INDEX HugeArray<DATA>::get_huge_index(igraph_int_t index) const {
HUGE_INDEX h_index;
unsigned int shift_index = 0;
igraph_int_t help_index;
help_index = index;
if (index < 2) {
h_index.field_index = 0;
h_index.in_field_index = index;
return h_index;
}
// wie oft muessen wir help_index nach links shiften, damit das 31. Bit gesetzt ist??
while (!(max_bit_left & help_index)) {
help_index <<= 1;
shift_index++;
}
h_index.field_index = 31 - shift_index; // das hoechste besetzte Bit im Index
help_index = igraph_int_t(1) << h_index.field_index; // in help_index wird das hoechste besetzte Bit von Index gesetzt
h_index.in_field_index = (index ^ help_index); // index XOR help_index, womit alle bits unter dem hoechsten erhalten bleiben
return h_index;
}
template <class DATA>
DATA &HugeArray<DATA>::Set(igraph_int_t index) {
igraph_int_t data_size;
while (size < index + 1) {
highest_field_index++;
data_size = igraph_int_t(1) << highest_field_index;
data = new DATA[data_size];
for (igraph_int_t i = 0; i < data_size; i++) {
data[i] = 0;
}
size = size + data_size; //overflow noch abfangen
fields[highest_field_index] = data;
}
HUGE_INDEX h_index = get_huge_index(index);
data = fields[h_index.field_index];
if (max_index < index) {
max_index = index;
}
return data[h_index.in_field_index];
}
//###############################################################################
template <class L_DATA>
DLItem<L_DATA>::DLItem(L_DATA i, igraph_int_t ind) :
item(i), index(ind), previous(nullptr), next(nullptr) { }
template <class L_DATA>
DLItem<L_DATA>::DLItem(L_DATA i, igraph_int_t ind, DLItem<L_DATA> *p, DLItem<L_DATA> *n) :
item(i), index(ind), previous(p), next(n) { }
//######################################################################################################################
template <class L_DATA>
DLList<L_DATA>::DLList() {
head = new DLItem<L_DATA>(NULL, 0); //fuer head und Tail gibt es das gleiche Array-Element!! Vorsicht!!
tail = new DLItem<L_DATA>(NULL, 0);
head->next = tail;
tail->previous = head;
}
template <class L_DATA>
DLList<L_DATA>::~DLList() {
DLItem<L_DATA> *cur = head, *next;
while (cur) {
next = cur->next;
delete cur;
cur = next;
}
number_of_items = 0;
}
template <class L_DATA>
void DLList<L_DATA>::delete_items() {
DLItem<L_DATA> *cur, *next;
cur = this->head;
while (cur) {
next = cur->next;
cur->del();
cur = next;
}
this->number_of_items = 0;
}
//privates Insert
template <class L_DATA>
DLItem<L_DATA> *DLList<L_DATA>::pInsert(L_DATA data, DLItem<L_DATA> *pos) {
auto *i = new DLItem<L_DATA>(data, number_of_items + 1, pos->previous, pos);
pos->previous->next = i;
pos->previous = i;
number_of_items++;
return i;
}
//privates delete
template <class L_DATA>
L_DATA DLList<L_DATA>::pDelete(DLItem<L_DATA> *i) {
assert(number_of_items > 0);
L_DATA data = i->item;
i->previous->next = i->next;
i->next->previous = i->previous;
delete i;
number_of_items--;
return data;
}
//oeffentliche Delete
template <class L_DATA>
int DLList<L_DATA>::fDelete(L_DATA data) {
if ((number_of_items == 0) || (!data)) {
return 0;
}
DLItem<L_DATA> *cur;
cur = head->next;
while ((cur != tail) && (cur->item != data)) {
cur = cur->next;
}
if (cur != tail) {
return (pDelete(cur) != 0);
}
return 0;
}
template <class L_DATA>
L_DATA DLList<L_DATA>::Push(L_DATA data) {
DLItem<L_DATA> *tmp = pInsert(data, tail);
return tmp->item;
}
template <class L_DATA>
L_DATA DLList<L_DATA>::Pop() {
return pDelete(tail->previous);
}
template <class L_DATA>
L_DATA DLList<L_DATA>::Get(igraph_int_t pos) {
if ((pos < 1) || (pos > (number_of_items + 1))) {
return 0;
}
DLItem<L_DATA> *cur = head;
while (pos--) {
cur = cur->next;
}
return (cur->item);
}
//gibt Index des gesuchte Listenelement zurueck, besser waere eigentlich zeiger
template <class L_DATA>
igraph_int_t DLList<L_DATA>::Is_In_List(L_DATA data) {
DLItem<L_DATA> *cur = head, *next;
igraph_int_t pos = 0;
while (cur) {
next = cur->next;
if (cur->item == data) {
return pos ;
}
cur = next;
pos++;
}
return 0;
}
//######################################################################################################################
//privates Insert
template <class L_DATA>
DLItem<L_DATA> *DL_Indexed_List<L_DATA>::pInsert(L_DATA data, DLItem<L_DATA> *pos) {
auto *i = new DLItem<L_DATA>(data, last_index, pos->previous, pos);
pos->previous->next = i;
pos->previous = i;
this->number_of_items++;
array[last_index] = i;
last_index++;
return i;
}
//privates delete
template <class L_DATA>
L_DATA DL_Indexed_List<L_DATA>::pDelete(DLItem<L_DATA> *i) {
assert(this->number_of_items > 0);
L_DATA data = i->item;
i->previous->next = i->next;
i->next->previous = i->previous;
array[i->index] = 0;
last_index = i->index;
delete i;
this->number_of_items--;
return data;
}
template <class L_DATA>
L_DATA DL_Indexed_List<L_DATA>::Push(L_DATA data) {
DLItem<L_DATA> *tmp;
tmp = pInsert(data, this->tail);
return tmp->item;
}
template <class L_DATA>
L_DATA DL_Indexed_List<L_DATA>::Pop() {
return pDelete(this->tail->previous);
}
template <class L_DATA>
L_DATA DL_Indexed_List<L_DATA>::Get(igraph_int_t pos) {
if (pos > this->number_of_items - 1) {
return 0;
}
return array[pos]->item;
}
//#####################################################################################
template <class L_DATA>
L_DATA DLList_Iter<L_DATA>::Next() {
current = current->next;
if (current == (list->tail)) {
end_reached = true;
}
return (current->item);
}
template <class L_DATA>
L_DATA DLList_Iter<L_DATA>::Previous() {
current = current->previous;
if (current == (list->head)) {
end_reached = true;
}
return (current->item);
}
template <class L_DATA>
L_DATA DLList_Iter<L_DATA>::First(const DLList<L_DATA> *l) {
list = l;
current = list->head->next;
if (current == (list->tail)) {
end_reached = true;
} else {
end_reached = false;
}
return (current->item);
}
template <class L_DATA>
L_DATA DLList_Iter<L_DATA>::Last(const DLList<L_DATA> *l) {
list = l;
current = list->tail->previous;
if (current == (list->head)) {
end_reached = true; // falls die List leer ist
} else {
end_reached = false;
}
return (current->item);
}
template <class L_DATA>
bool DLList_Iter<L_DATA>::Swap(DLList_Iter<L_DATA> b) {
L_DATA h;
if (list != b.list) {
return false; //elemeten muessen aus der gleichen List stammen
}
if (end_reached || b.end_reached) {
return false;
}
h = current->item; current->item = b.current->item; b.current->item = h;
return true;
}
#endif
@@ -0,0 +1,80 @@
/*
igraph library.
Copyright (C) 2006-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Jörg Reichardt
The original copyright notice follows here */
/***************************************************************************
NetRoutines.cpp - description
-------------------
begin : Tue Oct 28 2003
copyright : (C) 2003 by Joerg Reichardt
email : reichardt@mitte
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "NetRoutines.h"
#include "NetDataTypes.h"
#include "igraph_types.h"
#include "igraph_interface.h"
igraph_error_t igraph_i_read_network_spinglass(
const igraph_t *graph, const igraph_vector_t *weights,
network *net, igraph_bool_t use_weights) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t no_of_edges = igraph_ecount(graph);
double sum_weight;
for (igraph_int_t vid = 0; vid < no_of_nodes; vid++) {
char name[SPINGLASS_MAX_NAME_LEN];
snprintf(name, sizeof(name) / sizeof(name[0]), "%" IGRAPH_PRId "", vid+1);
net->node_list.Push(new NNode(vid, 0, &net->link_list, name));
}
sum_weight = 0.0;
for (igraph_int_t eid = 0; eid < no_of_edges; eid++) {
igraph_int_t v1 = IGRAPH_FROM(graph, eid);
igraph_int_t v2 = IGRAPH_TO(graph, eid);
igraph_real_t w = use_weights ? VECTOR(*weights)[eid] : 1.0;
NNode *node1 = net->node_list.Get(v1);
NNode *node2 = net->node_list.Get(v2);
node1->Connect_To(node2, w);
sum_weight += w;
}
net->sum_weights = sum_weight;
return IGRAPH_SUCCESS;
}
@@ -0,0 +1,54 @@
/*
igraph library.
Copyright (C) 2006-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Jörg Reichardt
The original copyright notice follows here */
/***************************************************************************
NetRoutines.h - description
-------------------
begin : Tue Oct 28 2003
copyright : (C) 2003 by Joerg Reichardt
email : reichardt@mitte
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef NETROUTINES_H
#define NETROUTINES_H
#include "NetDataTypes.h"
#include "igraph_types.h"
#include "igraph_datatype.h"
igraph_error_t igraph_i_read_network_spinglass(
const igraph_t *graph, const igraph_vector_t *weights,
network *net, igraph_bool_t use_weights);
#endif
@@ -0,0 +1,632 @@
/*
igraph library.
Copyright (C) 2006-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Joerg Reichardt
The original copyright notice follows here */
/***************************************************************************
main.cpp - description
-------------------
begin : Tue Jul 13 11:26:47 CEST 2004
copyright : (C) 2004 by
email :
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#include "NetDataTypes.h"
#include "NetRoutines.h"
#include "pottsmodel_2.h"
#include "igraph_community.h"
#include "igraph_components.h"
#include "igraph_error.h"
#include "igraph_interface.h"
#include "igraph_random.h"
#include "core/interruption.h"
#include "core/exceptions.h"
static igraph_error_t igraph_i_community_spinglass_orig(
const igraph_t *graph,
const igraph_vector_t *weights,
igraph_real_t *modularity,
igraph_real_t *temperature,
igraph_vector_int_t *membership,
igraph_vector_int_t *csize,
igraph_int_t spins,
igraph_bool_t parupdate,
igraph_real_t starttemp,
igraph_real_t stoptemp,
igraph_real_t coolfact,
igraph_spincomm_update_t update_rule,
igraph_real_t gamma);
static igraph_error_t igraph_i_community_spinglass_negative(
const igraph_t *graph,
const igraph_vector_t *weights,
igraph_real_t *modularity,
igraph_real_t *temperature,
igraph_vector_int_t *membership,
igraph_vector_int_t *csize,
igraph_int_t spins,
igraph_bool_t parupdate,
igraph_real_t starttemp,
igraph_real_t stoptemp,
igraph_real_t coolfact,
igraph_spincomm_update_t update_rule,
igraph_real_t gamma,
igraph_real_t gamma_minus);
/**
* \function igraph_community_spinglass
* \brief Community detection based on statistical mechanics.
*
* This function implements the community structure detection
* algorithm proposed by Joerg Reichardt and Stefan Bornholdt.
* The algorithm is described in their paper: Statistical Mechanics of
* Community Detection, http://arxiv.org/abs/cond-mat/0603718 .
*
* </para><para>
* From version 0.6, igraph also supports an extension to
* the algorithm that allows negative edge weights. This is described
* in V. A. Traag and Jeroen Bruggeman: Community detection in networks
* with positive and negative links, http://arxiv.org/abs/0811.2329 .
*
* \param graph The input graph, it may be directed but the direction
* of the edges is ignored by the algorithm.
* \param weights The vector giving the edge weights, it may be \c NULL,
* in which case all edges are weighted equally. The edge weights
* must be positive unless using the \c IGRAPH_SPINCOMM_IMP_NEG
* implementation.
* \param modularity Pointer to a real number, if not \c NULL then the
* modularity score of the solution will be stored here. This is the
* gereralized modularity, taking into account the resolution parameter
* \p gamma. See \ref igraph_modularity() for details.
* \param temperature Pointer to a real number, if not \c NULL then
* the temperature at the end of the algorithm will be stored
* here.
* \param membership Pointer to an initialized vector or \c NULL. If
* not \c NULL then the result of the clustering will be stored
* here. For each vertex, the number of its cluster is given, with the
* first cluster numbered zero. The vector will be resized as
* needed.
* \param csize Pointer to an initialized vector or \c NULL. If not \c
* NULL then the sizes of the clusters will stored here in cluster
* number order. The vector will be resized as needed.
* \param spins Integer giving the number of spins, i.e. the maximum
* number of clusters. Even if the number of spins is high the number of
* clusters in the result might be small.
* \param parupdate A Boolean constant, whether to update all spins in
* parallel. It is not implemented in the \c IGRAPH_SPINCOMM_INP_NEG
* implementation.
* \param starttemp Real number, the temperature at the start. A reasonable
* default is 1.0.
* \param stoptemp Real number, the algorithm stops at this temperature. A
* reasonable default is 0.01.
* \param coolfact Real number, the cooling factor for the simulated
* annealing. A reasonable default is 0.99.
* \param update_rule The type of the update rule. Possible values: \c
* IGRAPH_SPINCOMM_UPDATE_SIMPLE and \c
* IGRAPH_SPINCOMM_UPDATE_CONFIG. Basically this parameter defines
* the null model based on which the actual clustering is done. If
* this is \c IGRAPH_SPINCOMM_UPDATE_SIMPLE then the random graph
* (i.e. G(n,p)), if it is \c IGRAPH_SPINCOMM_UPDATE then the
* configuration model is used. The configuration means that the
* baseline for the clustering is a random graph with the same
* degree distribution as the input graph.
* \param gamma Real number. The gamma parameter of the algorithm,
* acting as a resolution parameter. Smaller values typically lead to
* larger clusters, larger values typically lead to smaller clusters.
* \param implementation Constant, chooses between the two
* implementations of the spin-glass algorithm that are included
* in igraph. \c IGRAPH_SPINCOMM_IMP_ORIG selects the original
* implementation, this is faster, \c IGRAPH_SPINCOMM_INP_NEG selects
* an implementation that allows negative edge weights.
* \param gamma_minus Real number. Parameter for the \c IGRAPH_SPINCOMM_IMP_NEG
* implementation. This acts as a resolution parameter for the negative part
* of the network. Smaller values of \p gamma_minus leads to fewer negative
* edges within clusters. If this argument is set to zero, the algorithm
* reduces to a graph coloring algorithm when all edges have negative
* weights, using the number of spins as the number of colors.
* \return Error code.
*
* \sa \ref igraph_community_spinglass_single() for calculating the community
* of a single vertex.
*
* Time complexity: TODO.
*
*/
igraph_error_t igraph_community_spinglass(const igraph_t *graph,
const igraph_vector_t *weights,
igraph_real_t *modularity,
igraph_real_t *temperature,
igraph_vector_int_t *membership,
igraph_vector_int_t *csize,
igraph_int_t spins,
igraph_bool_t parupdate,
igraph_real_t starttemp,
igraph_real_t stoptemp,
igraph_real_t coolfact,
igraph_spincomm_update_t update_rule,
igraph_real_t gamma,
igraph_spinglass_implementation_t implementation,
igraph_real_t gamma_minus) {
IGRAPH_HANDLE_EXCEPTIONS(
switch (implementation) {
case IGRAPH_SPINCOMM_IMP_ORIG:
return igraph_i_community_spinglass_orig(graph, weights, modularity,
temperature, membership, csize,
spins, parupdate, starttemp,
stoptemp, coolfact, update_rule,
gamma);
break;
case IGRAPH_SPINCOMM_IMP_NEG:
return igraph_i_community_spinglass_negative(graph, weights, modularity,
temperature, membership, csize,
spins, parupdate, starttemp,
stoptemp, coolfact,
update_rule, gamma,
gamma_minus);
break;
default:
IGRAPH_ERROR("Unknown implementation in spinglass community detection.",
IGRAPH_EINVAL);
}
);
}
static igraph_error_t igraph_i_community_spinglass_orig(
const igraph_t *graph,
const igraph_vector_t *weights,
igraph_real_t *modularity,
igraph_real_t *temperature,
igraph_vector_int_t *membership,
igraph_vector_int_t *csize,
igraph_int_t spins,
igraph_bool_t parupdate,
igraph_real_t starttemp,
igraph_real_t stoptemp,
igraph_real_t coolfact,
igraph_spincomm_update_t update_rule,
igraph_real_t gamma) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t changes, runs;
igraph_bool_t use_weights = false;
bool zeroT;
double kT, acc, prob;
/* Check arguments */
if (spins < 2) {
IGRAPH_ERROR("Number of spins must be at least 2.", IGRAPH_EINVAL);
}
if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE &&
update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) {
IGRAPH_ERROR("Invalid update rule for spinglass community detection.", IGRAPH_EINVAL);
}
if (weights) {
if (igraph_vector_size(weights) != igraph_ecount(graph)) {
IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL);
}
use_weights = true;
if (igraph_vector_size(weights) > 0 && igraph_vector_min(weights) < 0) {
IGRAPH_ERROR(
"Weights must not be negative when using the original implementation of spinglass communities. "
"Select the implementation meant for negative weights.",
IGRAPH_EINVAL);
}
}
if (coolfact < 0 || coolfact >= 1.0) {
IGRAPH_ERROR("Cooling factor must be positive and strictly smaller than 1.", IGRAPH_EINVAL);
}
if (gamma < 0.0) {
IGRAPH_ERROR("Gamma value must not be negative.", IGRAPH_EINVAL);
}
if ( !(starttemp == 0 && stoptemp == 0) ) {
if (! (starttemp > 0 && stoptemp > 0)) {
IGRAPH_ERROR("Starting and stopping temperatures must be both positive or both zero.",
IGRAPH_EINVAL);
}
if (starttemp <= stoptemp) {
IGRAPH_ERROR("The starting temperature must be larger than the stopping temperature.",
IGRAPH_EINVAL);
}
}
/* The spinglass algorithm does not handle the trivial cases of the
null and singleton graphs, so we catch them here. */
if (no_of_nodes < 2) {
if (membership) {
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
igraph_vector_int_null(membership);
}
if (modularity) {
IGRAPH_CHECK(igraph_modularity(graph, membership, nullptr, 1, igraph_is_directed(graph), modularity));
}
if (temperature) {
*temperature = stoptemp;
}
if (csize) {
/* 0 clusters for 0 nodes, 1 cluster for 1 node */
IGRAPH_CHECK(igraph_vector_int_resize(csize, no_of_nodes));
igraph_vector_int_fill(csize, 1);
}
return IGRAPH_SUCCESS;
}
/* Check whether we have a single component */
igraph_bool_t conn;
IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK));
if (!conn) {
IGRAPH_ERROR("Cannot work with unconnected graph.", IGRAPH_EINVAL);
}
network net;
/* Transform the igraph_t */
IGRAPH_CHECK(igraph_i_read_network_spinglass(graph, weights,
&net, use_weights));
prob = 2.0 * net.sum_weights / double(net.node_list.Size())
/ double(net.node_list.Size() - 1);
PottsModel pm(&net, spins, update_rule);
if ((stoptemp == 0.0) && (starttemp == 0.0)) {
zeroT = true;
} else {
zeroT = false;
}
if (!zeroT) {
kT = pm.FindStartTemp(gamma, prob, starttemp);
} else {
kT = stoptemp;
}
/* assign random initial configuration */
pm.assign_initial_conf(-1);
runs = 0;
changes = 1;
while (changes > 0 && (kT / stoptemp > 1.0 || (zeroT && runs < 150))) {
IGRAPH_ALLOW_INTERRUPTION();
runs++;
if (!zeroT) {
kT *= coolfact;
if (parupdate) {
changes = pm.HeatBathParallelLookup(gamma, prob, kT, 50);
} else {
acc = pm.HeatBathLookup(gamma, prob, kT, 50);
if (acc < (1.0 - 1.0 / double(spins)) * 0.01) {
changes = 0;
} else {
changes = 1;
}
}
} else {
if (parupdate) {
changes = pm.HeatBathParallelLookupZeroTemp(gamma, prob, 50);
} else {
acc = pm.HeatBathLookupZeroTemp(gamma, prob, 50);
/* less than 1 percent acceptance ratio */
if (acc < (1.0 - 1.0 / double(spins)) * 0.01) {
changes = 0;
} else {
changes = 1;
}
}
}
} /* while loop */
pm.WriteClusters(modularity, temperature, csize, membership, kT, gamma);
return IGRAPH_SUCCESS;
}
/**
* \function igraph_community_spinglass_single
* \brief Community of a single node based on statistical mechanics.
*
* This function implements the community structure detection
* algorithm proposed by Joerg Reichardt and Stefan Bornholdt. It is
* described in their paper: Statistical Mechanics of
* Community Detection, http://arxiv.org/abs/cond-mat/0603718 .
*
* </para><para>
* This function calculates the community of a single vertex without
* calculating all the communities in the graph.
*
* \param graph The input graph, it may be directed but the direction
* of the edges is not used in the algorithm.
* \param weights Pointer to a vector with the weights of the edges.
* Alternatively \c NULL can be supplied to have the same weight
* for every edge.
* \param vertex The vertex ID of the vertex of which this community is
* calculated.
* \param community Pointer to an initialized vector, the result, the
* IDs of the vertices in the community of the input vertex will be
* stored here. The vector will be resized as needed.
* \param cohesion Pointer to a real variable, if not \c NULL the
* cohesion index of the community will be stored here.
* \param adhesion Pointer to a real variable, if not \c NULL the
* adhesion index of the community will be stored here.
* \param inner_links Pointer to a real, if not \c NULL the
* number of edges within the community (or the sum of their weights)
* is stored here.
* \param outer_links Pointer to a real, if not \c NULL the
* number of edges between the community and the rest of the graph
* (or the sum of their weights) will be stored here.
* \param spins The number of spins to use, this can be higher than
* the actual number of clusters in the network, in which case some
* clusters will contain zero vertices.
* \param update_rule The type of the update rule. Possible values: \c
* IGRAPH_SPINCOMM_UPDATE_SIMPLE and \c
* IGRAPH_SPINCOMM_UPDATE_CONFIG. Basically this parameter defined
* the null model based on which the actual clustering is done. If
* this is \c IGRAPH_SPINCOMM_UPDATE_SIMPLE then the random graph
* (ie. G(n,p)), if it is \c IGRAPH_SPINCOMM_UPDATE then the
* configuration model is used. The configuration means that the
* baseline for the clustering is a random graph with the same
* degree distribution as the input graph.
* \param gamma Real number. The gamma parameter of the
* algorithm. This defined the weight of the missing and existing
* links in the quality function for the clustering. The default
* value in the original code was 1.0, which is equal weight to
* missing and existing edges. Smaller values make the existing
* links contibute more to the energy function which is minimized
* in the algorithm. Bigger values make the missing links more
* important. (If my understanding is correct.)
* \return Error code.
*
* \sa igraph_community_spinglass() for the traditional version of the
* algorithm.
*
* Time complexity: TODO.
*/
igraph_error_t igraph_community_spinglass_single(const igraph_t *graph,
const igraph_vector_t *weights,
igraph_int_t vertex,
igraph_vector_int_t *community,
igraph_real_t *cohesion,
igraph_real_t *adhesion,
igraph_real_t *inner_links,
igraph_real_t *outer_links,
igraph_int_t spins,
igraph_spincomm_update_t update_rule,
igraph_real_t gamma) {
IGRAPH_HANDLE_EXCEPTIONS(
igraph_bool_t use_weights = false;
char startnode[SPINGLASS_MAX_NAME_LEN];
/* Check arguments */
if (spins < 2) {
IGRAPH_ERROR("Number of spins must be at least 2.", IGRAPH_EINVAL);
}
if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE &&
update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) {
IGRAPH_ERROR("Invalid update rule", IGRAPH_EINVAL);
}
if (weights) {
if (igraph_vector_size(weights) != igraph_ecount(graph)) {
IGRAPH_ERROR("Invalid edge weight vector length.", IGRAPH_EINVAL);
}
use_weights = 1;
}
if (gamma < 0.0) {
IGRAPH_ERROR("Invalid gamma value.", IGRAPH_EINVAL);
}
if (vertex < 0 || vertex > igraph_vcount(graph)) {
IGRAPH_ERROR("Invalid vertex ID.", IGRAPH_EINVAL);
}
/* Check whether we have a single component */
igraph_bool_t conn;
IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK));
if (!conn) {
IGRAPH_ERROR("Cannot work with disconnected graph.", IGRAPH_EINVAL);
}
network net;
/* Transform the igraph_t */
IGRAPH_CHECK(igraph_i_read_network_spinglass(graph, weights,
&net, use_weights));
PottsModel pm(&net, spins, update_rule);
/* to be expected, if we want to find the community around a particular node*/
/* the initial conf is needed, because otherwise,
the degree of the nodes is not in the weight property, stupid!!! */
pm.assign_initial_conf(-1);
snprintf(startnode, sizeof(startnode) / sizeof(startnode[0]), "%" IGRAPH_PRId "", vertex + 1);
pm.FindCommunityFromStart(gamma, startnode, community,
cohesion, adhesion, inner_links, outer_links);
);
return IGRAPH_SUCCESS;
}
static igraph_error_t igraph_i_community_spinglass_negative(
const igraph_t *graph,
const igraph_vector_t *weights,
igraph_real_t *modularity,
igraph_real_t *temperature,
igraph_vector_int_t *membership,
igraph_vector_int_t *csize,
igraph_int_t spins,
igraph_bool_t parupdate,
igraph_real_t starttemp,
igraph_real_t stoptemp,
igraph_real_t coolfact,
igraph_spincomm_update_t update_rule,
igraph_real_t gamma,
igraph_real_t gamma_minus) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t runs;
igraph_bool_t use_weights = false;
bool zeroT;
double kT, acc;
igraph_real_t d_n;
igraph_real_t d_p;
/* Check arguments */
if (parupdate) {
IGRAPH_ERROR("Parallel spin update not implemented with negative weights.",
IGRAPH_UNIMPLEMENTED);
}
if (spins < 2) {
IGRAPH_ERROR("Number of spins must be at least 2.", IGRAPH_EINVAL);
}
if (update_rule != IGRAPH_SPINCOMM_UPDATE_SIMPLE &&
update_rule != IGRAPH_SPINCOMM_UPDATE_CONFIG) {
IGRAPH_ERROR("Invalid update rule for spinglass community detection.", IGRAPH_EINVAL);
}
if (weights) {
if (igraph_vector_size(weights) != igraph_ecount(graph)) {
IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL);
}
use_weights = true;
}
if (coolfact < 0 || coolfact >= 1.0) {
IGRAPH_ERROR("Cooling factor must be positive and strictly smaller than 1.", IGRAPH_EINVAL);
}
if (gamma < 0.0) {
IGRAPH_ERROR("Gamma value must not be negative.", IGRAPH_EINVAL);
}
if ( !(starttemp == 0 && stoptemp == 0) ) {
if (! (starttemp > 0 && stoptemp > 0)) {
IGRAPH_ERROR("Starting and stopping temperatures must be both positive or both zero.",
IGRAPH_EINVAL);
}
if (starttemp <= stoptemp) {
IGRAPH_ERROR("The starting temperature must be larger than the stopping temperature.",
IGRAPH_EINVAL);
}
}
/* The spinglass algorithm does not handle the trivial cases of the
null and singleton graphs, so we catch them here. */
if (no_of_nodes < 2) {
if (membership) {
IGRAPH_CHECK(igraph_vector_int_resize(membership, no_of_nodes));
igraph_vector_int_null(membership);
}
if (modularity) {
IGRAPH_CHECK(igraph_modularity(graph, membership, nullptr, 1, igraph_is_directed(graph), modularity));
}
if (temperature) {
*temperature = stoptemp;
}
if (csize) {
/* 0 clusters for 0 nodes, 1 cluster for 1 node */
IGRAPH_CHECK(igraph_vector_int_resize(csize, no_of_nodes));
igraph_vector_int_fill(csize, 1);
}
return IGRAPH_SUCCESS;
}
/* Check whether we have a single component */
igraph_bool_t conn;
IGRAPH_CHECK(igraph_is_connected(graph, &conn, IGRAPH_WEAK));
if (!conn) {
IGRAPH_ERROR("Cannot work with unconnected graph.", IGRAPH_EINVAL);
}
if (weights && igraph_vector_size(weights) > 0) {
igraph_vector_minmax(weights, &d_n, &d_p);
} else {
d_n = d_p = 1;
}
if (d_n > 0) {
d_n = 0;
}
if (d_p < 0) {
d_p = 0;
}
d_n = -d_n;
network net;
/* Transform the igraph_t */
IGRAPH_CHECK(igraph_i_read_network_spinglass(graph, weights,
&net, use_weights));
bool directed = igraph_is_directed(graph);
PottsModelN pm(&net, spins, directed);
if ((stoptemp == 0.0) && (starttemp == 0.0)) {
zeroT = true;
} else {
zeroT = false;
}
//Begin at a high enough temperature
kT = pm.FindStartTemp(gamma, gamma_minus, starttemp);
/* assign random initial configuration */
pm.assign_initial_conf(true);
runs = 0;
while (kT / stoptemp > 1.0 || (zeroT && runs < 150)) {
IGRAPH_ALLOW_INTERRUPTION();
runs++;
kT = kT * coolfact;
acc = pm.HeatBathLookup(gamma, gamma_minus, kT, 50);
if (acc < (1.0 - 1.0 / double(spins)) * 0.001) {
break;
}
} /* while loop */
/* These are needed, otherwise 'modularity' is not calculated */
igraph_matrix_t adhesion, normalized_adhesion;
igraph_real_t polarization;
IGRAPH_MATRIX_INIT_FINALLY(&adhesion, 0, 0);
IGRAPH_MATRIX_INIT_FINALLY(&normalized_adhesion, 0, 0);
pm.WriteClusters(modularity, temperature, csize, membership,
&adhesion, &normalized_adhesion, &polarization,
kT, d_p, d_n);
igraph_matrix_destroy(&normalized_adhesion);
igraph_matrix_destroy(&adhesion);
IGRAPH_FINALLY_CLEAN(2);
return IGRAPH_SUCCESS;
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,164 @@
/*
igraph library.
Copyright (C) 2006-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Jörg Reichardt
This file was modified by Vincent Traag
The original copyright notice follows here */
/***************************************************************************
pottsmodel.h - description
-------------------
begin : Fri May 28 2004
copyright : (C) 2004 by
email :
***************************************************************************/
/***************************************************************************
* *
* 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. *
* *
***************************************************************************/
#ifndef POTTSMODEL_H
#define POTTSMODEL_H
#include "NetDataTypes.h"
#include "igraph_types.h"
#include "igraph_vector.h"
#include "igraph_matrix.h"
// Simple matrix class with heap allocation, allowing mat[i][j] indexing.
class SimpleMatrix {
double *data;
const size_t n;
public:
explicit SimpleMatrix(size_t n_) : n(n_) { data = new double[n*n]; }
SimpleMatrix(const SimpleMatrix &) = delete;
~SimpleMatrix() { delete [] data; }
// Return a pointer to the i'th column, which can be indexed into using a second [] operator.
// We assume column-major storage.
double *operator [] (size_t i) { return &(data[n*i]); }
};
class PottsModel {
private:
//these lists are needed to keep track of spin states for parallel update mode
DL_Indexed_List<igraph_int_t*> new_spins;
DL_Indexed_List<igraph_int_t*> previous_spins;
HugeArray<HugeArray<double>*> correlation;
network *net;
igraph_int_t q;
unsigned int operation_mode;
SimpleMatrix Qmatrix;
double* Qa;
double* weights;
double total_degree_sum;
igraph_int_t num_of_nodes;
igraph_int_t num_of_links;
igraph_int_t k_max = 0;
double acceptance = 0;
double* neighbours;
double* color_field;
public:
PottsModel(network *net, igraph_int_t q, int norm_by_degree);
~PottsModel();
igraph_int_t assign_initial_conf(igraph_int_t spin);
double initialize_Qmatrix();
double calculate_Q();
double FindStartTemp(double gamma, double prob, double ts);
igraph_int_t HeatBathParallelLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps);
double HeatBathLookupZeroTemp(double gamma, double prob, unsigned int max_sweeps);
igraph_int_t HeatBathParallelLookup(double gamma, double prob, double kT, unsigned int max_sweeps);
double HeatBathLookup(double gamma, double prob, double kT, unsigned int max_sweeps);
igraph_int_t WriteClusters(igraph_real_t *modularity,
igraph_real_t *temperature,
igraph_vector_int_t *csize, igraph_vector_int_t *membership,
double kT, double gamma) const;
double FindCommunityFromStart(double gamma, const char *nodename,
igraph_vector_int_t *result,
igraph_real_t *cohesion,
igraph_real_t *adhesion,
igraph_real_t *inner_links,
igraph_real_t *outer_links) const;
};
class PottsModelN {
private:
HugeArray<HugeArray<double>*> correlation;
network *net;
igraph_int_t q; //number of communities
double m_p; //number of positive ties (or sum of degrees), this equals the number of edges only if it is undirected and each edge has a weight of 1
double m_n; //number of negative ties (or sum of degrees)
igraph_int_t num_nodes; //number of nodes
bool is_directed;
bool is_init = false;
double *degree_pos_in = nullptr; //Postive indegree of the nodes (or sum of weights)
double *degree_neg_in = nullptr; //Negative indegree of the nodes (or sum of weights)
double *degree_pos_out = nullptr; //Postive outdegree of the nodes (or sum of weights)
double *degree_neg_out = nullptr; //Negative outdegree of the nodes (or sum of weights)
double *degree_community_pos_in = nullptr; //Positive sum of indegree for communities
double *degree_community_neg_in = nullptr; //Negative sum of indegree for communities
double *degree_community_pos_out = nullptr; //Positive sum of outegree for communities
double *degree_community_neg_out = nullptr; //Negative sum of outdegree for communities
igraph_int_t *csize = nullptr; //The number of nodes in each community
igraph_int_t *spin = nullptr; //The membership of each node
double *neighbours = nullptr; //Array of neighbours of a vertex in each community
double *weights = nullptr; //Weights of all possible transitions to another community
public:
PottsModelN(network *n, igraph_int_t num_communities, bool directed);
~PottsModelN();
void assign_initial_conf(bool init_spins);
double FindStartTemp(double gamma, double lambda, double ts);
double HeatBathLookup(double gamma, double lambda, double t, unsigned int max_sweeps);
igraph_int_t WriteClusters(igraph_real_t *modularity,
igraph_real_t *temperature,
igraph_vector_int_t *community_size,
igraph_vector_int_t *membership,
igraph_matrix_t *adhesion,
igraph_matrix_t *normalised_adhesion,
igraph_real_t *polarization,
double t,
double d_p,
double d_n);
};
#endif
@@ -0,0 +1,645 @@
/*
igraph library.
Copyright (C) 2023 The igraph development team <igraph@igraph.org>
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, see <https://www.gnu.org/licenses/>.
*/
#include "igraph_community.h"
#include "igraph_adjlist.h"
#include "igraph_bitset.h"
#include "igraph_interface.h"
#include "igraph_iterators.h"
#include "igraph_nongraph.h"
#include "igraph_paths.h"
#include "igraph_structural.h"
#include "igraph_transitivity.h"
#include "core/indheap.h"
/**
* Unweighted local relative density for some vertices.
*
* This function ignores self-loops and edge multiplicities.
* For isolated vertices, zero is returned.
*
* \param graph The input graph.
* \param res Pointer to a vector, the result will be stored here.
* \param vs Vertex selector, the vertices for which to perform the calculation.
* \return Error code.
*
* Time complexity: TODO.
*/
static igraph_error_t igraph_i_local_relative_density(const igraph_t *graph, igraph_vector_t *res, igraph_vs_t vs) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t vs_size;
igraph_vector_int_t nei_mask; /* which nodes are in the local neighbourhood? */
igraph_vector_int_t nei_done; /* which local nodes have already been processed? -- avoids duplicate processing in multigraphs */
igraph_lazy_adjlist_t al;
igraph_vit_t vit;
IGRAPH_CHECK(igraph_lazy_adjlist_init(graph, &al, IGRAPH_ALL, IGRAPH_LOOPS, IGRAPH_MULTIPLE));
IGRAPH_FINALLY(igraph_lazy_adjlist_destroy, &al);
IGRAPH_VECTOR_INT_INIT_FINALLY(&nei_mask, no_of_nodes);
IGRAPH_VECTOR_INT_INIT_FINALLY(&nei_done, no_of_nodes);
IGRAPH_CHECK(igraph_vit_create(graph, vs, &vit));
IGRAPH_FINALLY(igraph_vit_destroy, &vit);
vs_size = IGRAPH_VIT_SIZE(vit);
IGRAPH_CHECK(igraph_vector_resize(res, vs_size));
for (igraph_int_t i=0; ! IGRAPH_VIT_END(vit); IGRAPH_VIT_NEXT(vit), i++) {
igraph_int_t w = IGRAPH_VIT_GET(vit);
igraph_int_t int_count = 0, ext_count = 0;
igraph_vector_int_t *w_neis = igraph_lazy_adjlist_get(&al, w);
IGRAPH_CHECK_OOM(w_neis, "Cannot calculate local relative density.");
igraph_int_t dw = igraph_vector_int_size(w_neis);
/* mark neighbours of w, as well as w itself */
for (igraph_int_t j=0; j < dw; ++j) {
VECTOR(nei_mask)[ VECTOR(*w_neis)[j] ] = i + 1;
}
VECTOR(nei_mask)[w] = i + 1;
/* all incident edges of w are internal */
int_count += dw;
VECTOR(nei_done)[w] = i + 1;
for (igraph_int_t j=0; j < dw; ++j) {
igraph_int_t v = VECTOR(*w_neis)[j];
if (VECTOR(nei_done)[v] == i + 1) {
continue;
} else {
VECTOR(nei_done)[v] = i + 1;
}
igraph_vector_int_t *v_neis = igraph_lazy_adjlist_get(&al, v);
IGRAPH_CHECK_OOM(v_neis, "Cannot calculate local relative density.");
igraph_int_t dv = igraph_vector_int_size(v_neis);
for (igraph_int_t k=0; k < dv; ++k) {
igraph_int_t u = VECTOR(*v_neis)[k];
if (VECTOR(nei_mask)[u] == i + 1) {
int_count += 1;
} else {
ext_count += 1;
}
}
}
IGRAPH_ASSERT(int_count % 2 == 0);
int_count /= 2;
VECTOR(*res)[i] = int_count == 0 ? 0.0 : (igraph_real_t) int_count / (igraph_real_t) (int_count + ext_count);
}
igraph_vit_destroy(&vit);
igraph_vector_int_destroy(&nei_done);
igraph_vector_int_destroy(&nei_mask);
igraph_lazy_adjlist_destroy(&al);
IGRAPH_FINALLY_CLEAN(4);
return IGRAPH_SUCCESS;
}
/* Weighted local density: we simply multiply the unweighted local relative density with the undirected strength. */
static igraph_error_t weighted_local_density(const igraph_t *graph, igraph_vector_t *res, const igraph_vector_t *weights) {
igraph_vector_t str;
IGRAPH_CHECK(igraph_i_local_relative_density(graph, res, igraph_vss_all()));
IGRAPH_VECTOR_INIT_FINALLY(&str, igraph_vcount(graph));
IGRAPH_CHECK(igraph_strength(graph, &str, igraph_vss_all(), IGRAPH_ALL, IGRAPH_NO_LOOPS, weights));
igraph_vector_mul(res, &str);
igraph_vector_destroy(&str);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
/**
* Chooses the generated points for the Voronoi partitioning.
*
* Each generator has the highest local density within a radius \p r around it.
*
* Additionally, if rmax != NULL, the longest distance reached will be stored here.
* This may be smaller than \p r. This feature is used to determine the largest r
* value worth considering, through calling this function with r = INFINITY.
*/
static igraph_error_t choose_generators(
const igraph_t *graph,
igraph_vector_int_t *generators,
igraph_real_t *rmax,
const igraph_vector_t *local_rel_dens,
const igraph_vector_t *lengths,
igraph_neimode_t mode,
igraph_real_t r) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_vector_int_t ord;
igraph_bitset_t excluded;
igraph_int_t excluded_count;
igraph_inclist_t il;
igraph_2wheap_t q;
igraph_real_t radius_max;
/* ord[i] is the index of the ith largest element of local_rel_dens */
IGRAPH_VECTOR_INT_INIT_FINALLY(&ord, 0);
IGRAPH_CHECK(igraph_vector_sort_ind(local_rel_dens, &ord, IGRAPH_DESCENDING));
/* If excluded[v] is true, then v is closer to some already chosen generator than r */
IGRAPH_BITSET_INIT_FINALLY(&excluded, no_of_nodes);
excluded_count = 0;
/* The input graph is expected to be simple, but we still set IGRAPH_LOOPS,
* as inclist_init() performs better this way. */
IGRAPH_CHECK(igraph_inclist_init(graph, &il, mode, IGRAPH_LOOPS));
IGRAPH_FINALLY(igraph_inclist_destroy, &il);
IGRAPH_CHECK(igraph_2wheap_init(&q, no_of_nodes));
IGRAPH_FINALLY(igraph_2wheap_destroy, &q);
radius_max = -IGRAPH_INFINITY;
igraph_vector_int_clear(generators);
for (igraph_int_t i=0; i < no_of_nodes; i++) {
igraph_int_t g = VECTOR(ord)[i];
if (IGRAPH_BIT_TEST(excluded, g)) continue;
IGRAPH_CHECK(igraph_vector_int_push_back(generators, g));
igraph_2wheap_clear(&q);
IGRAPH_CHECK(igraph_2wheap_push_with_index(&q, g, -0.0));
while (!igraph_2wheap_empty(&q)) {
igraph_int_t vid = igraph_2wheap_max_index(&q);
igraph_real_t mindist = -igraph_2wheap_deactivate_max(&q);
/* Exceeded cutoff distance, do not search further along this path. */
if (mindist > r) continue;
/* Note: We cannot stop the search after hitting an excluded vertex
* because it is possible that another non-excluded one is reachable only
* through this one. */
if (! IGRAPH_BIT_TEST(excluded, vid)) {
IGRAPH_BIT_SET(excluded, vid);
excluded_count++;
}
if (mindist > radius_max) {
radius_max = mindist;
}
igraph_vector_int_t *inc_edges = igraph_inclist_get(&il, vid);
igraph_int_t inc_count = igraph_vector_int_size(inc_edges);
for (igraph_int_t j=0; j < inc_count; j++) {
igraph_int_t edge = VECTOR(*inc_edges)[j];
igraph_real_t weight = VECTOR(*lengths)[edge];
/* Optimization: do not follow infinite-length edges. */
if (weight == IGRAPH_INFINITY) {
continue;
}
igraph_int_t to = IGRAPH_OTHER(graph, edge, vid);
igraph_real_t altdist = mindist + weight;
if (!igraph_2wheap_has_elem(&q, to)) {
/* This is the first non-infinite distance */
IGRAPH_CHECK(igraph_2wheap_push_with_index(&q, to, -altdist));
} else if (igraph_2wheap_has_active(&q, to)) {
igraph_real_t curdist = -igraph_2wheap_get(&q, to);
if (altdist < curdist) {
/* This is a shorter path */
igraph_2wheap_modify(&q, to, -altdist);
}
}
}
}
/* All vertices have been excluded, no need to search further. */
if (excluded_count == no_of_nodes) break;
}
if (rmax) {
*rmax = radius_max;
}
igraph_2wheap_destroy(&q);
igraph_inclist_destroy(&il);
igraph_bitset_destroy(&excluded);
igraph_vector_int_destroy(&ord);
IGRAPH_FINALLY_CLEAN(4);
return IGRAPH_SUCCESS;
}
/* Find the smallest and largest reasonable values of r to consider for the purpose
* of choosing generator points. */
static igraph_error_t estimate_minmax_r(
const igraph_t *graph,
const igraph_vector_t *local_rel_dens,
const igraph_vector_t *lengths,
igraph_neimode_t mode,
igraph_real_t *minr, igraph_real_t *maxr) {
igraph_vector_int_t generators;
/* As minimum distance, we use the shortest edge length. This may be shorter than the shortest
* incident edge of a generator point, but underestimating the minimum distance does not affect
* the radius optimization negatively. */
*minr = igraph_vector_min(lengths);
/* To determine the maximum distance, we run a generator selection with r=INFINITY,
* and record the longest actual distance encountered in the process. */
IGRAPH_VECTOR_INT_INIT_FINALLY(&generators, 0);
IGRAPH_CHECK(choose_generators(graph, &generators, maxr, local_rel_dens, lengths, mode, IGRAPH_INFINITY));
igraph_vector_int_destroy(&generators);
IGRAPH_FINALLY_CLEAN(1);
return IGRAPH_SUCCESS;
}
typedef igraph_error_t optfun_t(double x, double *res, void *extra);
/* This is the coefficient of the second order part when fitting a quadratic
* polynomial to the points given in the argument. */
static igraph_real_t coeff2(
igraph_real_t x1, igraph_real_t x2, igraph_real_t x3,
igraph_real_t f1, igraph_real_t f2, igraph_real_t f3) {
igraph_real_t num = x1*(f3 - f2) + x2*(f1 - f3) + x3*(f2 - f1);
igraph_real_t denom = (x1 - x2)*(x1 - x3)*(x2 - x3);
return num / denom;
}
/* Given the stationary point of the quadratic fit to the given points */
static igraph_real_t peakx(
igraph_real_t x1, igraph_real_t x2, igraph_real_t x3,
igraph_real_t f1, igraph_real_t f2, igraph_real_t f3) {
igraph_real_t x1s = x1*x1, x2s = x2*x2, x3s = x3*x3;
igraph_real_t num = f3 * (x1s - x2s) + f1 * (x2s - x3s) + f2 * (x3s - x1s);
igraph_real_t denom = f3 * (x1 - x2) + f1 * (x2 - x3) + f2 * (x3 - x1);
return 0.5 * num / denom;
}
/**
* Simple Brent's method optimizer, with some specializations for the use
* case at hand (see code comments). It must be called with x2 > x1.
* The optimal argument is the last one for which f() is invoked.
* f() is expected to record this in 'extra'.
*/
static igraph_error_t brent_opt(optfun_t *f, igraph_real_t x1, igraph_real_t x2, void *extra) {
igraph_real_t lo = x1, hi = x2;
IGRAPH_ASSERT(isfinite(lo));
IGRAPH_ASSERT(isfinite(hi));
/* We choose the initial x3 point to be closer to x1 than x2.
* This is so that if f1 == f2, the next computed point (newx)
* would not coincide with x3. */
igraph_real_t x3 = 0.6*x1 + 0.4*x2;
igraph_real_t f1, f2, f3;
IGRAPH_CHECK(f(x1, &f1, extra));
/* Catch special case that would wreak havoc in the optimizer. */
if (x1 == x2) {
return IGRAPH_SUCCESS;
}
IGRAPH_CHECK(f(x2, &f2, extra));
IGRAPH_CHECK(f(x3, &f3, extra));
/* We expect that the middle point, f3, is greater than the boundary points. */
/* Currently, we do not handle the case when f3 < f1. */
if (f1 > f3) {
IGRAPH_ERROR("Optimizer did not converge while maximizing modularity for Voronoi communities.",
IGRAPH_DIVERGED);
}
/* It sometimes happens in disconnected graphs that the maximum is reached at or near the
* top of the radius range. If so, we bisect the (x3, x2) interval to search for a configuration
* where f3 >= f2. */
if (f2 > f3) {
/* Limit iterations to 'maxiter'. */
const int maxiter = 10;
int i;
for (i=0; i < maxiter; ++i) {
x1 = x3; f1 = f3;
x3 = 0.5 * (x1 + x2);
IGRAPH_CHECK(f(x3, &f3, extra));
if (f3 >= f2) break;
}
/* If no maximum was found in 'maxiter' bisections, just take the upper end of the range. */
if (i == maxiter) {
IGRAPH_CHECK(f(x2, &f2, extra));
return IGRAPH_SUCCESS;
}
}
/* Limit iterations to 20 */
for (int i=0; i < 20; ++i) {
igraph_real_t newx, newf;
newx = peakx(x1, x2, x3, f1, f2, f3);
IGRAPH_CHECK(f(newx, &newf, extra));
/* We need to decide whether we drop (x1, f1) or (x2, f2) for the following iterations.
* The sign of a1 (or a2) determines whether dropping x1 (or x2) yields a convex or concave
* parabola in the next iteration. We need a negative sign = concave parabola,
* as we are looking for a maximum. We always keep (x3, f3) as it was the last added point. */
igraph_real_t a1 = coeff2(x2, x3, newx, f2, f3, newf);
igraph_real_t a2 = coeff2(x1, x3, newx, f1, f3, newf);
/* We cannot continue without the Brent optimizer switching to minimization.
* Terminate search, accepting the current result. */
if (a1 >= 0 && a2 >= 0) {
break;
}
if (a1 <= a2) {
x1 = x2;
x2 = x3;
x3 = newx;
f1 = f2;
f2 = f3;
f3 = newf;
} else {
x2 = x1;
x1 = x3;
x3 = newx;
f2 = f1;
f1 = f3;
f3 = newf;
}
/* Check if value goes out of initial interval. */
if (x3 < lo || x3 > hi) {
IGRAPH_ERROR("Optimizer did not converge while maximizing modularity for Voronoi communities.",
IGRAPH_DIVERGED);
}
/* We exploit the fact that we are optimizing a discrete valued function, and we can
* detect convergence by checking that the function value stays exactly the same.
*
* As an optimization, we only check whether the two of the three f values are the same.
* Almost always, when this is the case, another iteration would not yield a better
* maximum, however, saving a call to f() improves performance noticeably.
*/
const igraph_real_t eps = 1e-10;
int c1 = igraph_cmp_epsilon(f1, f3, eps);
int c2 = igraph_cmp_epsilon(f2, f3, eps);
if (c1 == 0 || c2 == 0) {
break;
}
}
return IGRAPH_SUCCESS;
}
/* Work data for get_modularity() */
typedef struct {
const igraph_t *graph;
const igraph_vector_t *local_dens;
const igraph_vector_t *lengths;
const igraph_vector_t *weights;
igraph_neimode_t mode;
igraph_vector_int_t *generators;
igraph_vector_int_t *membership;
igraph_real_t modularity;
} get_modularity_work_t;
/* Objective function used with brent_opt(), it computes the modularity for a given radius. */
static igraph_error_t get_modularity(igraph_real_t r, igraph_real_t *modularity, void *extra) {
get_modularity_work_t *gm = extra;
IGRAPH_CHECK(choose_generators(gm->graph, gm->generators, NULL,
gm->local_dens, gm->lengths, gm->mode,
r));
IGRAPH_CHECK(igraph_voronoi(gm->graph, gm->membership, NULL,
gm->generators, gm->lengths,
gm->mode, IGRAPH_VORONOI_RANDOM));
IGRAPH_CHECK(igraph_modularity(gm->graph, gm->membership, gm->weights,
1, gm->mode == IGRAPH_ALL ? IGRAPH_UNDIRECTED : IGRAPH_DIRECTED,
&gm->modularity));
*modularity = gm->modularity;
return IGRAPH_SUCCESS;
}
/**
* \function igraph_community_voronoi
* \brief Finds communities using Voronoi partitioning.
*
* \experimental
*
* This function finds communities using a Voronoi partitioning of vertices based
* on the given edge lengths divided by the edge clustering coefficient
* (\ref igraph_ecc()). The generator vertices are chosen to be those with the
* largest local relative density within a radius \p r, with the local relative
* density of a vertex defined as
* <code>s m / (m + k)</code>, where \c s is the strength of the vertex,
* \c m is the number of edges within the vertex's first order neighborhood,
* while \c k is the number of edges with only one endpoint within this
* neighborhood.
*
* </para><para>
* References:
*
* </para><para>
* Deritei et al., Community detection by graph Voronoi diagrams,
* New Journal of Physics 16, 063007 (2014)
* https://doi.org/10.1088/1367-2630/16/6/063007
*
* </para><para>
* Molnár et al., Community Detection in Directed Weighted Networks using Voronoi Partitioning,
* Scientific Reports 14, 8124 (2024)
* https://doi.org/10.1038/s41598-024-58624-4
*
* \param graph The input graph. It must be simple.
* \param membership If not \c NULL, the membership of each vertex is returned here.
* \param generators If not \c NULL, the generator points used for Voronoi partitioning are returned here.
* \param modularity If not \c NULL, the modularity score of the partitioning is returned here.
* \param lengths Edge lengths, or \c NULL to consider all edges as having unit length.
* Voronoi partitioning will use edge lengths equal to lengths / ECC where ECC is the edge
* clustering coefficient.
* \param weights Edge weights, or \c NULL to consider all edges as having unit weight.
* Weights are used when selecting generator points, as well as for computing modularity.
* \param mode If \c IGRAPH_OUT, distances from generator points to all other nodes are considered.
* If \c IGRAPH_IN, the reverse distances are used. If \c IGRAPH_ALL, edge directions are ignored.
* This parameter is ignored for undirected graphs.
* \param r The radius/resolution to use when selecting generator points. The larger this value, the
* fewer partitions there will be. Pass in a negative value to automatically select the radius
* that maximizes modularity.
* \return Error code.
*
* \sa \ref igraph_voronoi(), \ref igraph_ecc().
*
* Time complexity: TODO.
*/
igraph_error_t igraph_community_voronoi(
const igraph_t *graph,
igraph_vector_int_t *membership, igraph_vector_int_t *generators,
igraph_real_t *modularity,
const igraph_vector_t *lengths, const igraph_vector_t *weights,
igraph_neimode_t mode, igraph_real_t r) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t no_of_edges = igraph_ecount(graph);
igraph_vector_t local_rel_dens;
igraph_vector_t lengths2; /* lengths2 = lengths / ecc */
igraph_vector_int_t imembership, igenerators;
igraph_vector_int_t *pmembership, *pgenerators;
igraph_bool_t simple;
if (! igraph_is_directed(graph)) {
mode = IGRAPH_ALL;
}
if (lengths && igraph_vector_size(lengths) != no_of_edges) {
IGRAPH_ERROR("Edge length vector size does not match edge count.", IGRAPH_EINVAL);
}
if (weights && igraph_vector_size(weights) != no_of_edges) {
IGRAPH_ERROR("Edge length vector size does not match edge count.", IGRAPH_EINVAL);
}
IGRAPH_CHECK(igraph_is_simple(graph, &simple, mode == IGRAPH_ALL ? IGRAPH_UNDIRECTED : IGRAPH_DIRECTED));
if (! simple) {
IGRAPH_ERROR("The graph must be simple for Voronoi communities.", IGRAPH_EINVAL);
}
if (no_of_edges == 0) {
/* Also handles no_of_nodes <= 1 */
if (membership) {
IGRAPH_CHECK(igraph_vector_int_range(membership, 0, no_of_nodes));
}
if (generators) {
IGRAPH_CHECK(igraph_vector_int_range(generators, 0, no_of_nodes));
}
return IGRAPH_SUCCESS;
}
if (! generators) {
IGRAPH_VECTOR_INT_INIT_FINALLY(&igenerators, no_of_nodes);
pgenerators = &igenerators;
} else {
pgenerators = generators;
}
if (! membership) {
IGRAPH_VECTOR_INT_INIT_FINALLY(&imembership, no_of_nodes);
pmembership = &imembership;
} else {
pmembership = membership;
}
if (lengths) {
igraph_real_t m = igraph_vector_min(lengths);
if (isnan(m)) {
IGRAPH_ERROR("Edge lengths must not be NaN.", IGRAPH_EINVAL);
}
if (m < 0) {
IGRAPH_ERROR("Edge lengths must be non-negative.", IGRAPH_EINVAL);
}
}
if (weights) {
igraph_real_t m = igraph_vector_min(weights);
if (isnan(m)) {
IGRAPH_ERROR("Edge weights must not be NaN.", IGRAPH_EINVAL);
}
if (m <= 0) {
IGRAPH_ERROR("Edge weights must be positive.", IGRAPH_EINVAL);
}
}
IGRAPH_VECTOR_INIT_FINALLY(&local_rel_dens, 0);
IGRAPH_CHECK(weighted_local_density(graph, &local_rel_dens, weights));
IGRAPH_VECTOR_INIT_FINALLY(&lengths2, 0);
IGRAPH_CHECK(igraph_ecc(graph, &lengths2, igraph_ess_all(IGRAPH_EDGEORDER_ID), 3, true, true));
/* Note: ECC is never NaN but it may be Inf */
for (igraph_int_t i=0; i < no_of_edges; i++) {
VECTOR(lengths2)[i] = 1 / (VECTOR(lengths2)[i]);
}
if (lengths) {
igraph_vector_mul(&lengths2, lengths);
}
if (r < 0) {
igraph_real_t minr, maxr;
IGRAPH_CHECK(estimate_minmax_r(graph, &local_rel_dens, &lengths2, mode, &minr, &maxr));
get_modularity_work_t gm = {
graph,
&local_rel_dens,
&lengths2,
weights,
mode,
pgenerators,
pmembership,
/* modularity */ IGRAPH_NAN
};
IGRAPH_CHECK(brent_opt(get_modularity, minr, maxr, &gm));
if (modularity) {
*modularity = gm.modularity;
}
} else {
IGRAPH_CHECK(choose_generators(graph, pgenerators, NULL, &local_rel_dens, &lengths2, mode, r));
IGRAPH_CHECK(igraph_voronoi(graph, membership, NULL, pgenerators, &lengths2, mode, IGRAPH_VORONOI_RANDOM));
if (modularity) {
IGRAPH_CHECK(igraph_modularity(graph, pmembership, weights, 1,
mode == IGRAPH_ALL ? IGRAPH_UNDIRECTED : IGRAPH_DIRECTED, modularity));
}
}
if (! generators) {
igraph_vector_int_destroy(&igenerators);
IGRAPH_FINALLY_CLEAN(1);
}
if (! membership) {
igraph_vector_int_destroy(&imembership);
IGRAPH_FINALLY_CLEAN(1);
}
igraph_vector_destroy(&local_rel_dens);
igraph_vector_destroy(&lengths2);
IGRAPH_FINALLY_CLEAN(2);
return IGRAPH_SUCCESS;
}
@@ -0,0 +1,233 @@
/*
igraph library.
Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Pascal Pons
The original copyright notice follows here. The FSF address was
fixed by Tamas Nepusz */
// File: walktrap.cpp
//-----------------------------------------------------------------------------
// Walktrap v0.2 -- Finds community structure of networks using random walks
// Copyright (C) 2004-2005 Pascal Pons
//
// 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
//-----------------------------------------------------------------------------
// Author : Pascal Pons
// Email : pascal.pons@gmail.com
// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html
// Location : Paris, France
// Time : June 2005
//-----------------------------------------------------------------------------
// see readme.txt for more details
#include "walktrap_graph.h"
#include "walktrap_communities.h"
#include "igraph_community.h"
#include "igraph_components.h"
#include "igraph_interface.h"
#include "core/exceptions.h"
#include "core/interruption.h"
#include <climits>
#include <cmath>
// This is necessary for GCC 5 and earlier, where including <cmath>
// makes isnan() unusable without the std:: prefix, even if <math.h>
// was included as well.
using std::isnan;
using namespace igraph::walktrap;
/**
* \function igraph_community_walktrap
* \brief Community finding using a random walk based similarity measure.
*
* This function is the implementation of the Walktrap community
* finding algorithm, see Pascal Pons, Matthieu Latapy: Computing
* communities in large networks using random walks,
* https://arxiv.org/abs/physics/0512106
*
* </para><para>
* Currently the original C++ implementation is used in igraph,
* see https://www-complexnetworks.lip6.fr/~latapy/PP/walktrap.html
* We are grateful to Matthieu Latapy and Pascal Pons for providing this
* source code.
*
* </para><para>
* In contrast to the original implementation, isolated vertices are allowed
* in the graph and they are assumed to have a single incident loop edge with
* weight 1.
*
* \param graph The input graph, edge directions are ignored.
* \param weights Numeric vector giving the weights of the edges.
* If it is a NULL pointer then all edges will have equal
* weights. The weights are expected to be positive.
* \param steps Integer constant, the length of the random walks.
* Typically, good results are obtained with values between
* 3-8 with 4-5 being a reasonable default.
* \param merges Pointer to a matrix, the merges performed by the
* algorithm will be stored here (if not \c NULL). Each merge is a
* row in a two-column matrix and contains the IDs of the merged
* clusters. Clusters are numbered from zero and cluster numbers
* smaller than the number of nodes in the network belong to the
* individual vertices as singleton clusters. In each step a new
* cluster is created from two other clusters and its id will be
* one larger than the largest cluster id so far. This means that
* before the first merge we have \c n clusters (the number of
* vertices in the graph) numbered from zero to <code>n - 1</code>.
* The first merge creates cluster \c n, the second cluster
* <code>n + 1</code>, etc.
* \param modularity Pointer to a vector. If not \c NULL then the
* modularity score of the current clustering is stored here after
* each merge operation.
* \param membership Pointer to a vector. If not a \c NULL pointer, then
* the membership vector corresponding to the maximal modularity
* score is stored here.
* \return Error code.
*
* \sa \ref igraph_community_spinglass(), \ref
* igraph_community_edge_betweenness().
*
* Time complexity: O(|E||V|^2) in the worst case, O(|V|^2 log|V|) typically,
* |V| is the number of vertices, |E| is the number of edges.
*
* \example examples/simple/walktrap.c
*/
igraph_error_t igraph_community_walktrap(const igraph_t *graph,
const igraph_vector_t *weights,
igraph_int_t steps,
igraph_matrix_int_t *merges,
igraph_vector_t *modularity,
igraph_vector_int_t *membership) {
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t no_of_edges = igraph_ecount(graph);
igraph_int_t comp_count;
igraph_matrix_int_t imerges, *pmerges = merges;
igraph_vector_t imodularity, *pmodularity = modularity;
if (steps <= 0) {
IGRAPH_ERROR("Length of random walks must be positive for walktrap community detection.", IGRAPH_EINVAL);
}
if (steps > INT_MAX) {
IGRAPH_ERROR("Length of random walks too large for walktrap community detection.", IGRAPH_EINVAL);
}
int length = steps;
if (weights) {
if (igraph_vector_size(weights) != no_of_edges) {
IGRAPH_ERROR("Invalid weight vector length.", IGRAPH_EINVAL);
}
if (no_of_edges > 0) {
igraph_real_t minweight = igraph_vector_min(weights);
if (minweight < 0) {
IGRAPH_ERROR("Weight vector must be non-negative.", IGRAPH_EINVAL);
} else if (isnan(minweight)) {
IGRAPH_ERROR("Weight vector must not contain NaN values.", IGRAPH_EINVAL);
}
}
}
if (membership) {
/* We need both 'modularity' and 'merges' to compute 'membership'.
* If they were not provided by the called, we allocate these here. */
if (! modularity) {
IGRAPH_VECTOR_INIT_FINALLY(&imodularity, 0);
pmodularity = &imodularity;
}
if (! merges) {
IGRAPH_MATRIX_INT_INIT_FINALLY(&imerges, 0, 0);
pmerges = &imerges;
}
}
IGRAPH_HANDLE_EXCEPTIONS(
Graph G;
IGRAPH_CHECK(G.convert_from_igraph(graph, weights));
if (pmerges || pmodularity) {
IGRAPH_CHECK(igraph_connected_components(graph, /*membership=*/ NULL, /*csize=*/ NULL,
&comp_count, IGRAPH_WEAK));
}
if (pmerges) {
IGRAPH_CHECK(igraph_matrix_int_resize(pmerges, no_of_nodes - comp_count, 2));
}
if (pmodularity) {
IGRAPH_CHECK(igraph_vector_resize(pmodularity, no_of_nodes - comp_count + 1));
igraph_vector_null(pmodularity);
}
Communities C(&G, length, pmerges, pmodularity);
while (!C.H->is_empty()) {
IGRAPH_ALLOW_INTERRUPTION();
C.merge_nearest_communities();
}
);
if (membership) {
igraph_int_t m;
m = no_of_nodes > 0 ? igraph_vector_which_max(pmodularity) : 0;
IGRAPH_CHECK(igraph_community_to_membership(pmerges, no_of_nodes,
/*steps=*/ m,
membership,
/*csize=*/ NULL));
if (! merges) {
igraph_matrix_int_destroy(&imerges);
IGRAPH_FINALLY_CLEAN(1);
}
if (! modularity) {
igraph_vector_destroy(&imodularity);
IGRAPH_FINALLY_CLEAN(1);
}
}
/* The walktrap implementation cannot work with NaN values internally,
* and produces 0 for the modularity of edgeless graphs. We correct
* this to NaN in the last step for consistency. */
if (modularity && no_of_edges == 0) {
VECTOR(*modularity)[0] = IGRAPH_NAN;
}
return IGRAPH_SUCCESS;
}
@@ -0,0 +1,785 @@
/*
igraph library.
Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Pascal Pons
The original copyright notice follows here. The FSF address was
fixed by Tamas Nepusz */
// File: communities.cpp
//-----------------------------------------------------------------------------
// Walktrap v0.2 -- Finds community structure of networks using random walks
// Copyright (C) 2004-2005 Pascal Pons
//
// 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
//-----------------------------------------------------------------------------
// Author : Pascal Pons
// Email : pascal.pons@gmail.com
// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html
// Location : Paris, France
// Time : June 2005
//-----------------------------------------------------------------------------
// see readme.txt for more details
#include "walktrap_communities.h"
#include "config.h" /* IGRAPH_THREAD_LOCAL */
#include <algorithm>
#include <cmath>
using namespace std;
namespace igraph {
namespace walktrap {
IGRAPH_THREAD_LOCAL int Probabilities::length = 0;
IGRAPH_THREAD_LOCAL Communities* Probabilities::C = nullptr;
IGRAPH_THREAD_LOCAL double* Probabilities::tmp_vector1 = nullptr;
IGRAPH_THREAD_LOCAL double* Probabilities::tmp_vector2 = nullptr;
IGRAPH_THREAD_LOCAL int* Probabilities::id = nullptr;
IGRAPH_THREAD_LOCAL int* Probabilities::vertices1 = nullptr;
IGRAPH_THREAD_LOCAL int* Probabilities::vertices2 = nullptr;
IGRAPH_THREAD_LOCAL int Probabilities::current_id = 0;
Neighbor::Neighbor() {
next_community1 = nullptr;
previous_community1 = nullptr;
next_community2 = nullptr;
previous_community2 = nullptr;
heap_index = -1;
}
Probabilities::~Probabilities() {
delete[] P;
delete[] vertices;
}
Probabilities::Probabilities(int community) {
Graph* G = C->G;
int nb_vertices1 = 0;
int nb_vertices2 = 0;
double initial_proba = 1. / static_cast<double>(C->communities[community].size);
int last = C->members[C->communities[community].last_member];
for (int m = C->communities[community].first_member; m != last; m = C->members[m]) {
tmp_vector1[m] = initial_proba;
vertices1[nb_vertices1++] = m;
}
for (int t = 0; t < length; t++) {
current_id++;
if (nb_vertices1 > (G->nb_vertices / 2)) {
nb_vertices2 = G->nb_vertices;
for (int i = 0; i < G->nb_vertices; i++) {
tmp_vector2[i] = 0.;
}
if (nb_vertices1 == G->nb_vertices) {
for (int i = 0; i < G->nb_vertices; i++) {
double proba = tmp_vector1[i] / G->vertices[i].total_weight;
for (int j = 0; j < G->vertices[i].degree; j++) {
tmp_vector2[G->vertices[i].edges[j].neighbor] += proba * G->vertices[i].edges[j].weight;
}
}
} else {
for (int i = 0; i < nb_vertices1; i++) {
int v1 = vertices1[i];
double proba = tmp_vector1[v1] / G->vertices[v1].total_weight;
for (int j = 0; j < G->vertices[v1].degree; j++) {
tmp_vector2[G->vertices[v1].edges[j].neighbor] += proba * G->vertices[v1].edges[j].weight;
}
}
}
} else {
nb_vertices2 = 0;
for (int i = 0; i < nb_vertices1; i++) {
int v1 = vertices1[i];
double proba = tmp_vector1[v1] / G->vertices[v1].total_weight;
for (int j = 0; j < G->vertices[v1].degree; j++) {
int v2 = G->vertices[v1].edges[j].neighbor;
if (id[v2] == current_id) {
tmp_vector2[v2] += proba * G->vertices[v1].edges[j].weight;
} else {
tmp_vector2[v2] = proba * G->vertices[v1].edges[j].weight;
id[v2] = current_id;
vertices2[nb_vertices2++] = v2;
}
}
}
}
double* tmp = tmp_vector2;
tmp_vector2 = tmp_vector1;
tmp_vector1 = tmp;
int* tmp2 = vertices2;
vertices2 = vertices1;
vertices1 = tmp2;
nb_vertices1 = nb_vertices2;
}
if (nb_vertices1 > (G->nb_vertices / 2)) {
P = new double[G->nb_vertices];
size = G->nb_vertices;
vertices = nullptr;
if (nb_vertices1 == G->nb_vertices) {
for (int i = 0; i < G->nb_vertices; i++) {
P[i] = tmp_vector1[i] / sqrt(G->vertices[i].total_weight);
}
} else {
for (int i = 0; i < G->nb_vertices; i++) {
P[i] = 0.;
}
for (int i = 0; i < nb_vertices1; i++) {
P[vertices1[i]] = tmp_vector1[vertices1[i]] / sqrt(G->vertices[vertices1[i]].total_weight);
}
}
} else {
P = new double[nb_vertices1];
size = nb_vertices1;
vertices = new int[nb_vertices1];
int j = 0;
for (int i = 0; i < G->nb_vertices; i++) {
if (id[i] == current_id) {
P[j] = tmp_vector1[i] / sqrt(G->vertices[i].total_weight);
vertices[j] = i;
j++;
}
}
}
}
Probabilities::Probabilities(int community1, int community2) {
// The two following probability vectors must exist.
// Do not call this function if it is not the case.
Probabilities* P1 = C->communities[community1].P;
Probabilities* P2 = C->communities[community2].P;
double w1 = C->communities[community1].size / static_cast<double>(C->communities[community1].size + C->communities[community2].size);
double w2 = C->communities[community2].size / static_cast<double>(C->communities[community1].size + C->communities[community2].size);
if (P1->size == C->G->nb_vertices) {
P = new double[C->G->nb_vertices];
size = C->G->nb_vertices;
vertices = nullptr;
if (P2->size == C->G->nb_vertices) { // two full vectors
for (int i = 0; i < C->G->nb_vertices; i++) {
P[i] = P1->P[i] * w1 + P2->P[i] * w2;
}
} else { // P1 full vector, P2 partial vector
int j = 0;
for (int i = 0; i < P2->size; i++) {
for (; j < P2->vertices[i]; j++) {
P[j] = P1->P[j] * w1;
}
P[j] = P1->P[j] * w1 + P2->P[i] * w2;
j++;
}
for (; j < C->G->nb_vertices; j++) {
P[j] = P1->P[j] * w1;
}
}
} else {
if (P2->size == C->G->nb_vertices) { // P1 partial vector, P2 full vector
P = new double[C->G->nb_vertices];
size = C->G->nb_vertices;
vertices = nullptr;
int j = 0;
for (int i = 0; i < P1->size; i++) {
for (; j < P1->vertices[i]; j++) {
P[j] = P2->P[j] * w2;
}
P[j] = P1->P[i] * w1 + P2->P[j] * w2;
j++;
}
for (; j < C->G->nb_vertices; j++) {
P[j] = P2->P[j] * w2;
}
} else { // two partial vectors
int i = 0;
int j = 0;
int nb_vertices1 = 0;
while ((i < P1->size) && (j < P2->size)) {
if (P1->vertices[i] < P2->vertices[j]) {
tmp_vector1[P1->vertices[i]] = P1->P[i] * w1;
vertices1[nb_vertices1++] = P1->vertices[i];
i++;
continue;
}
if (P1->vertices[i] > P2->vertices[j]) {
tmp_vector1[P2->vertices[j]] = P2->P[j] * w2;
vertices1[nb_vertices1++] = P2->vertices[j];
j++;
continue;
}
tmp_vector1[P1->vertices[i]] = P1->P[i] * w1 + P2->P[j] * w2;
vertices1[nb_vertices1++] = P1->vertices[i];
i++;
j++;
}
if (i == P1->size) {
for (; j < P2->size; j++) {
tmp_vector1[P2->vertices[j]] = P2->P[j] * w2;
vertices1[nb_vertices1++] = P2->vertices[j];
}
} else {
for (; i < P1->size; i++) {
tmp_vector1[P1->vertices[i]] = P1->P[i] * w1;
vertices1[nb_vertices1++] = P1->vertices[i];
}
}
if (nb_vertices1 > (C->G->nb_vertices / 2)) {
P = new double[C->G->nb_vertices];
size = C->G->nb_vertices;
vertices = nullptr;
for (int i = 0; i < C->G->nb_vertices; i++) {
P[i] = 0.;
}
for (int i = 0; i < nb_vertices1; i++) {
P[vertices1[i]] = tmp_vector1[vertices1[i]];
}
} else {
P = new double[nb_vertices1];
size = nb_vertices1;
vertices = new int[nb_vertices1];
for (int i = 0; i < nb_vertices1; i++) {
vertices[i] = vertices1[i];
P[i] = tmp_vector1[vertices1[i]];
}
}
}
}
}
double Probabilities::compute_distance(const Probabilities* P2) const {
double r = 0.0;
if (vertices) {
if (P2->vertices) { // two partial vectors
int i = 0;
int j = 0;
while ((i < size) && (j < P2->size)) {
if (vertices[i] < P2->vertices[j]) {
r += P[i] * P[i];
i++;
continue;
}
if (vertices[i] > P2->vertices[j]) {
r += P2->P[j] * P2->P[j];
j++;
continue;
}
r += (P[i] - P2->P[j]) * (P[i] - P2->P[j]);
i++;
j++;
}
if (i == size) {
for (; j < P2->size; j++) {
r += P2->P[j] * P2->P[j];
}
} else {
for (; i < size; i++) {
r += P[i] * P[i];
}
}
} else { // P1 partial vector, P2 full vector
int i = 0;
for (int j = 0; j < size; j++) {
for (; i < vertices[j]; i++) {
r += P2->P[i] * P2->P[i];
}
r += (P[j] - P2->P[i]) * (P[j] - P2->P[i]);
i++;
}
for (; i < P2->size; i++) {
r += P2->P[i] * P2->P[i];
}
}
} else {
if (P2->vertices) { // P1 full vector, P2 partial vector
int i = 0;
for (int j = 0; j < P2->size; j++) {
for (; i < P2->vertices[j]; i++) {
r += P[i] * P[i];
}
r += (P[i] - P2->P[j]) * (P[i] - P2->P[j]);
i++;
}
for (; i < size; i++) {
r += P[i] * P[i];
}
} else { // two full vectors
for (int i = 0; i < size; i++) {
r += (P[i] - P2->P[i]) * (P[i] - P2->P[i]);
}
}
}
return r;
}
Community::Community() {
P = nullptr;
first_neighbor = nullptr;
last_neighbor = nullptr;
sub_community_of = -1;
sub_communities[0] = -1;
sub_communities[1] = -1;
sigma = 0.;
internal_weight = 0.;
total_weight = 0.;
}
Community::~Community() {
delete P;
}
Communities::Communities(Graph* graph, int random_walks_length,
igraph_matrix_int_t *pmerges,
igraph_vector_t *pmodularity) {
G = graph;
merges = pmerges;
mergeidx = 0;
modularity = pmodularity;
Probabilities::C = this;
Probabilities::length = random_walks_length;
Probabilities::tmp_vector1 = new double[G->nb_vertices];
Probabilities::tmp_vector2 = new double[G->nb_vertices];
Probabilities::id = new int[G->nb_vertices];
for (int i = 0; i < G->nb_vertices; i++) {
Probabilities::id[i] = 0;
}
Probabilities::vertices1 = new int[G->nb_vertices];
Probabilities::vertices2 = new int[G->nb_vertices];
Probabilities::current_id = 0;
members = new int[G->nb_vertices];
for (int i = 0; i < G->nb_vertices; i++) {
members[i] = -1;
}
H = new Neighbor_heap(G->nb_edges);
IGRAPH_ASSUME(G->nb_vertices >= 0); // avoid false-positive GCC warnings
communities = new Community[2 * G->nb_vertices];
// init the n single vertex communities
for (int i = 0; i < G->nb_vertices; i++) {
communities[i].this_community = i;
communities[i].first_member = i;
communities[i].last_member = i;
communities[i].size = 1;
communities[i].sub_community_of = 0;
}
nb_communities = G->nb_vertices;
nb_active_communities = G->nb_vertices;
for (int i = 0; i < G->nb_vertices; i++)
for (int j = 0; j < G->vertices[i].degree; j++)
if (i < G->vertices[i].edges[j].neighbor) {
communities[i].total_weight += G->vertices[i].edges[j].weight / 2.;
communities[G->vertices[i].edges[j].neighbor].total_weight += G->vertices[i].edges[j].weight / 2.;
Neighbor* N = new Neighbor;
N->community1 = i;
N->community2 = G->vertices[i].edges[j].neighbor;
N->delta_sigma = -1. / double(min(G->vertices[i].degree, G->vertices[G->vertices[i].edges[j].neighbor].degree));
N->weight = G->vertices[i].edges[j].weight;
N->exact = false;
add_neighbor(N);
}
/* int c = 0; */
Neighbor* N = H->get_first();
if (N == nullptr) {
return; /* this can happen if there are no edges */
}
while (!N->exact) {
update_neighbor(N, compute_delta_sigma(N->community1, N->community2));
N->exact = true;
N = H->get_first();
/* TODO: this could use igraph_progress */
/* if(!silent) { */
/* c++; */
/* for(int k = (500*(c-1))/G->nb_edges + 1; k <= (500*c)/G->nb_edges; k++) { */
/* if(k % 50 == 1) {cerr.width(2); cerr << endl << k/ 5 << "% ";} */
/* cerr << "."; */
/* } */
/* } */
}
if (modularity) {
double Q = 0.0;
for (int i = 0; i < nb_communities; i++) {
if (communities[i].sub_community_of == 0) {
Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight);
}
}
Q /= G->total_weight;
VECTOR(*modularity)[mergeidx] = Q;
}
}
Communities::~Communities() {
delete[] members;
delete[] communities;
delete H;
delete[] Probabilities::tmp_vector1;
delete[] Probabilities::tmp_vector2;
delete[] Probabilities::id;
delete[] Probabilities::vertices1;
delete[] Probabilities::vertices2;
}
void Community::add_neighbor(Neighbor* N) { // add a new neighbor at the end of the list
if (last_neighbor) {
if (last_neighbor->community1 == this_community) {
last_neighbor->next_community1 = N;
} else {
last_neighbor->next_community2 = N;
}
if (N->community1 == this_community) {
N->previous_community1 = last_neighbor;
} else {
N->previous_community2 = last_neighbor;
}
} else {
first_neighbor = N;
if (N->community1 == this_community) {
N->previous_community1 = nullptr;
} else {
N->previous_community2 = nullptr;
}
}
last_neighbor = N;
}
void Community::remove_neighbor(Neighbor* N) { // remove a neighbor from the list
if (N->community1 == this_community) {
if (N->next_community1) {
// if (N->next_community1->community1 == this_community)
N->next_community1->previous_community1 = N->previous_community1;
// else
// N->next_community1->previous_community2 = N->previous_community1;
} else {
last_neighbor = N->previous_community1;
}
if (N->previous_community1) {
if (N->previous_community1->community1 == this_community) {
N->previous_community1->next_community1 = N->next_community1;
} else {
N->previous_community1->next_community2 = N->next_community1;
}
} else {
first_neighbor = N->next_community1;
}
} else {
if (N->next_community2) {
if (N->next_community2->community1 == this_community) {
N->next_community2->previous_community1 = N->previous_community2;
} else {
N->next_community2->previous_community2 = N->previous_community2;
}
} else {
last_neighbor = N->previous_community2;
}
if (N->previous_community2) {
// if (N->previous_community2->community1 == this_community)
// N->previous_community2->next_community1 = N->next_community2;
// else
N->previous_community2->next_community2 = N->next_community2;
} else {
first_neighbor = N->next_community2;
}
}
}
void Communities::remove_neighbor(Neighbor* N) {
communities[N->community1].remove_neighbor(N);
communities[N->community2].remove_neighbor(N);
H->remove(N);
}
void Communities::add_neighbor(Neighbor* N) {
communities[N->community1].add_neighbor(N);
communities[N->community2].add_neighbor(N);
H->add(N);
}
void Communities::update_neighbor(Neighbor* N, double new_delta_sigma) {
N->delta_sigma = new_delta_sigma;
H->update(N);
}
void Communities::merge_communities(Neighbor* merge_N) {
int c1 = merge_N->community1;
int c2 = merge_N->community2;
communities[nb_communities].first_member = communities[c1].first_member; // merge the
communities[nb_communities].last_member = communities[c2].last_member; // two lists
members[communities[c1].last_member] = communities[c2].first_member; // of members
communities[nb_communities].size = communities[c1].size + communities[c2].size;
communities[nb_communities].this_community = nb_communities;
communities[nb_communities].sub_community_of = 0;
communities[nb_communities].sub_communities[0] = c1;
communities[nb_communities].sub_communities[1] = c2;
communities[nb_communities].total_weight = communities[c1].total_weight + communities[c2].total_weight;
communities[nb_communities].internal_weight = communities[c1].internal_weight + communities[c2].internal_weight + merge_N->weight;
communities[nb_communities].sigma = communities[c1].sigma + communities[c2].sigma + merge_N->delta_sigma;
communities[c1].sub_community_of = nb_communities;
communities[c2].sub_community_of = nb_communities;
// update the new probability vector...
if (communities[c1].P && communities[c2].P) {
communities[nb_communities].P = new Probabilities(c1, c2);
}
if (communities[c1].P) {
delete communities[c1].P;
communities[c1].P = nullptr;
}
if (communities[c2].P) {
delete communities[c2].P;
communities[c2].P = nullptr;
}
// update the new neighbors
// by enumerating all the neighbors of c1 and c2
Neighbor* N1 = communities[c1].first_neighbor;
Neighbor* N2 = communities[c2].first_neighbor;
while (N1 && N2) {
int neighbor_community1;
int neighbor_community2;
if (N1->community1 == c1) {
neighbor_community1 = N1->community2;
} else {
neighbor_community1 = N1->community1;
}
if (N2->community1 == c2) {
neighbor_community2 = N2->community2;
} else {
neighbor_community2 = N2->community1;
}
if (neighbor_community1 < neighbor_community2) {
Neighbor* tmp = N1;
if (N1->community1 == c1) {
N1 = N1->next_community1;
} else {
N1 = N1->next_community2;
}
remove_neighbor(tmp);
Neighbor* N = new Neighbor;
N->weight = tmp->weight;
N->community1 = neighbor_community1;
N->community2 = nb_communities;
N->delta_sigma = (double(communities[c1].size + communities[neighbor_community1].size) * tmp->delta_sigma + double(communities[c2].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community1].size)); //compute_delta_sigma(neighbor_community1, nb_communities);
N->exact = false;
delete tmp;
add_neighbor(N);
}
if (neighbor_community2 < neighbor_community1) {
Neighbor* tmp = N2;
if (N2->community1 == c2) {
N2 = N2->next_community1;
} else {
N2 = N2->next_community2;
}
remove_neighbor(tmp);
Neighbor* N = new Neighbor;
N->weight = tmp->weight;
N->community1 = neighbor_community2;
N->community2 = nb_communities;
N->delta_sigma = (double(communities[c1].size) * merge_N->delta_sigma + double(communities[c2].size + communities[neighbor_community2].size) * tmp->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community2].size)); //compute_delta_sigma(neighbor_community2, nb_communities);
N->exact = false;
delete tmp;
add_neighbor(N);
}
if (neighbor_community1 == neighbor_community2) {
Neighbor* tmp1 = N1;
Neighbor* tmp2 = N2;
bool exact = N1->exact && N2->exact;
if (N1->community1 == c1) {
N1 = N1->next_community1;
} else {
N1 = N1->next_community2;
}
if (N2->community1 == c2) {
N2 = N2->next_community1;
} else {
N2 = N2->next_community2;
}
remove_neighbor(tmp1);
remove_neighbor(tmp2);
Neighbor* N = new Neighbor;
N->weight = tmp1->weight + tmp2->weight;
N->community1 = neighbor_community1;
N->community2 = nb_communities;
N->delta_sigma = (double(communities[c1].size + communities[neighbor_community1].size) * tmp1->delta_sigma + double(communities[c2].size + communities[neighbor_community1].size) * tmp2->delta_sigma - double(communities[neighbor_community1].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community1].size));
N->exact = exact;
delete tmp1;
delete tmp2;
add_neighbor(N);
}
}
if (!N1) {
while (N2) {
// double delta_sigma2 = N2->delta_sigma;
int neighbor_community;
if (N2->community1 == c2) {
neighbor_community = N2->community2;
} else {
neighbor_community = N2->community1;
}
Neighbor* tmp = N2;
if (N2->community1 == c2) {
N2 = N2->next_community1;
} else {
N2 = N2->next_community2;
}
remove_neighbor(tmp);
Neighbor* N = new Neighbor;
N->weight = tmp->weight;
N->community1 = neighbor_community;
N->community2 = nb_communities;
N->delta_sigma = (double(communities[c1].size) * merge_N->delta_sigma + double(communities[c2].size + communities[neighbor_community].size) * tmp->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community].size)); //compute_delta_sigma(neighbor_community, nb_communities);
N->exact = false;
delete tmp;
add_neighbor(N);
}
}
if (!N2) {
while (N1) {
// double delta_sigma1 = N1->delta_sigma;
int neighbor_community;
if (N1->community1 == c1) {
neighbor_community = N1->community2;
} else {
neighbor_community = N1->community1;
}
Neighbor* tmp = N1;
if (N1->community1 == c1) {
N1 = N1->next_community1;
} else {
N1 = N1->next_community2;
}
remove_neighbor(tmp);
Neighbor* N = new Neighbor;
N->weight = tmp->weight;
N->community1 = neighbor_community;
N->community2 = nb_communities;
N->delta_sigma = (double(communities[c1].size + communities[neighbor_community].size) * tmp->delta_sigma + double(communities[c2].size) * merge_N->delta_sigma) / (double(communities[c1].size + communities[c2].size + communities[neighbor_community].size)); //compute_delta_sigma(neighbor_community, nb_communities);
N->exact = false;
delete tmp;
add_neighbor(N);
}
}
nb_communities++;
nb_active_communities--;
}
double Communities::merge_nearest_communities() {
Neighbor* N = H->get_first();
while (!N->exact) {
update_neighbor(N, compute_delta_sigma(N->community1, N->community2));
N->exact = true;
N = H->get_first();
}
double d = N->delta_sigma;
remove_neighbor(N);
merge_communities(N);
if (merges) {
MATRIX(*merges, mergeidx, 0) = N->community1;
MATRIX(*merges, mergeidx, 1) = N->community2;
}
mergeidx++;
if (modularity) {
double Q = 0.0;
for (int i = 0; i < nb_communities; i++) {
if (communities[i].sub_community_of == 0) {
Q += (communities[i].internal_weight - communities[i].total_weight * communities[i].total_weight / G->total_weight);
}
}
Q /= G->total_weight;
VECTOR(*modularity)[mergeidx] = Q;
}
delete N;
/* This could use igraph_progress */
/* if(!silent) { */
/* for(int k = (500*(G->nb_vertices - nb_active_communities - 1))/(G->nb_vertices-1) + 1; k <= (500*(G->nb_vertices - nb_active_communities))/(G->nb_vertices-1); k++) { */
/* if(k % 50 == 1) {cerr.width(2); cerr << endl << k/ 5 << "% ";} */
/* cerr << "."; */
/* } */
/* } */
return d;
}
double Communities::compute_delta_sigma(int community1, int community2) const {
if (!communities[community1].P) {
communities[community1].P = new Probabilities(community1);
}
if (!communities[community2].P) {
communities[community2].P = new Probabilities(community2);
}
return communities[community1].P->compute_distance(communities[community2].P) * double(communities[community1].size) * double(communities[community2].size) / double(communities[community1].size + communities[community2].size);
}
}
} /* end of namespaces */
@@ -0,0 +1,160 @@
/*
igraph library.
Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard st, Cambridge, MA, 02138 USA
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
*/
/* The original version of this file was written by Pascal Pons
The original copyright notice follows here. The FSF address was
fixed by Tamas Nepusz */
// File: communities.h
//-----------------------------------------------------------------------------
// Walktrap v0.2 -- Finds community structure of networks using random walks
// Copyright (C) 2004-2005 Pascal Pons
//
// 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
//-----------------------------------------------------------------------------
// Author : Pascal Pons
// Email : pascal.pons@gmail.com
// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html
// Location : Paris, France
// Time : June 2005
//-----------------------------------------------------------------------------
// see readme.txt for more details
#ifndef WALKTRAP_COMMUNITIES_H
#define WALKTRAP_COMMUNITIES_H
#include "walktrap_graph.h"
#include "walktrap_heap.h"
#include "config.h" /* IGRAPH_THREAD_LOCAL */
namespace igraph {
namespace walktrap {
class Communities;
class Probabilities {
public:
static IGRAPH_THREAD_LOCAL double* tmp_vector1; //
static IGRAPH_THREAD_LOCAL double* tmp_vector2; //
static IGRAPH_THREAD_LOCAL int* id; //
static IGRAPH_THREAD_LOCAL int* vertices1; //
static IGRAPH_THREAD_LOCAL int* vertices2; //
static IGRAPH_THREAD_LOCAL int current_id; //
static IGRAPH_THREAD_LOCAL Communities* C; // pointer to all the communities
static IGRAPH_THREAD_LOCAL int length; // length of the random walks
int size; // number of probabilities stored
int* vertices; // the vertices corresponding to the stored probabilities, 0 if all the probabilities are stored
double* P; // the probabilities
double compute_distance(const Probabilities* P2) const; // compute the squared distance r^2 between this probability vector and P2
explicit Probabilities(int community); // compute the probability vector of a community
Probabilities(int community1, int community2); // merge the probability vectors of two communities in a new one
// the two communities must have their probability vectors stored
~Probabilities(); // destructor
};
class Community {
public:
Neighbor* first_neighbor; // first item of the list of adjacent communities
Neighbor* last_neighbor; // last item of the list of adjacent communities
int this_community; // number of this community
int first_member; // number of the first vertex of the community
int last_member; // number of the last vertex of the community
int size; // number of members of the community
Probabilities* P; // the probability vector, 0 if not stored.
double sigma; // sigma(C) of the community
double internal_weight; // sum of the weight of the internal edges
double total_weight; // sum of the weight of all the edges of the community (an edge between two communities is a half-edge for each community)
int sub_communities[2]; // the two sub communities, -1 if no sub communities;
int sub_community_of; // number of the community in which this community has been merged
// 0 if the community is active
// -1 if the community is not used
void add_neighbor(Neighbor* N);
void remove_neighbor(Neighbor* N);
Community(); // create an empty community
~Community(); // destructor
};
class Communities {
private:
igraph_matrix_int_t *merges;
igraph_int_t mergeidx;
igraph_vector_t *modularity;
public:
Graph* G; // the graph
int* members; // the members of each community represented as a chained list.
// a community points to the first_member the array which contains
// the next member (-1 = end of the community)
Neighbor_heap* H; // the distances between adjacent communities.
Community* communities; // array of the communities
int nb_communities; // number of valid communities
int nb_active_communities; // number of active communities
Communities(Graph* G, int random_walks_length = 3,
igraph_matrix_int_t *merges = nullptr,
igraph_vector_t *modularity = nullptr); // Constructor
~Communities(); // Destructor
void merge_communities(Neighbor* N); // create a community by merging two existing communities
double merge_nearest_communities();
double compute_delta_sigma(int c1, int c2) const; // compute delta_sigma(c1,c2)
void remove_neighbor(Neighbor* N);
void add_neighbor(Neighbor* N);
void update_neighbor(Neighbor* N, double new_delta_sigma);
};
}
} /* end of namespaces */
#endif // WALKTRAP_COMMUNITIES_H
@@ -0,0 +1,228 @@
/*
igraph library.
Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Pascal Pons
The original copyright notice follows here. The FSF address was
fixed by Tamas Nepusz */
// File: graph.cpp
//-----------------------------------------------------------------------------
// Walktrap v0.2 -- Finds community structure of networks using random walks
// Copyright (C) 2004-2005 Pascal Pons
//
// 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
//-----------------------------------------------------------------------------
// Author : Pascal Pons
// Email : pascal.pons@gmail.com
// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html
// Location : Paris, France
// Time : June 2005
//-----------------------------------------------------------------------------
// see readme.txt for more details
#include "walktrap_graph.h"
#include "igraph_interface.h"
#include <algorithm>
#include <stdexcept>
#include <climits>
using namespace std;
namespace igraph {
namespace walktrap {
bool operator<(const Edge& E1, const Edge& E2) {
return (E1.neighbor < E2.neighbor);
}
Vertex::Vertex() {
degree = 0;
edges = nullptr;
total_weight = 0.;
}
Vertex::~Vertex() {
delete[] edges;
}
Graph::Graph() {
nb_vertices = 0;
nb_edges = 0;
vertices = nullptr;
total_weight = 0.;
}
Graph::~Graph () {
delete[] vertices;
}
class Edge_list {
public:
int* V1;
int* V2;
double* W;
int size;
int size_max;
void add(int v1, int v2, double w);
Edge_list() {
size = 0;
size_max = 1024;
V1 = new int[1024];
V2 = new int[1024];
W = new double[1024];
}
~Edge_list() {
delete[] V1;
delete[] V2;
delete[] W;
}
};
void Edge_list::add(int v1, int v2, double w) {
if (size == size_max) {
int* tmp1 = new int[2 * size_max];
int* tmp2 = new int[2 * size_max];
double* tmp3 = new double[2 * size_max];
for (int i = 0; i < size_max; i++) {
tmp1[i] = V1[i];
tmp2[i] = V2[i];
tmp3[i] = W[i];
}
delete[] V1;
delete[] V2;
delete[] W;
V1 = tmp1;
V2 = tmp2;
W = tmp3;
size_max *= 2;
}
V1[size] = v1;
V2[size] = v2;
W[size] = w;
size++;
}
igraph_error_t Graph::convert_from_igraph(const igraph_t *graph,
const igraph_vector_t *weights) {
Graph &G = *this;
igraph_int_t no_of_nodes = igraph_vcount(graph);
igraph_int_t no_of_edges = igraph_ecount(graph);
// Avoid warnings with GCC when compiling with LTO.
IGRAPH_ASSUME(no_of_nodes >= 0);
IGRAPH_ASSUME(no_of_edges >= 0);
// Refactoring the walktrap code to support larger graphs is pointless
// as running the algorithm on them would take an impractically long time.
if (no_of_nodes > INT_MAX || no_of_edges > INT_MAX) {
IGRAPH_ERROR("Graph too large for walktrap community detection.", IGRAPH_EINVAL);
}
Edge_list EL;
for (igraph_int_t i = 0; i < no_of_edges; i++) {
igraph_real_t w = weights ? VECTOR(*weights)[i] : 1.0;
EL.add(IGRAPH_FROM(graph, i), IGRAPH_TO(graph, i), w);
}
G.nb_vertices = no_of_nodes;
G.vertices = new Vertex[G.nb_vertices];
G.nb_edges = 0;
G.total_weight = 0.0;
for (int i = 0; i < EL.size; i++) {
G.vertices[EL.V1[i]].degree++;
G.vertices[EL.V2[i]].degree++;
G.vertices[EL.V1[i]].total_weight += EL.W[i];
G.vertices[EL.V2[i]].total_weight += EL.W[i];
G.nb_edges++;
G.total_weight += EL.W[i];
}
for (int i = 0; i < G.nb_vertices; i++) {
int deg = G.vertices[i].degree;
double w = (deg == 0) ? 1.0 : (G.vertices[i].total_weight / double(deg));
G.vertices[i].edges = new Edge[deg + 1];
G.vertices[i].edges[0].neighbor = i;
G.vertices[i].edges[0].weight = w;
G.vertices[i].total_weight += w;
G.vertices[i].degree = 1;
}
for (int i = 0; i < EL.size; i++) {
G.vertices[EL.V1[i]].edges[G.vertices[EL.V1[i]].degree].neighbor = EL.V2[i];
G.vertices[EL.V1[i]].edges[G.vertices[EL.V1[i]].degree].weight = EL.W[i];
G.vertices[EL.V1[i]].degree++;
G.vertices[EL.V2[i]].edges[G.vertices[EL.V2[i]].degree].neighbor = EL.V1[i];
G.vertices[EL.V2[i]].edges[G.vertices[EL.V2[i]].degree].weight = EL.W[i];
G.vertices[EL.V2[i]].degree++;
}
for (int i = 0; i < G.nb_vertices; i++) {
/* Check for zero strength, as it may lead to crashes the in walktrap algorithm.
* See https://github.com/igraph/igraph/pull/2043 */
if (G.vertices[i].total_weight == 0) {
/* G.vertices will be destroyed by Graph::~Graph() */
IGRAPH_ERROR("Vertex with zero strength found: all vertices must have positive strength for walktrap.",
IGRAPH_EINVAL);
}
sort(G.vertices[i].edges, G.vertices[i].edges + G.vertices[i].degree);
}
for (int i = 0; i < G.nb_vertices; i++) { // merge multi edges
int a = 0;
for (int b = 1; b < G.vertices[i].degree; b++) {
if (G.vertices[i].edges[b].neighbor == G.vertices[i].edges[a].neighbor) {
G.vertices[i].edges[a].weight += G.vertices[i].edges[b].weight;
} else {
G.vertices[i].edges[++a] = G.vertices[i].edges[b];
}
}
G.vertices[i].degree = a + 1;
}
return IGRAPH_SUCCESS;
}
}
}
@@ -0,0 +1,100 @@
/*
igraph library.
Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard st, Cambridge, MA, 02138 USA
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
*/
/* The original version of this file was written by Pascal Pons
The original copyright notice follows here */
// File: graph.h
//-----------------------------------------------------------------------------
// Walktrap v0.2 -- Finds community structure of networks using random walks
// Copyright (C) 2004-2005 Pascal Pons
//
// 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
//-----------------------------------------------------------------------------
// Author : Pascal Pons
// Email : pascal.pons@gmail.com
// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html
// Location : Paris, France
// Time : June 2005
//-----------------------------------------------------------------------------
// see readme.txt for more details
/* FSF address above was fixed by Tamas Nepusz */
#ifndef WALKTRAP_GRAPH_H
#define WALKTRAP_GRAPH_H
#include "igraph_community.h"
namespace igraph {
namespace walktrap {
class Edge { // code an edge of a given vertex
public:
int neighbor; // the number of the neighbor vertex
double weight; // the weight of the edge
};
bool operator<(const Edge& E1, const Edge& E2);
class Vertex {
public:
Edge* edges; // the edges of the vertex
int degree; // number of neighbors
double total_weight; // the total weight of the vertex
Vertex(); // creates empty vertex
~Vertex(); // destructor
};
class Graph {
public:
int nb_vertices; // number of vertices
int nb_edges; // number of edges
double total_weight; // total weight of the edges
Vertex* vertices; // array of the vertices
Graph(); // create an empty graph
~Graph(); // destructor
igraph_error_t convert_from_igraph(const igraph_t *igraph, const igraph_vector_t *weights);
};
}
} /* end of namespaces */
#endif // WALKTRAP_GRAPH_H
@@ -0,0 +1,141 @@
/*
igraph library.
Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard street, Cambridge, MA 02139 USA
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
*/
/* The original version of this file was written by Pascal Pons
The original copyright notice follows here. The FSF address was
fixed by Tamas Nepusz */
// File: heap.cpp
//-----------------------------------------------------------------------------
// Walktrap v0.2 -- Finds community structure of networks using random walks
// Copyright (C) 2004-2005 Pascal Pons
//
// 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
//-----------------------------------------------------------------------------
// Author : Pascal Pons
// Email : pascal.pons@gmail.com
// Web page : http://www-rp.lip6.fr/~latapy/PP/walktrap.html
// Location : Paris, France
// Time : June 2005
//-----------------------------------------------------------------------------
// see readme.txt for more details
#include "walktrap_heap.h"
using namespace igraph::walktrap;
void Neighbor_heap::move_up(int index) {
while (H[index / 2]->delta_sigma > H[index]->delta_sigma) {
Neighbor* tmp = H[index / 2];
H[index]->heap_index = index / 2;
H[index / 2] = H[index];
tmp->heap_index = index;
H[index] = tmp;
index = index / 2;
}
}
void Neighbor_heap::move_down(int index) {
while (true) {
int min = index;
if ((2 * index < size) && (H[2 * index]->delta_sigma < H[min]->delta_sigma)) {
min = 2 * index;
}
if (2 * index + 1 < size && H[2 * index + 1]->delta_sigma < H[min]->delta_sigma) {
min = 2 * index + 1;
}
if (min != index) {
Neighbor* tmp = H[min];
H[index]->heap_index = min;
H[min] = H[index];
tmp->heap_index = index;
H[index] = tmp;
index = min;
} else {
break;
}
}
}
Neighbor* Neighbor_heap::get_first() {
if (size == 0) {
return nullptr;
} else {
return H[0];
}
}
void Neighbor_heap::remove(Neighbor* N) {
if (N->heap_index == -1 || size == 0) {
return;
}
Neighbor* last_N = H[--size];
H[N->heap_index] = last_N;
last_N->heap_index = N->heap_index;
move_up(last_N->heap_index);
move_down(last_N->heap_index);
N->heap_index = -1;
}
void Neighbor_heap::add(Neighbor* N) {
if (size >= max_size) {
return;
}
N->heap_index = size++;
H[N->heap_index] = N;
move_up(N->heap_index);
}
void Neighbor_heap::update(Neighbor* N) {
if (N->heap_index == -1) {
return;
}
move_up(N->heap_index);
move_down(N->heap_index);
}
Neighbor_heap::Neighbor_heap(int max_s) {
max_size = max_s;
size = 0;
H = new Neighbor*[max_s];
}
Neighbor_heap::~Neighbor_heap() {
delete[] H;
}
bool Neighbor_heap::is_empty() const {
return (size == 0);
}
@@ -0,0 +1,106 @@
/*
igraph library.
Copyright (C) 2007-2012 Gabor Csardi <csardi.gabor@gmail.com>
334 Harvard st, Cambridge, MA, 02138 USA
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
*/
/* The original version of this file was written by Pascal Pons
The original copyright notice follows here. The FSF address was
fixed by Tamas Nepusz */
// File: heap.h
//-----------------------------------------------------------------------------
// Walktrap v0.2 -- Finds community structure of networks using random walks
// Copyright (C) 2004-2005 Pascal Pons
//
// 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
//-----------------------------------------------------------------------------
// Author : Pascal Pons
// Email : pons@liafa.jussieu.fr
// Web page : http://www.liafa.jussieu.fr/~pons/
// Location : Paris, France
// Time : June 2005
//-----------------------------------------------------------------------------
// see readme.txt for more details
#ifndef WALKTRAP_HEAP_H
#define WALKTRAP_HEAP_H
namespace igraph {
namespace walktrap {
class Neighbor {
public:
int community1; // the two adjacent communities
int community2; // community1 < community2
double delta_sigma; // the delta sigma between the two communities
double weight; // the total weight of the edges between the two communities
bool exact; // true if delta_sigma is exact, false if it is only a lower bound
Neighbor* next_community1; // pointers of two double
Neighbor* previous_community1; // chained lists containing
Neighbor* next_community2; // all the neighbors of
Neighbor* previous_community2; // each communities.
int heap_index; //
Neighbor();
};
class Neighbor_heap {
private:
int size;
int max_size;
Neighbor** H; // the heap that contains a pointer to each Neighbor object stored
void move_up(int index);
void move_down(int index);
public:
void add(Neighbor* N); // add a new distance
void update(Neighbor* N); // update a distance
void remove(Neighbor* N); // remove a distance
Neighbor* get_first(); // get the first item
bool is_empty() const;
explicit Neighbor_heap(int max_size);
~Neighbor_heap();
};
}
} /* end of namespaces */
#endif // WALKTRAP_HEAP_H