Add graph references
This commit is contained in:
@@ -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
|
||||
* & 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 (1–1): 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
|
||||
* & 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., & 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(°, no_of_nodes);
|
||||
|
||||
IGRAPH_CHECK(igraph_strength(graph, °, 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(°);
|
||||
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
|
||||
Reference in New Issue
Block a user