From e67d9c4ecfe53cb66f7526a2c41aa685aa0854e1 Mon Sep 17 00:00:00 2001 From: Tim Bernhard Date: Wed, 13 Nov 2024 16:53:58 +0100 Subject: [PATCH 1/4] Implement simple cycle search Python binding --- CHANGELOG.md | 1 + src/_igraph/graphobject.c | 77 +++++++++++++++++++++++++++++++++++++++ tests/test_cycles.py | 19 ++++++++++ 3 files changed, 97 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dea92015e..88c1c9d7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Dropped support for Python 3.8 as it has now reached its end of life. - The C core of igraph was updated to version 0.10.15. +- Added `Graph.simple_cycles()` to find simple cycles in the graph. ## [0.11.8] - 2024-10-25 diff --git a/src/_igraph/graphobject.c b/src/_igraph/graphobject.c index 95a29e5ff..7a3022fc7 100644 --- a/src/_igraph/graphobject.c +++ b/src/_igraph/graphobject.c @@ -7813,6 +7813,65 @@ PyObject *igraphmodule_Graph_minimum_cycle_basis( return result_o; } + +PyObject *igraphmodule_Graph_simple_cycles( + igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds +) { + PyObject *mode_o = Py_None; + PyObject *output_o = Py_None; + PyObject *min_cycle_length_o = Py_None; + PyObject *max_cycle_length_o = Py_None; + + // argument defaults: no cycle limits + igraph_integer_t mode = IGRAPH_OUT; + igraph_integer_t min_cycle_length = -1; + igraph_integer_t max_cycle_length = -1; + igraph_bool_t use_edges = false; + + static char *kwlist[] = { "mode", "min_cycle_length", "max_cycle_length", "output" NULL }; + + if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, &mode_o, &min_cycle_length_o, &max_cycle_length_o, &output_o)) + return NULL; + + if (mode_o != Py_None && igraphmodule_PyObject_to_integer_t(mode_o, &mode)) + return NULL; + + if (min_cycle_length_o != Py_None && igraphmodule_PyObject_to_integer_t(min_cycle_length_o, &min_cycle_length)) + return NULL; + + if (max_cycle_length_o != Py_None && igraphmodule_PyObject_to_integer_t(max_cycle_length_o, &max_cycle_length)) + return NULL; + + if (igraphmodule_PyObject_to_vpath_or_epath(output_o, &use_edges)) + return NULL; + + igraph_vector_int_list_t vertices; + igraph_vector_int_list_init(&vertices, 0); + igraph_vector_int_list_t edges; + igraph_vector_int_list_init(&edges, 0); + + if (igraph_simple_cycles( + &self->g, use_edges ? NULL : &vertices, use_edges ? &edges : NULL, mode, min_cycle_length, max_cycle_length + )) { + igraph_vector_int_list_destroy(&vertices); + igraph_vector_int_list_destroy(&edges); + igraphmodule_handle_igraph_error(); + return NULL; + } + + PyObject *result_o; + + if (use_edges) { + result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&edges); + } else { + result_vertices_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&vertices); + } + igraph_vector_int_list_destroy(&edges); + igraph_vector_int_list_destroy(&vertices); + + return result_o; +} + /********************************************************************** * Graph layout algorithms * **********************************************************************/ @@ -16563,6 +16622,24 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " no guarantees are given about the ordering of edge IDs within cycles.\n" "@return: the cycle basis as a list of tuples containing edge IDs" }, + {"simple_cycles", (PyCFunction) igraphmodule_Graph_simple_cycles, + METH_VARARGS | METH_KEYWORDS, + "simple_cycles(mode=None, min_cycle_length=0, max_cycle_length=-1, output=\"vpath\")\n--\n\n" + "Finds simple cycles in a graph\n\n" + "@param mode: for directed graphs, specifies how the edge directions\n" + " should be taken into account. C{\"all\"} means that the edge directions\n" + " must be ignored, C{\"out\"} means that the edges must be oriented away\n" + " from the root, C{\"in\"} means that the edges must be oriented\n" + " towards the root. Ignored for undirected graphs.\n" + "@param min_cycle_length: the minimum number of vertices in a cycle\n" + " for it to be returned.\n" + "@param max_cycle_length: the maximum number of vertices in a cycle\n" + " for it to be considered.\n" + "@param output: determines what should be returned. If this is\n" + " C{\"vpath\"}, a list of tuples of vertex IDs will be returned. If this is\n" + " C{\"epath\"}, edge IDs are returned instead of vertex IDs.\n" + "@return: see the documentation of the C{output} parameter.\n" + }, /********************/ /* LAYOUT FUNCTIONS */ diff --git a/tests/test_cycles.py b/tests/test_cycles.py index 48a37f2f7..e3549121c 100644 --- a/tests/test_cycles.py +++ b/tests/test_cycles.py @@ -60,6 +60,25 @@ def test_fundamental_cycles(self): ] assert cycles == [[6, 7, 10], [8, 9, 10]] + def test_simple_cycles(self): + g = Graph( + [ + (0, 1), + (1, 2), + (2, 0), + (0, 0), + (0, 3), + (3, 4), + (4, 5), + (5, 0), + ] + ) + + vertices = g.simple_cycles(output="vpath") + edges = g.simple_cycles(output="epath") + assert len(vertices) == 3 + assert len(edges) == 3 + def test_minimum_cycle_basis(self): g = Graph( [ From 1473dd3dc8c56357d9022552d93ff0a1ddaf79e4 Mon Sep 17 00:00:00 2001 From: Tim Bernhard Date: Wed, 13 Nov 2024 21:20:55 +0100 Subject: [PATCH 2/4] Fix two typos --- src/_igraph/graphobject.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_igraph/graphobject.c b/src/_igraph/graphobject.c index 7a3022fc7..ee27de0a6 100644 --- a/src/_igraph/graphobject.c +++ b/src/_igraph/graphobject.c @@ -7828,7 +7828,7 @@ PyObject *igraphmodule_Graph_simple_cycles( igraph_integer_t max_cycle_length = -1; igraph_bool_t use_edges = false; - static char *kwlist[] = { "mode", "min_cycle_length", "max_cycle_length", "output" NULL }; + static char *kwlist[] = { "mode", "min_cycle_length", "max_cycle_length", "output", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, &mode_o, &min_cycle_length_o, &max_cycle_length_o, &output_o)) return NULL; @@ -7864,7 +7864,7 @@ PyObject *igraphmodule_Graph_simple_cycles( if (use_edges) { result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&edges); } else { - result_vertices_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&vertices); + result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&vertices); } igraph_vector_int_list_destroy(&edges); igraph_vector_int_list_destroy(&vertices); From b8a922d31cd80367c50b5ea369532177bbffb36d Mon Sep 17 00:00:00 2001 From: Tim Bernhard Date: Wed, 13 Nov 2024 21:31:21 +0100 Subject: [PATCH 3/4] Rename parameters to simple_cycles method --- src/_igraph/graphobject.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/_igraph/graphobject.c b/src/_igraph/graphobject.c index ee27de0a6..a40ec7c45 100644 --- a/src/_igraph/graphobject.c +++ b/src/_igraph/graphobject.c @@ -7828,7 +7828,7 @@ PyObject *igraphmodule_Graph_simple_cycles( igraph_integer_t max_cycle_length = -1; igraph_bool_t use_edges = false; - static char *kwlist[] = { "mode", "min_cycle_length", "max_cycle_length", "output", NULL }; + static char *kwlist[] = { "mode", "min", "max", "output", NULL }; if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, &mode_o, &min_cycle_length_o, &max_cycle_length_o, &output_o)) return NULL; @@ -16624,16 +16624,16 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { }, {"simple_cycles", (PyCFunction) igraphmodule_Graph_simple_cycles, METH_VARARGS | METH_KEYWORDS, - "simple_cycles(mode=None, min_cycle_length=0, max_cycle_length=-1, output=\"vpath\")\n--\n\n" + "simple_cycles(mode=None, min=0, max=-1, output=\"vpath\")\n--\n\n" "Finds simple cycles in a graph\n\n" "@param mode: for directed graphs, specifies how the edge directions\n" " should be taken into account. C{\"all\"} means that the edge directions\n" " must be ignored, C{\"out\"} means that the edges must be oriented away\n" " from the root, C{\"in\"} means that the edges must be oriented\n" " towards the root. Ignored for undirected graphs.\n" - "@param min_cycle_length: the minimum number of vertices in a cycle\n" + "@param min: the minimum number of vertices in a cycle\n" " for it to be returned.\n" - "@param max_cycle_length: the maximum number of vertices in a cycle\n" + "@param max: the maximum number of vertices in a cycle\n" " for it to be considered.\n" "@param output: determines what should be returned. If this is\n" " C{\"vpath\"}, a list of tuples of vertex IDs will be returned. If this is\n" From 7f9a0f6ee847332c5ea5adc38fcd7bf4d12e1846 Mon Sep 17 00:00:00 2001 From: Tim Bernhard Date: Thu, 14 Nov 2024 11:38:27 +0100 Subject: [PATCH 4/4] Update src/_igraph/graphobject.c MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Szabolcs Horvát --- src/_igraph/graphobject.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/_igraph/graphobject.c b/src/_igraph/graphobject.c index a40ec7c45..00c3e4343 100644 --- a/src/_igraph/graphobject.c +++ b/src/_igraph/graphobject.c @@ -16624,7 +16624,7 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { }, {"simple_cycles", (PyCFunction) igraphmodule_Graph_simple_cycles, METH_VARARGS | METH_KEYWORDS, - "simple_cycles(mode=None, min=0, max=-1, output=\"vpath\")\n--\n\n" + "simple_cycles(mode=None, min=-1, max=-1, output=\"epath\")\n--\n\n" "Finds simple cycles in a graph\n\n" "@param mode: for directed graphs, specifies how the edge directions\n" " should be taken into account. C{\"all\"} means that the edge directions\n"