This repository has been archived on 2023-03-25. You can view files and clone it, but cannot push or open issues or pull requests.
mightyscape-1.1-deprecated/extensions/networkx/algorithms/coloring/greedy_coloring.py
2020-07-30 01:16:18 +02:00

363 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
# Copyright (C) 2014 by
# Christian Olsson <chro@itu.dk>
# Jan Aagaard Meier <jmei@itu.dk>
# Henrik Haugbølle <hhau@itu.dk>
# Arya McCarthy <admccarthy@smu.edu>
# All rights reserved.
# BSD license.
"""
Greedy graph coloring using various strategies.
"""
from collections import defaultdict, deque
import itertools
import networkx as nx
from networkx.utils import arbitrary_element
from networkx.utils import py_random_state
from . import greedy_coloring_with_interchange as _interchange
__all__ = ['greedy_color', 'strategy_connected_sequential',
'strategy_connected_sequential_bfs',
'strategy_connected_sequential_dfs', 'strategy_independent_set',
'strategy_largest_first', 'strategy_random_sequential',
'strategy_saturation_largest_first', 'strategy_smallest_last']
def strategy_largest_first(G, colors):
"""Returns a list of the nodes of ``G`` in decreasing order by
degree.
``G`` is a NetworkX graph. ``colors`` is ignored.
"""
return sorted(G, key=G.degree, reverse=True)
@py_random_state(2)
def strategy_random_sequential(G, colors, seed=None):
"""Returns a random permutation of the nodes of ``G`` as a list.
``G`` is a NetworkX graph. ``colors`` is ignored.
seed : integer, random_state, or None (default)
Indicator of random number generation state.
See :ref:`Randomness<randomness>`.
"""
nodes = list(G)
seed.shuffle(nodes)
return nodes
def strategy_smallest_last(G, colors):
"""Returns a deque of the nodes of ``G``, "smallest" last.
Specifically, the degrees of each node are tracked in a bucket queue.
From this, the node of minimum degree is repeatedly popped from the
graph, updating its neighbors' degrees.
``G`` is a NetworkX graph. ``colors`` is ignored.
This implementation of the strategy runs in $O(n + m)$ time
(ignoring polylogarithmic factors), where $n$ is the number of nodes
and $m$ is the number of edges.
This strategy is related to :func:`strategy_independent_set`: if we
interpret each node removed as an independent set of size one, then
this strategy chooses an independent set of size one instead of a
maximal independent set.
"""
H = G.copy()
result = deque()
# Build initial degree list (i.e. the bucket queue data structure)
degrees = defaultdict(set) # set(), for fast random-access removals
lbound = float('inf')
for node, d in H.degree():
degrees[d].add(node)
lbound = min(lbound, d) # Lower bound on min-degree.
def find_min_degree():
# Save time by starting the iterator at `lbound`, not 0.
# The value that we find will be our new `lbound`, which we set later.
return next(d for d in itertools.count(lbound) if d in degrees)
for _ in G:
# Pop a min-degree node and add it to the list.
min_degree = find_min_degree()
u = degrees[min_degree].pop()
if not degrees[min_degree]: # Clean up the degree list.
del degrees[min_degree]
result.appendleft(u)
# Update degrees of removed node's neighbors.
for v in H[u]:
degree = H.degree(v)
degrees[degree].remove(v)
if not degrees[degree]: # Clean up the degree list.
del degrees[degree]
degrees[degree - 1].add(v)
# Finally, remove the node.
H.remove_node(u)
lbound = min_degree - 1 # Subtract 1 in case of tied neighbors.
return result
def _maximal_independent_set(G):
"""Returns a maximal independent set of nodes in ``G`` by repeatedly
choosing an independent node of minimum degree (with respect to the
subgraph of unchosen nodes).
"""
result = set()
remaining = set(G)
while remaining:
G = G.subgraph(remaining)
v = min(remaining, key=G.degree)
result.add(v)
remaining -= set(G[v]) | {v}
return result
def strategy_independent_set(G, colors):
"""Uses a greedy independent set removal strategy to determine the
colors.
This function updates ``colors`` **in-place** and return ``None``,
unlike the other strategy functions in this module.
This algorithm repeatedly finds and removes a maximal independent
set, assigning each node in the set an unused color.
``G`` is a NetworkX graph.
This strategy is related to :func:`strategy_smallest_last`: in that
strategy, an independent set of size one is chosen at each step
instead of a maximal independent set.
"""
remaining_nodes = set(G)
while len(remaining_nodes) > 0:
nodes = _maximal_independent_set(G.subgraph(remaining_nodes))
remaining_nodes -= nodes
for v in nodes:
yield v
def strategy_connected_sequential_bfs(G, colors):
"""Returns an iterable over nodes in ``G`` in the order given by a
breadth-first traversal.
The generated sequence has the property that for each node except
the first, at least one neighbor appeared earlier in the sequence.
``G`` is a NetworkX graph. ``colors`` is ignored.
"""
return strategy_connected_sequential(G, colors, 'bfs')
def strategy_connected_sequential_dfs(G, colors):
"""Returns an iterable over nodes in ``G`` in the order given by a
depth-first traversal.
The generated sequence has the property that for each node except
the first, at least one neighbor appeared earlier in the sequence.
``G`` is a NetworkX graph. ``colors`` is ignored.
"""
return strategy_connected_sequential(G, colors, 'dfs')
def strategy_connected_sequential(G, colors, traversal='bfs'):
"""Returns an iterable over nodes in ``G`` in the order given by a
breadth-first or depth-first traversal.
``traversal`` must be one of the strings ``'dfs'`` or ``'bfs'``,
representing depth-first traversal or breadth-first traversal,
respectively.
The generated sequence has the property that for each node except
the first, at least one neighbor appeared earlier in the sequence.
``G`` is a NetworkX graph. ``colors`` is ignored.
"""
if traversal == 'bfs':
traverse = nx.bfs_edges
elif traversal == 'dfs':
traverse = nx.dfs_edges
else:
raise nx.NetworkXError("Please specify one of the strings 'bfs' or"
" 'dfs' for connected sequential ordering")
for component in nx.connected_components(G):
source = arbitrary_element(component)
# Yield the source node, then all the nodes in the specified
# traversal order.
yield source
for (_, end) in traverse(G.subgraph(component), source):
yield end
def strategy_saturation_largest_first(G, colors):
"""Iterates over all the nodes of ``G`` in "saturation order" (also
known as "DSATUR").
``G`` is a NetworkX graph. ``colors`` is a dictionary mapping nodes of
``G`` to colors, for those nodes that have already been colored.
"""
distinct_colors = {v: set() for v in G}
for i in range(len(G)):
# On the first time through, simply choose the node of highest degree.
if i == 0:
node = max(G, key=G.degree)
yield node
# Add the color 0 to the distinct colors set for each
# neighbors of that node.
for v in G[node]:
distinct_colors[v].add(0)
else:
# Compute the maximum saturation and the set of nodes that
# achieve that saturation.
saturation = {v: len(c) for v, c in distinct_colors.items()
if v not in colors}
# Yield the node with the highest saturation, and break ties by
# degree.
node = max(saturation, key=lambda v: (saturation[v], G.degree(v)))
yield node
# Update the distinct color sets for the neighbors.
color = colors[node]
for v in G[node]:
distinct_colors[v].add(color)
#: Dictionary mapping name of a strategy as a string to the strategy function.
STRATEGIES = {
'largest_first': strategy_largest_first,
'random_sequential': strategy_random_sequential,
'smallest_last': strategy_smallest_last,
'independent_set': strategy_independent_set,
'connected_sequential_bfs': strategy_connected_sequential_bfs,
'connected_sequential_dfs': strategy_connected_sequential_dfs,
'connected_sequential': strategy_connected_sequential,
'saturation_largest_first': strategy_saturation_largest_first,
'DSATUR': strategy_saturation_largest_first,
}
def greedy_color(G, strategy='largest_first', interchange=False):
"""Color a graph using various strategies of greedy graph coloring.
Attempts to color a graph using as few colors as possible, where no
neighbours of a node can have same color as the node itself. The
given strategy determines the order in which nodes are colored.
The strategies are described in [1]_, and smallest-last is based on
[2]_.
Parameters
----------
G : NetworkX graph
strategy : string or function(G, colors)
A function (or a string representing a function) that provides
the coloring strategy, by returning nodes in the ordering they
should be colored. ``G`` is the graph, and ``colors`` is a
dictionary of the currently assigned colors, keyed by nodes. The
function must return an iterable over all the nodes in ``G``.
If the strategy function is an iterator generator (that is, a
function with ``yield`` statements), keep in mind that the
``colors`` dictionary will be updated after each ``yield``, since
this function chooses colors greedily.
If ``strategy`` is a string, it must be one of the following,
each of which represents one of the built-in strategy functions.
* ``'largest_first'``
* ``'random_sequential'``
* ``'smallest_last'``
* ``'independent_set'``
* ``'connected_sequential_bfs'``
* ``'connected_sequential_dfs'``
* ``'connected_sequential'`` (alias for the previous strategy)
* ``'saturation_largest_first'``
* ``'DSATUR'`` (alias for the previous strategy)
interchange: bool
Will use the color interchange algorithm described by [3]_ if set
to ``True``.
Note that ``saturation_largest_first`` and ``independent_set``
do not work with interchange. Furthermore, if you use
interchange with your own strategy function, you cannot rely
on the values in the ``colors`` argument.
Returns
-------
A dictionary with keys representing nodes and values representing
corresponding coloring.
Examples
--------
>>> G = nx.cycle_graph(4)
>>> d = nx.coloring.greedy_color(G, strategy='largest_first')
>>> d in [{0: 0, 1: 1, 2: 0, 3: 1}, {0: 1, 1: 0, 2: 1, 3: 0}]
True
Raises
------
NetworkXPointlessConcept
If ``strategy`` is ``saturation_largest_first`` or
``independent_set`` and ``interchange`` is ``True``.
References
----------
.. [1] Adrian Kosowski, and Krzysztof Manuszewski,
Classical Coloring of Graphs, Graph Colorings, 2-19, 2004.
ISBN 0-8218-3458-4.
.. [2] David W. Matula, and Leland L. Beck, "Smallest-last
ordering and clustering and graph coloring algorithms." *J. ACM* 30,
3 (July 1983), 417427. <https://doi.org/10.1145/2402.322385>
.. [3] Maciej M. Sysło, Marsingh Deo, Janusz S. Kowalik,
Discrete Optimization Algorithms with Pascal Programs, 415-424, 1983.
ISBN 0-486-45353-7.
"""
if len(G) == 0:
return {}
# Determine the strategy provided by the caller.
strategy = STRATEGIES.get(strategy, strategy)
if not callable(strategy):
raise nx.NetworkXError('strategy must be callable or a valid string. '
'{0} not valid.'.format(strategy))
# Perform some validation on the arguments before executing any
# strategy functions.
if interchange:
if strategy is strategy_independent_set:
msg = 'interchange cannot be used with independent_set'
raise nx.NetworkXPointlessConcept(msg)
if strategy is strategy_saturation_largest_first:
msg = ('interchange cannot be used with'
' saturation_largest_first')
raise nx.NetworkXPointlessConcept(msg)
colors = {}
nodes = strategy(G, colors)
if interchange:
return _interchange.greedy_coloring_with_interchange(G, nodes)
for u in nodes:
# Set to keep track of colors of neighbours
neighbour_colors = {colors[v] for v in G[u] if v in colors}
# Find the first unused color.
for color in itertools.count():
if color not in neighbour_colors:
break
# Assign the new color to the current node.
colors[u] = color
return colors