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_with_interchange.py
2020-07-30 01:16:18 +02:00

180 lines
6.5 KiB
Python

import itertools
__all__ = ['greedy_coloring_with_interchange']
class Node(object):
__slots__ = ['node_id', 'color', 'adj_list', 'adj_color']
def __init__(self, node_id, n):
self.node_id = node_id
self.color = -1
self.adj_list = None
self.adj_color = [None for _ in range(n)]
def __repr__(self):
return "Node_id: {0}, Color: {1}, Adj_list: ({2}), \
adj_color: ({3})".format(
self.node_id, self.color, self.adj_list, self.adj_color)
def assign_color(self, adj_entry, color):
adj_entry.col_prev = None
adj_entry.col_next = self.adj_color[color]
self.adj_color[color] = adj_entry
if adj_entry.col_next is not None:
adj_entry.col_next.col_prev = adj_entry
def clear_color(self, adj_entry, color):
if adj_entry.col_prev is None:
self.adj_color[color] = adj_entry.col_next
else:
adj_entry.col_prev.col_next = adj_entry.col_next
if adj_entry.col_next is not None:
adj_entry.col_next.col_prev = adj_entry.col_prev
def iter_neighbors(self):
adj_node = self.adj_list
while adj_node is not None:
yield adj_node
adj_node = adj_node.next
def iter_neighbors_color(self, color):
adj_color_node = self.adj_color[color]
while adj_color_node is not None:
yield adj_color_node.node_id
adj_color_node = adj_color_node.col_next
class AdjEntry(object):
__slots__ = ['node_id', 'next', 'mate', 'col_next', 'col_prev']
def __init__(self, node_id):
self.node_id = node_id
self.next = None
self.mate = None
self.col_next = None
self.col_prev = None
def __repr__(self):
return "Node_id: {0}, Next: ({1}), Mate: ({2}), \
col_next: ({3}), col_prev: ({4})".format(
self.node_id,
self.next,
self.mate.node_id,
None if self.col_next is None else self.col_next.node_id,
None if self.col_prev is None else self.col_prev.node_id
)
def greedy_coloring_with_interchange(original_graph, nodes):
"""
This procedure is an adaption of the algorithm described by [1]_,
and is an implementation of coloring with interchange. Please be
advised, that the datastructures used are rather complex because
they are optimized to minimize the time spent identifying
subcomponents of the graph, which are possible candidates for color
interchange.
References
----------
.. [1] Maciej M. Syslo, Marsingh Deo, Janusz S. Kowalik,
Discrete Optimization Algorithms with Pascal Programs, 415-424, 1983.
ISBN 0-486-45353-7.
"""
n = len(original_graph)
graph = {node_id: Node(node_id, n) for node_id in original_graph}
for (node1, node2) in original_graph.edges():
adj_entry1 = AdjEntry(node2)
adj_entry2 = AdjEntry(node1)
adj_entry1.mate = adj_entry2
adj_entry2.mate = adj_entry1
node1_head = graph[node1].adj_list
adj_entry1.next = node1_head
graph[node1].adj_list = adj_entry1
node2_head = graph[node2].adj_list
adj_entry2.next = node2_head
graph[node2].adj_list = adj_entry2
k = 0
for node in nodes:
# Find the smallest possible, unused color
neighbors = graph[node].iter_neighbors()
col_used = {graph[adj_node.node_id].color for adj_node in neighbors}
col_used.discard(-1)
k1 = next(itertools.dropwhile(
lambda x: x in col_used, itertools.count()))
# k1 is now the lowest available color
if k1 > k:
connected = True
visited = set()
col1 = -1
col2 = -1
while connected and col1 < k:
col1 += 1
neighbor_cols = (
graph[node].iter_neighbors_color(col1))
col1_adj = [it for it in neighbor_cols]
col2 = col1
while connected and col2 < k:
col2 += 1
visited = set(col1_adj)
frontier = list(col1_adj)
i = 0
while i < len(frontier):
search_node = frontier[i]
i += 1
col_opp = (
col2 if graph[search_node].color == col1 else col1)
neighbor_cols = (
graph[search_node].iter_neighbors_color(col_opp))
for neighbor in neighbor_cols:
if neighbor not in visited:
visited.add(neighbor)
frontier.append(neighbor)
# Search if node is not adj to any col2 vertex
connected = len(visited.intersection(
graph[node].iter_neighbors_color(col2))) > 0
# If connected is false then we can swap !!!
if not connected:
# Update all the nodes in the component
for search_node in visited:
graph[search_node].color = (
col2 if graph[search_node].color == col1 else col1)
col2_adj = graph[search_node].adj_color[col2]
graph[search_node].adj_color[col2] = (
graph[search_node].adj_color[col1])
graph[search_node].adj_color[col1] = col2_adj
# Update all the neighboring nodes
for search_node in visited:
col = graph[search_node].color
col_opp = col1 if col == col2 else col2
for adj_node in graph[search_node].iter_neighbors():
if graph[adj_node.node_id].color != col_opp:
# Direct reference to entry
adj_mate = adj_node.mate
graph[adj_node.node_id].clear_color(
adj_mate, col_opp)
graph[adj_node.node_id].assign_color(adj_mate, col)
k1 = col1
# We can color this node color k1
graph[node].color = k1
k = max(k1, k)
# Update the neighbors of this node
for adj_node in graph[node].iter_neighbors():
adj_mate = adj_node.mate
graph[adj_node.node_id].assign_color(adj_mate, k1)
return {node.node_id: node.color for node in graph.values()}