From dca62534671f75896df84a78d3eeeddded5f82aa Mon Sep 17 00:00:00 2001 From: Mikael Henriksson <mike.zx@hotmail.com> Date: Fri, 17 Feb 2023 17:29:06 +0100 Subject: [PATCH] Add support for node selecting strategy in greedy_color of ProcessCollection spliting methods (closes #175) --- b_asic/resources.py | 230 +++++++++++++----- test/fixtures/interleaver-two-port-issue175.p | Bin 0 -> 2618 bytes test/test_resources.py | 40 +-- 3 files changed, 192 insertions(+), 78 deletions(-) create mode 100644 test/fixtures/interleaver-two-port-issue175.p diff --git a/b_asic/resources.py b/b_asic/resources.py index c50007a7..f6a0a1f0 100644 --- a/b_asic/resources.py +++ b/b_asic/resources.py @@ -187,10 +187,12 @@ class ProcessCollection: # Generate the life-time chart for i, process in enumerate(_sorted_nicely(self._collection)): bar_start = process.start_time % self._schedule_time + bar_end = process.start_time + process.execution_time bar_end = ( - process.start_time + process.execution_time - ) % self._schedule_time - bar_end = self._schedule_time if bar_end == 0 else bar_end + bar_end + if bar_end == self._schedule_time + else bar_end % self._schedule_time + ) if show_markers: _ax.scatter( x=bar_start, @@ -240,16 +242,84 @@ class ProcessCollection: _ax.set_ylim(0.25, len(self._collection) + 0.75) return _ax - def create_exclusion_graph_from_overlap( - self, add_name: bool = True + def create_exclusion_graph_from_ports( + self, + read_ports: Optional[int] = None, + write_ports: Optional[int] = None, + total_ports: Optional[int] = None, ) -> 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 ---------- - add_name : bool, default: True - Add name of all processes as a node attribute in the exclusion graph. + read_ports : int + 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 ------- @@ -279,7 +349,47 @@ class ProcessCollection: exclusion_graph.add_edge(process1, process2) 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, heuristic: str = "graph_color", read_ports: Optional[int] = None, @@ -309,77 +419,79 @@ class ProcessCollection: """ if total_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: 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 - if heuristic == "graph_color": - return self._split_graph_color( + return self._split_ports_graph_color( read_ports, write_ports, total_ports ) else: - raise ValueError("Invalid heuristic provided") + raise ValueError("Invalid heuristic provided.") - def _split_graph_color( - self, read_ports: int, write_ports: int, total_ports: int + def _split_ports_graph_color( + self, + read_ports: int, + write_ports: int, + total_ports: int, + coloring_strategy: str = "DSATUR", ) -> Set["ProcessCollection"]: """ Parameters ---------- - read_ports : int, optional + read_ports : int 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. - total_ports : int, optional + total_ports : int 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 - exclusion_graph = nx.Graph() - exclusion_graph.add_nodes_from(self._collection) + exclusion_graph = self.create_exclusion_graph_from_ports( + read_ports, write_ports, total_ports + ) - # Add exclusions (arcs) between processes in the exclusion graph - 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) + # Perform assignment from coloring and return result + coloring = nx.coloring.greedy_color( + exclusion_graph, strategy=coloring_strategy + ) + return self._split_from_graph_coloring(coloring) + + def _split_from_graph_coloring( + self, + coloring: Dict[Process, int], + ) -> Set["ProcessCollection"]: + """ + Split :class:`Process` objects into a set of :class:`ProcessesCollection` objects based on a provided graph coloring. + Resulting :class:`ProcessCollection` will have the same schedule time and cyclic propoery as self. + + Parameters + ---------- + coloring : Dict[Process, int] + Process->int (color) mappings - # Perform assignment - coloring = nx.coloring.greedy_color(exclusion_graph) - draw_exclusion_graph_coloring(exclusion_graph, coloring) - # process_collection_list = [ProcessCollection()]*(max(coloring.values()) + 1) + Returns + ------- + A set of new ProcessCollections. + """ process_collection_set_list = [ set() for _ in range(max(coloring.values()) + 1) ] diff --git a/test/fixtures/interleaver-two-port-issue175.p b/test/fixtures/interleaver-two-port-issue175.p new file mode 100644 index 0000000000000000000000000000000000000000..f62ee38cbca0f1ec5ff14ddc073b58976cb69ad1 GIT binary patch literal 2618 zcmai$OK;jx5Jn*-1wtSYpr!9XUr8Ut4!1U5cWG71s$0uAu58KR$Tn)TsMIbhbz!NM zdi#GnJalKUMk))0tmE-_XU<rBtNcAbD6Kzvd*--l=#3LUjhBh%r!wzcCNca--^Wqp zds!GS<R5wVU4E6PdBgGW>Myv^+TMS?xku*R%g7BEAN+Zo+<bJC(49p-y+85YE9W{+ zvQ*lo9}+4?;V0k8!Z|$K9<Hx^PQuJzL%;x$W^R(L5otflj~xGt?=4rsw%3OFzO!)W zzRc^Z?BkP(CFF7z)HkVv!_||5mfeBxwp|K3_QT-V?gU-rCmcUgd<pmw;f;43-&g#7 z;QNHPbdJY74~ls%I3DYl3-t|y<1zoeLVio*_`1qJ0KQK0Pc4q$Q~X`v_b5N*c&vY? zSl{G$?9VOW>AVcKenaKy1K*%|*#49i-vho(d3L^4#dm?PQvN@_9T)OXS>G^Er<jNJ z0k&o9a|QSz$z!ndT5Z9`|2FU?!cSR0ab7y`#0R0X=S3fmfZy?f^=+W?>;pd_c}$ka z`r2-Np|kzO`i~3sEq1<5m8S)KljdRjiM};~r@oo2pP1)KF%Rn}_WvCCo&K{vV4m@A z9-*`I#XN_FJSMx3*#8FbbiO8=AANocJlQXyv-8FMdILP!FQKvh$M?f);K}<zXl(y6 z&#OWnjXf{!%Ng)wUxdb<7yEMxJn4_nSl=qjw*%lS)HjxIDZUPTi}LI~qR%gZCq4^} z<$J1r4fr0d&+Zrc_5yg~ztC77eR~c(*>|C_K4U+h0l(8v*5{haUj@EK^RxBQpFQA- zKNExX0q6A;crq`cu|DIxM!?f~S**|4pWDEb{!C2vyg08R@N`}l>p%AA1bEsXYjxh= ddCN(?z`t5Xn?GdIzR0VNcjHB&Cx0$y<9~OV7z6+S literal 0 HcmV?d00001 diff --git a/test/test_resources.py b/test/test_resources.py index 38dfc245..581474af 100644 --- a/test/test_resources.py +++ b/test/test_resources.py @@ -1,3 +1,5 @@ +import pickle + import matplotlib.pyplot as plt import networkx as nx import pytest @@ -6,7 +8,7 @@ from b_asic.research.interleaver import ( generate_matrix_transposer, generate_random_interleaver, ) -from b_asic.resources import draw_exclusion_graph_coloring +from b_asic.resources import ProcessCollection, draw_exclusion_graph_coloring class TestProcessCollectionPlainMemoryVariable: @@ -16,40 +18,40 @@ class TestProcessCollectionPlainMemoryVariable: simple_collection.draw_lifetime_chart(ax=ax, show_markers=False) 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') def test_draw_matrix_transposer_4(self): fig, ax = plt.subplots() generate_matrix_transposer(4).draw_lifetime_chart(ax=ax) 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): - return for _ in range(10): for size in range(5, 20, 5): assert ( len( - generate_random_interleaver(size).split( + generate_random_interleaver(size).split_ports( read_ports=1, write_ports=1 ) ) == 1 ) assert ( - len(generate_random_interleaver(size).split(total_ports=1)) + len( + generate_random_interleaver(size).split_ports( + total_ports=1 + ) + ) == 2 ) -- GitLab