Skip to content

Commit 8292280

Browse files
authored
feat: implement simple cycle search Python binding (#806)
1 parent 63cfd01 commit 8292280

File tree

3 files changed

+104
-0
lines changed

3 files changed

+104
-0
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
- Dropped support for Python 3.8 as it has now reached its end of life.
88

99
- The C core of igraph was updated to version 0.10.15.
10+
- Added `Graph.simple_cycles()` to find simple cycles in the graph.
1011

1112
## [0.11.8] - 2024-10-25
1213

src/_igraph/graphobject.c

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7813,6 +7813,72 @@ PyObject *igraphmodule_Graph_minimum_cycle_basis(
78137813
return result_o;
78147814
}
78157815

7816+
7817+
PyObject *igraphmodule_Graph_simple_cycles(
7818+
igraphmodule_GraphObject *self, PyObject *args, PyObject *kwds
7819+
) {
7820+
PyObject *mode_o = Py_None;
7821+
PyObject *output_o = Py_None;
7822+
PyObject *min_cycle_length_o = Py_None;
7823+
PyObject *max_cycle_length_o = Py_None;
7824+
7825+
// argument defaults: no cycle limits
7826+
igraph_integer_t mode = IGRAPH_OUT;
7827+
igraph_integer_t min_cycle_length = -1;
7828+
igraph_integer_t max_cycle_length = -1;
7829+
igraph_bool_t use_edges = false;
7830+
7831+
static char *kwlist[] = { "mode", "min", "max", "output", NULL };
7832+
7833+
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOOO", kwlist, &mode_o, &min_cycle_length_o, &max_cycle_length_o, &output_o))
7834+
return NULL;
7835+
7836+
if (mode_o != Py_None && igraphmodule_PyObject_to_integer_t(mode_o, &mode))
7837+
return NULL;
7838+
7839+
if (min_cycle_length_o != Py_None && igraphmodule_PyObject_to_integer_t(min_cycle_length_o, &min_cycle_length))
7840+
return NULL;
7841+
7842+
if (max_cycle_length_o != Py_None && igraphmodule_PyObject_to_integer_t(max_cycle_length_o, &max_cycle_length))
7843+
return NULL;
7844+
7845+
if (igraphmodule_PyObject_to_vpath_or_epath(output_o, &use_edges))
7846+
return NULL;
7847+
7848+
igraph_vector_int_list_t vertices;
7849+
if (igraph_vector_int_list_init(&vertices, 0)) {
7850+
igraphmodule_handle_igraph_error();
7851+
return NULL;
7852+
}
7853+
igraph_vector_int_list_t edges;
7854+
if (igraph_vector_int_list_init(&edges, 0)) {
7855+
igraph_vector_int_list_destroy(&vertices);
7856+
igraphmodule_handle_igraph_error();
7857+
return NULL;
7858+
}
7859+
7860+
if (igraph_simple_cycles(
7861+
&self->g, use_edges ? NULL : &vertices, use_edges ? &edges : NULL, mode, min_cycle_length, max_cycle_length
7862+
)) {
7863+
igraph_vector_int_list_destroy(&vertices);
7864+
igraph_vector_int_list_destroy(&edges);
7865+
igraphmodule_handle_igraph_error();
7866+
return NULL;
7867+
}
7868+
7869+
PyObject *result_o;
7870+
7871+
if (use_edges) {
7872+
result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&edges);
7873+
} else {
7874+
result_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&vertices);
7875+
}
7876+
igraph_vector_int_list_destroy(&edges);
7877+
igraph_vector_int_list_destroy(&vertices);
7878+
7879+
return result_o;
7880+
}
7881+
78167882
/**********************************************************************
78177883
* Graph layout algorithms *
78187884
**********************************************************************/
@@ -16565,6 +16631,24 @@ struct PyMethodDef igraphmodule_Graph_methods[] = {
1656516631
" no guarantees are given about the ordering of edge IDs within cycles.\n"
1656616632
"@return: the cycle basis as a list of tuples containing edge IDs"
1656716633
},
16634+
{"simple_cycles", (PyCFunction) igraphmodule_Graph_simple_cycles,
16635+
METH_VARARGS | METH_KEYWORDS,
16636+
"simple_cycles(mode=None, min=-1, max=-1, output=\"epath\")\n--\n\n"
16637+
"Finds simple cycles in a graph\n\n"
16638+
"@param mode: for directed graphs, specifies how the edge directions\n"
16639+
" should be taken into account. C{\"all\"} means that the edge directions\n"
16640+
" must be ignored, C{\"out\"} means that the edges must be oriented away\n"
16641+
" from the root, C{\"in\"} means that the edges must be oriented\n"
16642+
" towards the root. Ignored for undirected graphs.\n"
16643+
"@param min: the minimum number of vertices in a cycle\n"
16644+
" for it to be returned.\n"
16645+
"@param max: the maximum number of vertices in a cycle\n"
16646+
" for it to be considered.\n"
16647+
"@param output: determines what should be returned. If this is\n"
16648+
" C{\"vpath\"}, a list of tuples of vertex IDs will be returned. If this is\n"
16649+
" C{\"epath\"}, edge IDs are returned instead of vertex IDs.\n"
16650+
"@return: see the documentation of the C{output} parameter.\n"
16651+
},
1656816652

1656916653
/********************/
1657016654
/* LAYOUT FUNCTIONS */

tests/test_cycles.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,25 @@ def test_fundamental_cycles(self):
6060
]
6161
assert cycles == [[6, 7, 10], [8, 9, 10]]
6262

63+
def test_simple_cycles(self):
64+
g = Graph(
65+
[
66+
(0, 1),
67+
(1, 2),
68+
(2, 0),
69+
(0, 0),
70+
(0, 3),
71+
(3, 4),
72+
(4, 5),
73+
(5, 0),
74+
]
75+
)
76+
77+
vertices = g.simple_cycles(output="vpath")
78+
edges = g.simple_cycles(output="epath")
79+
assert len(vertices) == 3
80+
assert len(edges) == 3
81+
6382
def test_minimum_cycle_basis(self):
6483
g = Graph(
6584
[

0 commit comments

Comments
 (0)