@@ -13775,6 +13775,125 @@ PyObject *igraphmodule_Graph_community_fluid_communities(igraphmodule_GraphObjec
1377513775 return result;
1377613776}
1377713777
13778+ /**
13779+ * Voronoi clustering
13780+ */
13781+ PyObject *igraphmodule_Graph_community_voronoi(igraphmodule_GraphObject *self,
13782+ PyObject *args, PyObject *kwds) {
13783+ static char *kwlist[] = {"lengths", "weights", "mode", "radius", NULL};
13784+ PyObject *lengths_o = Py_None, *weights_o = Py_None;
13785+ PyObject *mode_o = Py_None;
13786+ PyObject *radius_o = Py_None;
13787+ igraph_vector_t *lengths_v = NULL;
13788+ igraph_vector_t *weights_v = NULL;
13789+ igraph_vector_int_t membership_v, generators_v;
13790+ igraph_neimode_t mode = IGRAPH_OUT;
13791+ igraph_real_t radius = -1.0; /* negative means auto-optimize */
13792+ igraph_real_t modularity = IGRAPH_NAN;
13793+ PyObject *membership_o, *generators_o, *result_o;
13794+
13795+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist,
13796+ &lengths_o, &weights_o, &mode_o, &radius_o)) {
13797+ return NULL;
13798+ }
13799+
13800+ /* Handle mode parameter */
13801+ if (igraphmodule_PyObject_to_neimode_t(mode_o, &mode)) {
13802+ return NULL;
13803+ }
13804+
13805+ /* Handle radius parameter */
13806+ if (radius_o != Py_None) {
13807+ if (igraphmodule_PyObject_to_real_t(radius_o, &radius)) {
13808+ return NULL;
13809+ }
13810+ }
13811+
13812+ /* Handle lengths parameter */
13813+ if (igraphmodule_attrib_to_vector_t(lengths_o, self, &lengths_v, ATTRIBUTE_TYPE_EDGE)) {
13814+ return NULL;
13815+ }
13816+
13817+ /* Handle weights parameter */
13818+ if (igraphmodule_attrib_to_vector_t(weights_o, self, &weights_v, ATTRIBUTE_TYPE_EDGE)) {
13819+ if (lengths_v != NULL) {
13820+ igraph_vector_destroy(lengths_v); free(lengths_v);
13821+ }
13822+ return NULL;
13823+ }
13824+
13825+ /* Initialize result vectors */
13826+ if (igraph_vector_int_init(&membership_v, 0)) {
13827+ if (lengths_v != NULL) {
13828+ igraph_vector_destroy(lengths_v); free(lengths_v);
13829+ }
13830+ if (weights_v != NULL) {
13831+ igraph_vector_destroy(weights_v); free(weights_v);
13832+ }
13833+ igraphmodule_handle_igraph_error();
13834+ return NULL;
13835+ }
13836+
13837+ if (igraph_vector_int_init(&generators_v, 0)) {
13838+ if (lengths_v != NULL) {
13839+ igraph_vector_destroy(lengths_v); free(lengths_v);
13840+ }
13841+ if (weights_v != NULL) {
13842+ igraph_vector_destroy(weights_v); free(weights_v);
13843+ }
13844+ igraph_vector_int_destroy(&membership_v);
13845+ igraphmodule_handle_igraph_error();
13846+ return NULL;
13847+ }
13848+
13849+ /* Call the C function - pass NULL for None parameters */
13850+ if (igraph_community_voronoi(&self->g, &membership_v, &generators_v,
13851+ &modularity,
13852+ lengths_v,
13853+ weights_v,
13854+ mode, radius)) {
13855+
13856+ if (lengths_v != NULL) {
13857+ igraph_vector_destroy(lengths_v); free(lengths_v);
13858+ }
13859+ if (weights_v != NULL) {
13860+ igraph_vector_destroy(weights_v); free(weights_v);
13861+ }
13862+ igraph_vector_int_destroy(&membership_v);
13863+ igraph_vector_int_destroy(&generators_v);
13864+ igraphmodule_handle_igraph_error();
13865+ return NULL;
13866+ }
13867+
13868+ /* Clean up input vectors */
13869+ if (lengths_v != NULL) {
13870+ igraph_vector_destroy(lengths_v); free(lengths_v);
13871+ }
13872+ if (weights_v != NULL) {
13873+ igraph_vector_destroy(weights_v); free(weights_v);
13874+ }
13875+
13876+ /* Convert results to Python objects */
13877+ membership_o = igraphmodule_vector_int_t_to_PyList(&membership_v);
13878+ igraph_vector_int_destroy(&membership_v);
13879+ if (!membership_o) {
13880+ igraph_vector_int_destroy(&generators_v);
13881+ return NULL;
13882+ }
13883+
13884+ generators_o = igraphmodule_vector_int_t_to_PyList(&generators_v);
13885+ igraph_vector_int_destroy(&generators_v);
13886+ if (!generators_o) {
13887+ Py_DECREF(membership_o);
13888+ return NULL;
13889+ }
13890+
13891+ /* Return tuple with membership, generators, and modularity */
13892+ result_o = Py_BuildValue("(NNd)", membership_o, generators_o, modularity);
13893+
13894+ return result_o;
13895+ }
13896+
1377813897/**********************************************************************
1377913898 * Random walks *
1378013899 **********************************************************************/
@@ -18653,6 +18772,42 @@ struct PyMethodDef igraphmodule_Graph_methods[] = {
1865318772 " original implementation is used.\n"
1865418773 "@return: the community membership vector.\n"
1865518774 },
18775+ {"community_voronoi",
18776+ (PyCFunction) igraphmodule_Graph_community_voronoi,
18777+ METH_VARARGS | METH_KEYWORDS,
18778+ "community_voronoi(lengths=None, weights=None, mode=\"out\", radius=None)\n--\n\n"
18779+ "Finds communities using Voronoi partitioning.\n\n"
18780+ "This function finds communities using a Voronoi partitioning of vertices based\n"
18781+ "on the given edge lengths divided by the edge clustering coefficient.\n"
18782+ "The generator vertices are chosen to be those with the largest local relative\n"
18783+ "density within a radius, with the local relative density of a vertex defined as\n"
18784+ "C{s * m / (m + k)}, where s is the strength of the vertex, m is the number of\n"
18785+ "edges within the vertex's first order neighborhood, while k is the number of\n"
18786+ "edges with only one endpoint within this neighborhood.\n\n"
18787+ "B{References}\n\n"
18788+ " - Deritei et al., Community detection by graph Voronoi diagrams,\n"
18789+ " New Journal of Physics 16, 063007 (2014)\n"
18790+ " U{https://doi.org/10.1088/1367-2630/16/6/063007}\n"
18791+ " - Molnár et al., Community Detection in Directed Weighted Networks\n"
18792+ " using Voronoi Partitioning, Scientific Reports 14, 8124 (2024)\n"
18793+ " U{https://doi.org/10.1038/s41598-024-58624-4}\n\n"
18794+ "@param lengths: edge lengths, or C{None} to consider all edges as having\n"
18795+ " unit length. Voronoi partitioning will use edge lengths equal to\n"
18796+ " lengths / ECC where ECC is the edge clustering coefficient.\n"
18797+ "@param weights: edge weights, or C{None} to consider all edges as having\n"
18798+ " unit weight. Weights are used when selecting generator points, as well\n"
18799+ " as for computing modularity.\n"
18800+ "@param mode: if C{\"out\"} (the default), distances from generator points to all other\n"
18801+ " nodes are considered. If C{\"in\"}, the reverse distances are used.\n"
18802+ " If C{\"all\"}, edge directions are ignored. This parameter is ignored\n"
18803+ " for undirected graphs.\n"
18804+ "@param radius: the radius/resolution to use when selecting generator points.\n"
18805+ " The larger this value, the fewer partitions there will be. Pass C{None}\n"
18806+ " to automatically select the radius that maximizes modularity.\n"
18807+ "@return: a tuple containing the membership vector, generator vertices, and\n"
18808+ " modularity score: (membership, generators, modularity).\n"
18809+ "@rtype: tuple\n"
18810+ },
1865618811 {"community_leiden",
1865718812 (PyCFunction) igraphmodule_Graph_community_leiden,
1865818813 METH_VARARGS | METH_KEYWORDS,
0 commit comments