Skip to content

Commit

Permalink
Implement simple cycle search Python binding
Browse files Browse the repository at this point in the history
  • Loading branch information
GenieTim committed Nov 8, 2024
1 parent 912f7d9 commit be668d0
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ requires = [
# Workaround based on this commit:
# https://github.com/harfbuzz/uharfbuzz/commit/9b607bd06fb17fcb4abe3eab5c4f342ad08309d7
"setuptools>=64,<72.2.0; platform_python_implementation == 'PyPy'",
"setuptools>=64; platform_python_implementation != 'PyPy'"
"setuptools>=64; platform_python_implementation != 'PyPy'",
"cmake>=3.12"
]
build-backend = "setuptools.build_meta"

Expand Down
71 changes: 71 additions & 0 deletions src/_igraph/graphobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -7813,6 +7813,60 @@ 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 *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;

static char *kwlist[] = { "mode", "min_cycle_length", "max_cycle_length", NULL };

if (!PyArg_ParseTupleAndKeywords(args, kwds, "|OOO", kwlist, &mode_o, &min_cycle_length_o, &max_cycle_length_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;

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, &vertices, &edges, 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_edges_o;
result_edges_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&edges);
igraph_vector_int_list_destroy(&edges);

PyObject *result_vertices_o;
result_vertices_o = igraphmodule_vector_int_list_t_to_PyList_of_tuples(&vertices);
igraph_vector_int_list_destroy(&vertices);

PyObject *results;
results = Py_BuildValue("(OO)", result_vertices_o, result_edges_o);
return results;
}

/**********************************************************************
* Graph layout algorithms *
**********************************************************************/
Expand Down Expand Up @@ -16563,6 +16617,23 @@ 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)\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"
"@return: vertices, edges: a tuple with the vertices and edges, \n"
" respectively, each as a list of tuples containing edge and vertex\n"
" IDs, respectively."
},

/********************/
/* LAYOUT FUNCTIONS */
Expand Down
19 changes: 19 additions & 0 deletions tests/test_cycles.py
Original file line number Diff line number Diff line change
Expand Up @@ -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, edges = g.simple_cycles()
assert len(vertices) == 3
assert len(edges) == 3


def test_minimum_cycle_basis(self):
g = Graph(
[
Expand Down

0 comments on commit be668d0

Please sign in to comment.