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

363 lines
13 KiB
Python
Raw Normal View History

2020-07-30 01:16:18 +02:00
# -*- 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