Skip to content
Snippets Groups Projects
Commit dca62534 authored by Mikael Henriksson's avatar Mikael Henriksson :runner:
Browse files

Add support for node selecting strategy in greedy_color of ProcessCollection...

Add support for node selecting strategy in greedy_color of ProcessCollection spliting methods (closes #175)
parent 3d273210
No related branches found
No related tags found
1 merge request!204Add support for node selecting strategy in greedy_color of ProcessCollection spliting methods (closes #175)
Pipeline #89928 passed
...@@ -187,10 +187,12 @@ class ProcessCollection: ...@@ -187,10 +187,12 @@ class ProcessCollection:
# Generate the life-time chart # Generate the life-time chart
for i, process in enumerate(_sorted_nicely(self._collection)): for i, process in enumerate(_sorted_nicely(self._collection)):
bar_start = process.start_time % self._schedule_time bar_start = process.start_time % self._schedule_time
bar_end = process.start_time + process.execution_time
bar_end = ( bar_end = (
process.start_time + process.execution_time bar_end
) % self._schedule_time if bar_end == self._schedule_time
bar_end = self._schedule_time if bar_end == 0 else bar_end else bar_end % self._schedule_time
)
if show_markers: if show_markers:
_ax.scatter( _ax.scatter(
x=bar_start, x=bar_start,
...@@ -240,16 +242,84 @@ class ProcessCollection: ...@@ -240,16 +242,84 @@ class ProcessCollection:
_ax.set_ylim(0.25, len(self._collection) + 0.75) _ax.set_ylim(0.25, len(self._collection) + 0.75)
return _ax return _ax
def create_exclusion_graph_from_overlap( def create_exclusion_graph_from_ports(
self, add_name: bool = True self,
read_ports: Optional[int] = None,
write_ports: Optional[int] = None,
total_ports: Optional[int] = None,
) -> nx.Graph: ) -> nx.Graph:
""" """
Generate exclusion graph based on processes overlapping in time Create an exclusion graph from a ProcessCollection based on a number of read/write ports
Parameters Parameters
---------- ----------
add_name : bool, default: True read_ports : int
Add name of all processes as a node attribute in the exclusion graph. The number of read ports used when splitting process collection based on memory variable access.
write_ports : int
The number of write ports used when splitting process collection based on memory variable access.
total_ports : int
The total number of ports used when splitting process collection based on memory variable access.
Returns
-------
nx.Graph
"""
if total_ports is None:
if read_ports is None or write_ports is None:
raise ValueError(
"If total_ports is unset, both read_ports and write_ports"
" must be provided."
)
else:
total_ports = read_ports + write_ports
else:
read_ports = total_ports if read_ports is None else read_ports
write_ports = total_ports if write_ports is None else write_ports
# Guard for proper read/write port settings
if read_ports != 1 or write_ports != 1:
raise ValueError(
"Splitting with read and write ports not equal to one with the"
" graph coloring heuristic does not make sense."
)
if total_ports not in (1, 2):
raise ValueError(
"Total ports should be either 1 (non-concurrent reads/writes)"
" or 2 (concurrent read/writes) for graph coloring heuristic."
)
# Create new exclusion graph. Nodes are Processes
exclusion_graph = nx.Graph()
exclusion_graph.add_nodes_from(self._collection)
for node1 in exclusion_graph:
for node2 in exclusion_graph:
if node1 == node2:
continue
else:
node1_stop_time = node1.start_time + node1.execution_time
node2_stop_time = node2.start_time + node2.execution_time
if total_ports == 1:
# Single-port assignment
if node1.start_time == node2.start_time:
exclusion_graph.add_edge(node1, node2)
elif node1_stop_time == node2_stop_time:
exclusion_graph.add_edge(node1, node2)
elif node1.start_time == node2_stop_time:
exclusion_graph.add_edge(node1, node2)
elif node1_stop_time == node2.start_time:
exclusion_graph.add_edge(node1, node2)
else:
# Dual-port assignment
if node1.start_time == node2.start_time:
exclusion_graph.add_edge(node1, node2)
elif node1_stop_time == node2_stop_time:
exclusion_graph.add_edge(node1, node2)
return exclusion_graph
def create_exclusion_graph_from_execution_time(self) -> nx.Graph:
"""
Generate exclusion graph based on processes overlapping in time
Returns Returns
------- -------
...@@ -279,7 +349,47 @@ class ProcessCollection: ...@@ -279,7 +349,47 @@ class ProcessCollection:
exclusion_graph.add_edge(process1, process2) exclusion_graph.add_edge(process1, process2)
return exclusion_graph return exclusion_graph
def split( def split_execution_time(
self, heuristic: str = "graph_color", coloring_strategy: str = "DSATUR"
) -> Set["ProcessCollection"]:
"""
Split a ProcessCollection based on overlapping execution time.
Parameters
----------
heuristic : str, default: 'graph_color'
The heuristic used when splitting based on execution times.
One of: 'graph_color', 'left_edge'.
coloring_strategy: str, default: 'DSATUR'
Node ordering strategy passed to nx.coloring.greedy_color() if the heuristic is set to 'graph_color'. This
parameter is only considered if heuristic is set to graph_color.
One of
* `'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 saturation_largest_first strategy)
Returns
-------
A set of new ProcessCollection objects with the process splitting.
"""
if heuristic == "graph_color":
exclusion_graph = self.create_exclusion_graph_from_execution_time()
coloring = nx.coloring.greedy_color(
exclusion_graph, strategy=coloring_strategy
)
return self._split_from_graph_coloring(coloring)
elif heuristic == "left_edge":
raise NotImplementedError()
else:
raise ValueError(f"Invalid heuristic '{heuristic}'")
def split_ports(
self, self,
heuristic: str = "graph_color", heuristic: str = "graph_color",
read_ports: Optional[int] = None, read_ports: Optional[int] = None,
...@@ -309,77 +419,79 @@ class ProcessCollection: ...@@ -309,77 +419,79 @@ class ProcessCollection:
""" """
if total_ports is None: if total_ports is None:
if read_ports is None or write_ports is None: if read_ports is None or write_ports is None:
raise ValueError("inteligent quote") raise ValueError(
"If total_ports is unset, both read_ports and write_ports"
" must be provided."
)
else: else:
total_ports = read_ports + write_ports total_ports = read_ports + write_ports
else: else:
read_ports = total_ports if read_ports is None else read_ports read_ports = total_ports if read_ports is None else read_ports
write_ports = total_ports if write_ports is None else write_ports write_ports = total_ports if write_ports is None else write_ports
if heuristic == "graph_color": if heuristic == "graph_color":
return self._split_graph_color( return self._split_ports_graph_color(
read_ports, write_ports, total_ports read_ports, write_ports, total_ports
) )
else: else:
raise ValueError("Invalid heuristic provided") raise ValueError("Invalid heuristic provided.")
def _split_graph_color( def _split_ports_graph_color(
self, read_ports: int, write_ports: int, total_ports: int self,
read_ports: int,
write_ports: int,
total_ports: int,
coloring_strategy: str = "DSATUR",
) -> Set["ProcessCollection"]: ) -> Set["ProcessCollection"]:
""" """
Parameters Parameters
---------- ----------
read_ports : int, optional read_ports : int
The number of read ports used when splitting process collection based on memory variable access. The number of read ports used when splitting process collection based on memory variable access.
write_ports : int, optional write_ports : int
The number of write ports used when splitting process collection based on memory variable access. The number of write ports used when splitting process collection based on memory variable access.
total_ports : int, optional total_ports : int
The total number of ports used when splitting process collection based on memory variable access. The total number of ports used when splitting process collection based on memory variable access.
coloring_strategy: str, default: 'DSATUR'
Node ordering strategy passed to nx.coloring.greedy_color()
One of
* `'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 saturation_largest_first strategy)
""" """
if read_ports != 1 or write_ports != 1:
raise ValueError(
"Splitting with read and write ports not equal to one with the"
" graph coloring heuristic does not make sense."
)
if total_ports not in (1, 2):
raise ValueError(
"Total ports should be either 1 (non-concurrent reads/writes)"
" or 2 (concurrent read/writes) for graph coloring heuristic."
)
# Create new exclusion graph. Nodes are Processes # Create new exclusion graph. Nodes are Processes
exclusion_graph = nx.Graph() exclusion_graph = self.create_exclusion_graph_from_ports(
exclusion_graph.add_nodes_from(self._collection) read_ports, write_ports, total_ports
)
# Add exclusions (arcs) between processes in the exclusion graph # Perform assignment from coloring and return result
for node1 in exclusion_graph: coloring = nx.coloring.greedy_color(
for node2 in exclusion_graph: exclusion_graph, strategy=coloring_strategy
if node1 == node2: )
continue return self._split_from_graph_coloring(coloring)
else:
node1_stop_time = node1.start_time + node1.execution_time def _split_from_graph_coloring(
node2_stop_time = node2.start_time + node2.execution_time self,
if total_ports == 1: coloring: Dict[Process, int],
# Single-port assignment ) -> Set["ProcessCollection"]:
if node1.start_time == node2.start_time: """
exclusion_graph.add_edge(node1, node2) Split :class:`Process` objects into a set of :class:`ProcessesCollection` objects based on a provided graph coloring.
elif node1_stop_time == node2_stop_time: Resulting :class:`ProcessCollection` will have the same schedule time and cyclic propoery as self.
exclusion_graph.add_edge(node1, node2)
elif node1.start_time == node2_stop_time: Parameters
exclusion_graph.add_edge(node1, node2) ----------
elif node1_stop_time == node2.start_time: coloring : Dict[Process, int]
exclusion_graph.add_edge(node1, node2) Process->int (color) mappings
else:
# Dual-port assignment
if node1.start_time == node2.start_time:
exclusion_graph.add_edge(node1, node2)
elif node1_stop_time == node2_stop_time:
exclusion_graph.add_edge(node1, node2)
# Perform assignment Returns
coloring = nx.coloring.greedy_color(exclusion_graph) -------
draw_exclusion_graph_coloring(exclusion_graph, coloring) A set of new ProcessCollections.
# process_collection_list = [ProcessCollection()]*(max(coloring.values()) + 1) """
process_collection_set_list = [ process_collection_set_list = [
set() for _ in range(max(coloring.values()) + 1) set() for _ in range(max(coloring.values()) + 1)
] ]
......
File added
import pickle
import matplotlib.pyplot as plt import matplotlib.pyplot as plt
import networkx as nx import networkx as nx
import pytest import pytest
...@@ -6,7 +8,7 @@ from b_asic.research.interleaver import ( ...@@ -6,7 +8,7 @@ from b_asic.research.interleaver import (
generate_matrix_transposer, generate_matrix_transposer,
generate_random_interleaver, generate_random_interleaver,
) )
from b_asic.resources import draw_exclusion_graph_coloring from b_asic.resources import ProcessCollection, draw_exclusion_graph_coloring
class TestProcessCollectionPlainMemoryVariable: class TestProcessCollectionPlainMemoryVariable:
...@@ -16,40 +18,40 @@ class TestProcessCollectionPlainMemoryVariable: ...@@ -16,40 +18,40 @@ class TestProcessCollectionPlainMemoryVariable:
simple_collection.draw_lifetime_chart(ax=ax, show_markers=False) simple_collection.draw_lifetime_chart(ax=ax, show_markers=False)
return fig return fig
def test_draw_proces_collection(self, simple_collection):
_, ax = plt.subplots(1, 2)
simple_collection.draw_lifetime_chart(ax=ax[0])
exclusion_graph = (
simple_collection.create_exclusion_graph_from_overlap()
)
color_dict = nx.coloring.greedy_color(exclusion_graph)
draw_exclusion_graph_coloring(exclusion_graph, color_dict, ax=ax[1])
def test_split_memory_variable(self, simple_collection):
collection_split = simple_collection.split(
read_ports=1, write_ports=1, total_ports=2
)
assert len(collection_split) == 3
@pytest.mark.mpl_image_compare(style='mpl20') @pytest.mark.mpl_image_compare(style='mpl20')
def test_draw_matrix_transposer_4(self): def test_draw_matrix_transposer_4(self):
fig, ax = plt.subplots() fig, ax = plt.subplots()
generate_matrix_transposer(4).draw_lifetime_chart(ax=ax) generate_matrix_transposer(4).draw_lifetime_chart(ax=ax)
return fig return fig
def test_split_memory_variable(self, simple_collection: ProcessCollection):
collection_split = simple_collection.split_ports(
heuristic="graph_color", read_ports=1, write_ports=1, total_ports=2
)
assert len(collection_split) == 3
# Issue: #175
def test_interleaver_issue175(self):
with open('test/fixtures/interleaver-two-port-issue175.p', 'rb') as f:
interleaver_collection: ProcessCollection = pickle.load(f)
assert len(interleaver_collection.split_ports(total_ports=1)) == 2
def test_generate_random_interleaver(self): def test_generate_random_interleaver(self):
return
for _ in range(10): for _ in range(10):
for size in range(5, 20, 5): for size in range(5, 20, 5):
assert ( assert (
len( len(
generate_random_interleaver(size).split( generate_random_interleaver(size).split_ports(
read_ports=1, write_ports=1 read_ports=1, write_ports=1
) )
) )
== 1 == 1
) )
assert ( assert (
len(generate_random_interleaver(size).split(total_ports=1)) len(
generate_random_interleaver(size).split_ports(
total_ports=1
)
)
== 2 == 2
) )
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment