diff --git a/src/_igraph/graphobject.c b/src/_igraph/graphobject.c index 7a5f483cd..4315a6df0 100644 --- a/src/_igraph/graphobject.c +++ b/src/_igraph/graphobject.c @@ -2975,6 +2975,61 @@ PyObject *igraphmodule_Graph_Realize_Degree_Sequence(PyTypeObject *type, } +/** \ingroup python_interface_graph + * \brief Generates a graph with a specified degree sequence + * \return a reference to the newly generated Python igraph object + * \sa igraph_realize_bipartite_degree_sequence + */ +PyObject *igraphmodule_Graph_Realize_Bipartite_Degree_Sequence(PyTypeObject *type, + PyObject *args, PyObject *kwds) { + + igraph_vector_int_t degrees1, degrees2; + igraph_edge_type_sw_t allowed_edge_types = IGRAPH_SIMPLE_SW; + igraph_realize_degseq_t method = IGRAPH_REALIZE_DEGSEQ_SMALLEST; + PyObject *degrees1_o, *degrees2_o; + PyObject *edge_types_o = Py_None, *method_o = Py_None; + igraphmodule_GraphObject *self; + igraph_t g; + + static char *kwlist[] = { "degrees1", "degrees2", "allowed_edge_types", "method", NULL }; + if (!PyArg_ParseTupleAndKeywords(args, kwds, "OO|OO", kwlist, + °rees1_o, °rees2_o, &edge_types_o, &method_o)) + return NULL; + + /* allowed edge types */ + if (igraphmodule_PyObject_to_edge_type_sw_t(edge_types_o, &allowed_edge_types)) + return NULL; + + /* methods */ + if (igraphmodule_PyObject_to_realize_degseq_t(method_o, &method)) + return NULL; + + /* First degree vector */ + if (igraphmodule_PyObject_to_vector_int_t(degrees1_o, °rees1)) + return NULL; + + /* Second degree vector */ + if (igraphmodule_PyObject_to_vector_int_t(degrees2_o, °rees2)) { + igraph_vector_int_destroy(°rees1); + return NULL; + } + + if (igraph_realize_bipartite_degree_sequence(&g, °rees1, °rees2, allowed_edge_types, method)) { + igraph_vector_int_destroy(°rees1); + igraph_vector_int_destroy(°rees2); + igraphmodule_handle_igraph_error(); + return NULL; + } + + igraph_vector_int_destroy(°rees1); + igraph_vector_int_destroy(°rees2); + + CREATE_GRAPH_FROM_TYPE(self, g, type); + + return (PyObject *) self; +} + + /** \ingroup python_interface_graph * \brief Generates a graph based on vertex types and connection preferences * \return a reference to the newly generated Python igraph object @@ -14247,6 +14302,37 @@ struct PyMethodDef igraphmodule_Graph_methods[] = { " See Horvát and Modes (2021) for details.\n" }, + {"Realize_Bipartite_Degree_Sequence", (PyCFunction) igraphmodule_Graph_Realize_Bipartite_Degree_Sequence, + METH_VARARGS | METH_CLASS | METH_KEYWORDS, + "Realize_Bipartite_Degree_Sequence(degrees1, degrees2, allowed_edge_types=\"simple\", method=\"smallest\")\n--\n\n" + "Generates a bipartite graph from the degree sequences of its partitions.\n" + "\n" + "This method implements a Havel-Hakimi style graph construction for biparite\n" + "graphs. In each step, the algorithm picks two vertices in a deterministic\n" + "manner and connects them. The way the vertices are picked is defined by the\n" + "C{method} parameter. The allowed edge types (i.e. whether multi-edges are allowed)\n" + "are specified in the C{allowed_edge_types} parameter. Self-loops are never created,\n" + "since a graph with self-loops is not bipartite.\n" + "\n" + "@param degrees1: the degrees of the first partition.\n" + "@param degrees2: the degrees of the second partition.\n" + "@param allowed_edge_types: controls whether multi-edges are allowed\n" + " during the generation process. Possible values are:\n" + "\n" + " - C{\"simple\"}: simple graphs (no multi-edges)\n" + " - C{\"multi\"}: multi-edges allowed\n" + "\n" + "@param method: controls how the vertices are selected during the generation\n" + " process. Possible values are:\n" + "\n" + " - C{smallest}: The vertex with smallest remaining degree first.\n" + " - C{largest}: The vertex with the largest remaining degree first.\n" + " - C{index}: The vertices are selected in order of their index.\n" + "\n" + " The smallest C{smallest} method is guaranteed to produce a connected graph,\n" + " if one exists." + }, + // interface to igraph_ring {"Ring", (PyCFunction) igraphmodule_Graph_Ring, METH_VARARGS | METH_CLASS | METH_KEYWORDS, diff --git a/tests/test_generators.py b/tests/test_generators.py index 31b8692bc..17205eb95 100644 --- a/tests/test_generators.py +++ b/tests/test_generators.py @@ -249,6 +249,67 @@ def testRealizeDegreeSequence(self): self.assertFalse(g.is_directed()) self.assertTrue(g.degree() == degrees) + def testRealizeBipartiteDegreeSequence(self): + deg1 = [2, 2] + deg2 = [1, 1, 2] + g = Graph.Realize_Bipartite_Degree_Sequence( + deg1, + deg2, + "simple", + "smallest", + ) + self.assertFalse(g.is_directed()) + self.assertTrue(g.is_connected()) + self.assertTrue(g.degree() == deg1 + deg2) + + g = Graph.Realize_Bipartite_Degree_Sequence( + deg1, + deg2, + "simple", + "largest", + ) + self.assertFalse(g.is_directed()) + self.assertTrue(g.degree() == deg1 + deg2) + + g = Graph.Realize_Bipartite_Degree_Sequence( + deg1, + deg2, + "simple", + "index", + ) + self.assertFalse(g.is_directed()) + self.assertTrue(g.degree() == deg1 + deg2) + + deg1 = [3, 1, 1] + deg2 = [2, 3] + self.assertRaises( + InternalError, + Graph.Realize_Bipartite_Degree_Sequence, + deg1, + deg2, + "simple", + "smallest", + ) + + self.assertRaises( + InternalError, + Graph.Realize_Bipartite_Degree_Sequence, + deg1, + deg2, + "simple", + "index", + ) + + g = Graph.Realize_Bipartite_Degree_Sequence( + deg1, + deg2, + "multi", + "smallest", + ) + self.assertFalse(g.is_directed()) + self.assertTrue(g.is_connected()) + self.assertTrue(g.degree() == deg1 + deg2) + def testKautz(self): g = Graph.Kautz(2, 2) deg_in = g.degree(mode="in")