Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "saturation first" and "independent set" greedy node coloring strategies #1138

Open
wants to merge 42 commits into
base: main
Choose a base branch
from

Conversation

alexanderivrii
Copy link
Contributor

@alexanderivrii alexanderivrii commented Mar 10, 2024

Description

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)

Update: 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)

@coveralls
Copy link

coveralls commented Mar 10, 2024

Pull Request Test Coverage Report for Build 9197702952

Warning: This coverage report may be inaccurate.

This pull request's base commit is no longer the HEAD commit of its target branch. This means it includes changes from outside the original pull request, including, potentially, unrelated coverage changes.

Details

  • 191 of 200 (95.5%) changed or added relevant lines in 3 files are covered.
  • 77 unchanged lines in 6 files lost coverage.
  • Overall coverage decreased (-0.7%) to 95.793%

Changes Missing Coverage Covered Lines Changed/Added Lines %
src/coloring.rs 28 29 96.55%
rustworkx-core/src/coloring.rs 162 170 95.29%
Files with Coverage Reduction New Missed Lines %
src/dag_algo/mod.rs 1 99.78%
rustworkx-core/src/generators/random_graph.rs 3 82.47%
src/lib.rs 9 97.66%
src/graph.rs 13 97.13%
src/digraph.rs 15 98.34%
rustworkx-core/src/coloring.rs 36 90.02%
Totals Coverage Status
Change from base Build 8887516283: -0.7%
Covered Lines: 17122
Relevant Lines: 17874

💛 - Coveralls

src/coloring.rs Outdated Show resolved Hide resolved
src/coloring.rs Outdated Show resolved Hide resolved
src/coloring.rs Outdated Show resolved Hide resolved
@alexanderivrii
Copy link
Contributor Author

I am confused about the "stubs" problems: what are these and how should I fix them?

rustworkx.GreedyStrategy cannot be subclassed at runtime, but isn't marked with @final in the stub
rustworkx.GreedyStrategy.Degree is not present in stub
rustworkx.GreedyStrategy.Saturation is not present in stub
rustworkx.rustworkx.GreedyStrategy cannot be subclassed at runtime, but isn't marked with @final in the stub
rustworkx.rustworkx.GreedyStrategy.Degree is not present in stub
rustworkx.rustworkx.GreedyStrategy.Saturation is not present in stub

@alexanderivrii alexanderivrii changed the title Add "saturation first" greedy node coloring strategy Add "saturation first" and "independent set" greedy node coloring strategies Mar 12, 2024
rustworkx/rustworkx.pyi Outdated Show resolved Hide resolved
@mtreinish mtreinish added this to the 0.15.0 milestone Apr 4, 2024
@alexanderivrii
Copy link
Contributor Author

I think I have addressed all of the comments from the previous review, this is ready for another round! :)

Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this is looking good. I left a few inline code suggestions but the basic algorithms look sound. The only things I think we need to settle on is the naming of some of the new interfaces. The other concern I have is around backwards compatibility for the public api for the function in rustworkx-core. The modifications to the interfaces for the existing functions are all potentially backwards incompatible and could cause issues for users upgrading. It might be better to split off the strategy mode to yet another new function to avoid that. (Although having 3 functions isn't ideal either)

Also I think this is missing a release note. Nevermind it's the first file I looked at

rustworkx-core/src/coloring.rs Outdated Show resolved Hide resolved
rustworkx-core/src/coloring.rs Outdated Show resolved Hide resolved
rustworkx-core/src/coloring.rs Outdated Show resolved Hide resolved
rustworkx-core/src/coloring.rs Outdated Show resolved Hide resolved
rustworkx-core/src/coloring.rs Outdated Show resolved Hide resolved
rustworkx-core/src/coloring.rs Show resolved Hide resolved
rustworkx-core/src/coloring.rs Outdated Show resolved Hide resolved
rustworkx-core/src/coloring.rs Outdated Show resolved Hide resolved
rustworkx/rustworkx.pyi Outdated Show resolved Hide resolved
@alexanderivrii
Copy link
Contributor Author

alexanderivrii commented May 22, 2024

Thanks @mtreinish, as suggested (and later discussed offline) I have retained the older version of the coloring functions in rustworkx-core and removed using the unnecessary NodeIndexable trait, making the code backwards compatible, at the expense of introducing two new function greedy_node_coloring_with_coloring_strategy and greedy_edge_color_with_coloring_strategy. I could not think of shorter names. I have made their return type to be Result<Dict<...>, E> instead of Dict<...>, which allows error handling. I have renamed the enums to ColoringStrategy and the function argument to strategy, as per your suggestions, I like these much better.

Copy link
Member

@mtreinish mtreinish left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall this looks great, thanks for all the diligent work to update things and fix the backwards compatibility. I just have one inline question about the API for the new functions in rustworkx-core (it applies to both the nodes and edges functions). Otherwise I think this is good to merge.

///
pub fn greedy_edge_color_with_coloring_strategy<G, F, E>(
graph: G,
preset_color_fn: F,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to not make this Option<F> since it's not mandatory. It just looks a bit awkward in the rustworkx crate side to handle the case when there isn't preset.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! I definitely agree it's better to make it Option<F>. With this change, the function above would look like

pub fn greedy_edge_color_with_coloring_strategy<G, F, E>(
    graph: G,
    preset_color_fn: Option<F>,
    strategy: ColoringStrategy,
) -> Result<DictMap<G::EdgeId, usize>, E>
where
    G: EdgeCount + IntoNodeIdentifiers + IntoEdges,
    G::EdgeId: Hash + Eq,
    F: Fn(G::EdgeId) -> Result<Option<usize>, E>,

So far so good. I thought that using this function (from say testing) now becomes something like

let preset_color_fn = |node_idx: EdgeIndex| -> Result<Option<usize>, Infallible> {
    if node_idx.index() == 1 {
        Ok(Some(1))
    } else {
        Ok(None)
    }
};
let colors = greedy_edge_color_with_coloring_strategy(
        &graph,
        Some(preset_color_fn),
        ColoringStrategy::Degree,
    )
    .unwrap();

for the case that we want to specify the preset coloring function, and

let colors = greedy_edge_color_with_coloring_strategy(
        &graph,
        None,
        ColoringStrategy::Degree,
    )
    .unwrap();

for the case that we do not. However the compiler does not like the second case:

    --> rustworkx-core\src\coloring.rs:1505:13
     |
1502 |         let colors = greedy_edge_color_with_coloring_strategy(
     |                      ---------------------------------------- required by a bound introduced by this call
...
1505 |             None,
     |             ^^^^ cannot infer type of the type parameter `T` declared on the enum `Option`
     |
     = note: multiple `impl`s satisfying `_: Fn<(petgraph::prelude::EdgeIndex,)>` found in the following crates: `alloc`, `core`:
     |        ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `greedy_edge_color_with_coloring_strategy`
help: consider specifying the generic argument
     |
1505 |             None::<T>,
     |                 +++++

and I can't figure out how to make this work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants