Skip to content

Commit

Permalink
Adding edge coloring algorithm for bipartite graphs (#1026)
Browse files Browse the repository at this point in the history
### Summary

This PR adds a graph edge-coloring algorithm ``graph_bipartite_edge_color`` for **bipartite** graphs based on the paper "A simple algorithm for edge-coloring bipartite multigraphs" by Noga Alon, 2003. 

The above function first checks whether the graph is indeed bipartite, and raises an exception of type ``GraphNotBipartite`` if this is not the case. Otherwise, the function edge-colors the graph and returns  a dictionary with key being the edge index and value being the assigned color. This is the same output format as produced by other recently added edge-coloring functions ``graph_greedy_edge_color`` and ``graph_misra_gries_edge_color``. The algorithm uses exactly `d` colors when `d` is the maximum degree of a node in the graph.

Usage example:

```
import rustworkx as rx

graph = rx.generators.heavy_hex_graph(9)
edge_colors = rx.graph_bipartite_edge_color(graph)
num_colors = max(edge_colors.values()) + 1
assert num_colors == 3
```

The algorithm has a runtime of `O(m log m)` where `m` is the number of edges in the graph.

### A few technical details:

Internally this works with an undirected regular bipartite multigraph that 
- keeps an explicit partition of its nodes into "left nodes" and "right nodes"
- only the edges connecting a left node to a right node are allowed (this is what _bipartite_ means)
- each node in the graph has the same degree `r` (this is what _regular_ means)
- each edge keeps additional data representing its _multiplicity_ (aka _weight_) and whether or not the edge is _bad_ (it is important that multiple edges connecting the same pairs of nodes are grouped into a single edges with multiplicity)

The internal data structure for the above is called `RegularBipartiteMultiGraph`. I don't foresee it being used anywhere outside of this PR, and so it's not marked as ``pub``. 

There is one possible optimization that is mentioned in the paper but which I have not implemented. Let `r` be the maximum degree of a node in the original graph. If the original bipartite graph is not regular (i.e. some of its nodes have degree less than `r`), then extra vertices and edges are inserted to make it regular (and of degree `r`) with the final edge-coloring is obtained by restricting the edge-coloring of the multi-graph to original edges. In addition (this is the mentioned possible optimization), one could group multiple "left" vertices with total degree not exceeding `r` into a _single_ node in the multigraph; the same applies for subsets of right vertices. 

The implementation also contains a function ``euler_cycles`` that might be useful in general, however the flavor needed here is somewhat non-standard, and so I did not expose it. The standard definition assumes that the graph is connected and an _Euler cycle_ is a path that visits each edge exactly once and comes back to the starting vertex. In our case, however, the graph may be disconnected and the function ``euler_cycles`` returns a list of Euler cycles that visit each edge exactly once (however this is not an Euler cycle for the standard definition).

* very messy initial implementation

* adding random_bipartite_graph and some tests

* tests

* fix correctness checking functions

* imports in test module

* starting cleanup

* minor

* cleaning pass

* fmt

* fix for empty graphs

* python tests + fmt

* release notes

* first round of clippy

* clippy

* minor

* pylint

* fmt

* renaming

* more renaming

* api docs

* release notes fix

* correct release notes fix

* yet another fix

* Add Python type annotation to stub files

* Fix stubs

* adding python interface for creating random bipartite graphs, both directed and undirected

* stubs

* updating runtime complexity to include the number of nodes

* derive(Clone)

* panic! -> unreachable!

* making bipartite_edge_color_with_partitions non-public, cleaning and polishing tests
  • Loading branch information
alexanderivrii authored Jan 23, 2024
1 parent e39ecc6 commit dcece27
Show file tree
Hide file tree
Showing 14 changed files with 1,573 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/source/api/algorithm_functions/coloring.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ Coloring
:toctree: ../../apiref

rustworkx.graph_greedy_color
rustworkx.graph_bipartite_edge_color
rustworkx.graph_greedy_edge_color
rustworkx.graph_misra_gries_edge_color
2 changes: 2 additions & 0 deletions docs/source/api/random_graph_generator_functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ Random Graph Generator Functions
rustworkx.random_geometric_graph
rustworkx.barabasi_albert_graph
rustworkx.directed_barabasi_albert_graph
rustworkx.directed_random_bipartite_graph
rustworkx.undirected_random_bipartite_graph
42 changes: 42 additions & 0 deletions releasenotes/notes/edge-color-bipartite-70d52fc23c49ab4f.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
features:
- |
Added a new exception class :class:`~.GraphNotBipartite` which is raised when a
graph is not bipartite. The sole user of this exception is the :func:`~.graph_bipartite_edge_color`
which will raise it when the user provided graph is not bipartite.
- |
Added a new function, :func:`~.graph_bipartite_edge_color` to color edges
of a :class:`~.PyGraph` object. The function first checks whether a graph is
bipartite, raising exception of type :class:`~.GraphNotBipartite` if this is not the case.
Otherwise, the function calls the algorithm for edge-coloring bipartite graphs,
and returns a dictionary with key being the edge index and value being the assigned
color.
The implemented algorithm is based on the paper "A simple algorithm for edge-coloring
bipartite multigraphs" by Noga Alon, 2003.
The coloring produces at most d colors where d is the maximum degree of a node in the graph.
The algorithm runs in time ``O (n + m log m)``, where ``n`` is the number of vertices and
``m`` is the number of edges in the graph.
.. jupyter-execute::
import rustworkx as rx
from rustworkx.visualization import mpl_draw
graph = rx.generators.cycle_graph(8)
edge_colors = rx.graph_bipartite_edge_color(graph)
assert edge_colors == {0: 0, 1: 1, 2: 0, 3: 1, 4: 0, 5: 1, 6: 0, 7: 1}
mpl_draw(graph, edge_color=[edge_colors[i] for i in range(graph.num_edges())])
- |
Added two new random graph generator functions,
:func:`.directed_random_bipartite_graph` and :func:`.undirected_random_bipartite_graph`,
to generate a random bipartite graph. For example:
.. jupyter-execute::
import rustworkx as rx
from rustworkx.visualization import mpl_draw
random_graph = rx.undirected_random_bipartite_graph(10, 5, 0.5, seed=20)
mpl_draw(random_graph)
Loading

0 comments on commit dcece27

Please sign in to comment.