@@ -13775,6 +13775,125 @@ PyObject *igraphmodule_Graph_community_fluid_communities(igraphmodule_GraphObjec
13775
13775
return result;
13776
13776
}
13777
13777
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
+
13778
13897
/**********************************************************************
13779
13898
* Random walks *
13780
13899
**********************************************************************/
@@ -18653,6 +18772,42 @@ struct PyMethodDef igraphmodule_Graph_methods[] = {
18653
18772
" original implementation is used.\n"
18654
18773
"@return: the community membership vector.\n"
18655
18774
},
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
+ },
18656
18811
{"community_leiden",
18657
18812
(PyCFunction) igraphmodule_Graph_community_leiden,
18658
18813
METH_VARARGS | METH_KEYWORDS,
0 commit comments