Skip to content

Commit

Permalink
Add "saturation first" and "independent set" greedy node coloring str…
Browse files Browse the repository at this point in the history
…ategies (#1138)

This PR adds two more standard coloring strategies to greedy node/edge coloring functions graph_greedy_color and graph_greedy_edge_color.

In the literature the first strategy is known as "saturation", "DSATUR" or "SLF". We dynamically choose the vertex that has the largest number of different colors already assigned to its neighbors, and, in case of a tie, the vertex that has the largest number of uncolored neighbors.

The second strategy is known as "greedy independent sets" or "GIS". We greedily find independent subsets of the graph, and assign a different color to each of these subsets.

This addresses #1137, modulo the fact that there are quadrillions of different greedy node coloring algorithms, and each comes with trillion variations.

Both new strategies can be combined with preset_color_fn (for node coloring).

There is also a new Rust enum GreedyStrategy exposed as a python class that allows to specify which of the three currently supported greedy strategies is used. This is, calling the functions from the Python code would be as follows:

import rustworkx as rx

graph = rx.generators.generalized_petersen_graph(5, 2)

coloring = rx.graph_greedy_color(graph, greedy_strategy=rx.GreedyStrategy.Degree)
coloring = rx.graph_greedy_color(graph, greedy_strategy=rx.GreedyStrategy.Saturation)
coloring = rx.graph_greedy_color(graph, greedy_strategy=rx.GreedyStrategy.IndependentSet)
coloring = rx.graph_greedy_color(graph)
coloring = rx.graph_greedy_edge_color(graph)
coloring = rx.graph_greedy_edge_color(graph, greedy_strategy=rx.GreedyStrategy.Degree)
coloring = rx.graph_greedy_edge_color(graph, greedy_strategy=rx.GreedyStrategy.Saturation)
coloring = rx.graph_greedy_edge_color(graph, greedy_strategy=rx.GreedyStrategy.IndependentSet)

The greedy_graph_edge_color function has been also extended to support the preset_color_fn argument (though in this case it's an optional map from an edge index to either a color or to None)


* initial commit

* fix and test

* python formatting

* creating enums

* also passing greedy strategy to edge coloring functions; improving docstrings

* release notes'

* python test

* reno fix

* reno fix

* minor cleanup

* small rust cleanup

* adding greedy node coloring using  maximal independent set + reworked tests

* updating top-level source, tests and docs

* docs

* applying fix from code review

* adding IndependentSet to pyi as well

* Adding a table that describes greedy strategies available

* converting table to sphinx-style

* fix reference to footnotes

* docs improvements

* another attempt

* attempt to extend API with preset_color_fn for edges

* minor

* finally compiling

* fixing pyi API and edding python tests

* adding rustworkx-core tests

* adding arguments to docstrings

* expanding release note

* another docs fix

* Update releasenotes/notes/saturation-greedy-color-109d40f189590d3a.yaml

Co-authored-by: Matthew Treinish <[email protected]>

* a round of renaming

* restoring greedy_node_coloring API for backwards compatibility

* restoring greedy_edge_coloring API for backwards compatibility

* fix to rustworkx coloring

* changing return type of new rustworkx-core functions to be Result

* black

* removing NodeIndexable in node coloring functions

* and from edge coloring functions

* suggestions from code review

* suggestions from code review

* release note update

---------

Co-authored-by: Matthew Treinish <[email protected]>
  • Loading branch information
alexanderivrii and mtreinish authored May 30, 2024
1 parent e4dae8c commit 8af19f0
Show file tree
Hide file tree
Showing 8 changed files with 1,216 additions and 27 deletions.
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 @@ -6,6 +6,7 @@ Coloring
.. autosummary::
:toctree: ../../apiref

rustworkx.ColoringStrategy
rustworkx.graph_greedy_color
rustworkx.graph_bipartite_edge_color
rustworkx.graph_greedy_edge_color
Expand Down
59 changes: 59 additions & 0 deletions releasenotes/notes/saturation-greedy-color-109d40f189590d3a.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
features:
- Added a new class :class:`~.ColoringStrategy` used to specify the strategy used by
the greedy node and edge coloring algorithms.
The ``Degree`` strategy colors the nodes with higher degree first.
The ``Saturation`` strategy dynamically chooses the vertex that has the largest
number of different colors already assigned to its neighbors, and, in case of a tie,
the vertex that has the largest number of uncolored neighbors.
The ``IndependentSet`` strategy finds independent subsets of the graph, and assigns
a different color to each of these subsets.
- |
The rustworkx-core ``coloring`` module has 2 new functions,
``greedy_node_color_with_coloring_strategy`` and
``greedy_edge_color_with_coloring_strategy``.
These functions color respectively the nodes or the edges of the graph
using the specified coloring strategy and handling the preset colors
when provided.
- |
Added a new keyword argument, ``strategy``, to :func:`.graph_greedy_color`
and to :func:`.graph_greedy_edge_color` to specify the greedy coloring strategy.
For example:
.. jupyter-execute::
import rustworkx as rx
from rustworkx.visualization import mpl_draw
graph = rx.generators.generalized_petersen_graph(5, 2)
coloring = rx.graph_greedy_color(graph, strategy=rx.ColoringStrategy.Saturation)
colors = [coloring[node] for node in graph.node_indices()]
layout = rx.shell_layout(graph, nlist=[[0, 1, 2, 3, 4],[6, 7, 8, 9, 5]])
mpl_draw(graph, node_color=colors, pos=layout)
- |
Added a new keyword argument, ``preset_color_fn``, to :func:`.graph_greedy_edge_color`
which is used to provide preset colors for specific edges when computing the graph
coloring. You can optionally pass a callable to that argument which will
be passed edge index from the graph and is either expected to return an
integer color to use for that edge, or `None` to indicate there is no
preset color for that edge. For example:
.. jupyter-execute::
import rustworkx as rx
from rustworkx.visualization import mpl_draw
graph = rx.generators.generalized_petersen_graph(5, 2)
def preset_colors(edge_index):
if edge_index == 0:
return 3
coloring = rx.graph_greedy_edge_color(graph, preset_color_fn=preset_colors)
colors = [coloring[edge] for edge in graph.edge_indices()]
layout = rx.shell_layout(graph, nlist=[[0, 1, 2, 3, 4], [6, 7, 8, 9, 5]])
mpl_draw(graph, edge_color=colors, pos=layout)
Loading

0 comments on commit 8af19f0

Please sign in to comment.