@@ -13895,6 +13895,130 @@ PyObject *igraphmodule_Graph_random_walk(igraphmodule_GraphObject * self,
1389513895 }
1389613896}
1389713897
13898+
13899+ /**********************************************************************
13900+ * Other methods *
13901+ **********************************************************************/
13902+
13903+ /**
13904+ * Voronoi clustering
13905+ */
13906+ PyObject *igraphmodule_Graph_community_voronoi(igraphmodule_GraphObject *self,
13907+ PyObject *args, PyObject *kwds) {
13908+ static char *kwlist[] = {"lengths", "weights", "mode", "radius", NULL};
13909+ PyObject *lengths_o = Py_None, *weights_o = Py_None;
13910+ PyObject *mode_o = Py_None;
13911+ PyObject *radius_o = Py_None;
13912+ igraph_vector_t lengths_v, weights_v;
13913+ igraph_vector_int_t membership_v, generators_v;
13914+ igraph_neimode_t mode = IGRAPH_ALL;
13915+ igraph_real_t radius = -1.0; /* negative means auto-optimize */
13916+ igraph_real_t modularity;
13917+ PyObject *membership_o, *generators_o, *result_o;
13918+ igraph_bool_t return_modularity = 1;
13919+ igraph_bool_t lengths_allocated = 0, weights_allocated = 0;
13920+
13921+ if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist,
13922+ &lengths_o, &weights_o, &mode_o, &radius_o))
13923+ return NULL;
13924+
13925+ /* Handle mode parameter */
13926+ if (mode_o != Py_None) {
13927+ if (igraphmodule_PyObject_to_neimode_t(mode_o, &mode))
13928+ return NULL;
13929+ }
13930+
13931+ /* Handle radius parameter */
13932+ if (radius_o != Py_None) {
13933+ if (PyFloat_Check(radius_o)) {
13934+ radius = PyFloat_AsDouble(radius_o);
13935+ } else if (PyLong_Check(radius_o)) {
13936+ radius = PyLong_AsDouble(radius_o);
13937+ } else {
13938+ PyErr_SetString(PyExc_TypeError, "radius must be a number or None");
13939+ return NULL;
13940+ }
13941+ if (PyErr_Occurred()) return NULL;
13942+ }
13943+
13944+ /* Handle lengths parameter */
13945+ if (lengths_o != Py_None) {
13946+ if (igraphmodule_PyObject_to_vector_t(lengths_o, &lengths_v, 1)) {
13947+ return NULL;
13948+ }
13949+ lengths_allocated = 1;
13950+ }
13951+
13952+ /* Handle weights parameter */
13953+ if (weights_o != Py_None) {
13954+ if (igraphmodule_PyObject_to_vector_t(weights_o, &weights_v, 1)) {
13955+ if (lengths_allocated) {
13956+ igraph_vector_destroy(&lengths_v);
13957+ }
13958+ return NULL;
13959+ }
13960+ weights_allocated = 1;
13961+ }
13962+
13963+ /* Initialize result vectors */
13964+ if (igraph_vector_int_init(&membership_v, 0)) {
13965+ if (lengths_allocated) igraph_vector_destroy(&lengths_v);
13966+ if (weights_allocated) igraph_vector_destroy(&weights_v);
13967+ igraphmodule_handle_igraph_error();
13968+ return NULL;
13969+ }
13970+
13971+ if (igraph_vector_int_init(&generators_v, 0)) {
13972+ if (lengths_allocated) igraph_vector_destroy(&lengths_v);
13973+ if (weights_allocated) igraph_vector_destroy(&weights_v);
13974+ igraph_vector_int_destroy(&membership_v);
13975+ igraphmodule_handle_igraph_error();
13976+ return NULL;
13977+ }
13978+
13979+ /* Call the C function - pass NULL for None parameters */
13980+ if (igraph_community_voronoi(&self->g, &membership_v, &generators_v,
13981+ return_modularity ? &modularity : NULL,
13982+ lengths_allocated ? &lengths_v : NULL,
13983+ weights_allocated ? &weights_v : NULL,
13984+ mode, radius)) {
13985+ if (lengths_allocated) igraph_vector_destroy(&lengths_v);
13986+ if (weights_allocated) igraph_vector_destroy(&weights_v);
13987+ igraph_vector_int_destroy(&membership_v);
13988+ igraph_vector_int_destroy(&generators_v);
13989+ igraphmodule_handle_igraph_error();
13990+ return NULL;
13991+ }
13992+
13993+ /* Clean up input vectors */
13994+ if (lengths_allocated) igraph_vector_destroy(&lengths_v);
13995+ if (weights_allocated) igraph_vector_destroy(&weights_v);
13996+
13997+ /* Convert results to Python objects */
13998+ membership_o = igraphmodule_vector_int_t_to_PyList(&membership_v);
13999+ igraph_vector_int_destroy(&membership_v);
14000+ if (!membership_o) {
14001+ igraph_vector_int_destroy(&generators_v);
14002+ return NULL;
14003+ }
14004+
14005+ generators_o = igraphmodule_vector_int_t_to_PyList(&generators_v);
14006+ igraph_vector_int_destroy(&generators_v);
14007+ if (!generators_o) {
14008+ Py_DECREF(membership_o);
14009+ return NULL;
14010+ }
14011+
14012+ /* Return tuple with membership, generators, and modularity */
14013+ if (return_modularity) {
14014+ result_o = Py_BuildValue("(NNd)", membership_o, generators_o, (double)modularity);
14015+ } else {
14016+ result_o = Py_BuildValue("(NN)", membership_o, generators_o);
14017+ }
14018+
14019+ return result_o;
14020+ }
14021+
1389814022/**********************************************************************
1389914023 * Special internal methods that you won't need to mess around with *
1390014024 **********************************************************************/
@@ -18628,6 +18752,8 @@ struct PyMethodDef igraphmodule_Graph_methods[] = {
1862818752 " current membership vector any more.\n"
1862918753 "@return: the community membership vector.\n"
1863018754 },
18755+ {"community_voronoi", (PyCFunction) igraphmodule_Graph_community_voronoi,
18756+ METH_VARARGS | METH_KEYWORDS, "Finds communities using Voronoi partitioning"},
1863118757 {"community_walktrap",
1863218758 (PyCFunction) igraphmodule_Graph_community_walktrap,
1863318759 METH_VARARGS | METH_KEYWORDS,
@@ -18693,6 +18819,46 @@ struct PyMethodDef igraphmodule_Graph_methods[] = {
1869318819 "@return: a random walk that starts from the given vertex and has at most\n"
1869418820 " the given length (shorter if the random walk got stuck).\n"
1869518821 },
18822+
18823+ /****************/
18824+ /* OTHER METHODS */
18825+ /****************/{
18826+ "community_voronoi",
18827+ (PyCFunction) igraphmodule_Graph_community_voronoi,
18828+ METH_VARARGS | METH_KEYWORDS,
18829+ "community_voronoi(lengths=None, weights=None, mode=\"all\", radius=None)\n\n"
18830+ "Finds communities using Voronoi partitioning.\n\n"
18831+ "This function finds communities using a Voronoi partitioning of vertices based\n"
18832+ "on the given edge lengths divided by the edge clustering coefficient.\n"
18833+ "The generator vertices are chosen to be those with the largest local relative\n"
18834+ "density within a radius, with the local relative density of a vertex defined as\n"
18835+ "s * m / (m + k), where s is the strength of the vertex, m is the number of\n"
18836+ "edges within the vertex's first order neighborhood, while k is the number of\n"
18837+ "edges with only one endpoint within this neighborhood.\n\n"
18838+ "@param lengths: edge lengths, or C{None} to consider all edges as having\n"
18839+ " unit length. Voronoi partitioning will use edge lengths equal to\n"
18840+ " lengths / ECC where ECC is the edge clustering coefficient.\n"
18841+ "@param weights: edge weights, or C{None} to consider all edges as having\n"
18842+ " unit weight. Weights are used when selecting generator points, as well\n"
18843+ " as for computing modularity.\n"
18844+ "@param mode: if C{\"out\"}, distances from generator points to all other\n"
18845+ " nodes are considered. If C{\"in\"}, the reverse distances are used.\n"
18846+ " If C{\"all\"}, edge directions are ignored. This parameter is ignored\n"
18847+ " for undirected graphs.\n"
18848+ "@param radius: the radius/resolution to use when selecting generator points.\n"
18849+ " The larger this value, the fewer partitions there will be. Pass C{None}\n"
18850+ " to automatically select the radius that maximizes modularity.\n"
18851+ "@return: a tuple containing the membership vector, generator vertices,\n"
18852+ " and modularity score.\n"
18853+ "@rtype: tuple\n\n"
18854+ "@newfield ref: Reference\n"
18855+ "@ref: Deritei et al., Community detection by graph Voronoi diagrams,\n"
18856+ " New Journal of Physics 16, 063007 (2014)\n"
18857+ " U{https://doi.org/10.1088/1367-2630/16/6/063007}\n"
18858+ "@ref: Molnár et al., Community Detection in Directed Weighted Networks\n"
18859+ " using Voronoi Partitioning, Scientific Reports 14, 8124 (2024)\n"
18860+ " U{https://doi.org/10.1038/s41598-024-58624-4}\n"
18861+ },
1869618862
1869718863 /**********************/
1869818864 /* INTERNAL FUNCTIONS */
0 commit comments