From 7a940f423cfef38ea957b76a1bf69fc6f66341d1 Mon Sep 17 00:00:00 2001 From: Oscar Gustafsson <oscar.gustafsson@gmail.com> Date: Thu, 16 Feb 2023 10:15:45 +0100 Subject: [PATCH] Fix resource-related issues --- b_asic/process.py | 2 + b_asic/research/interleaver.py | 44 ++++++---- b_asic/resources.py | 77 +++++++++++------- .../test_draw_matrix_transposer_4.png | Bin 0 -> 21765 bytes test/fixtures/resources.py | 9 +- test/test_resources.py | 32 +++++++- 6 files changed, 110 insertions(+), 54 deletions(-) create mode 100644 test/baseline/test_draw_matrix_transposer_4.png diff --git a/b_asic/process.py b/b_asic/process.py index d48fa0ee..f25de759 100644 --- a/b_asic/process.py +++ b/b_asic/process.py @@ -138,6 +138,8 @@ class PlainMemoryVariable(Process): if name is None: self._name = str(PlainMemoryVariable._name_cnt) PlainMemoryVariable._name_cnt += 1 + else: + self._name = name super().__init__( start_time=write_time, execution_time=max(self._life_times) ) diff --git a/b_asic/research/interleaver.py b/b_asic/research/interleaver.py index 83eb4b3b..b64a79b9 100644 --- a/b_asic/research/interleaver.py +++ b/b_asic/research/interleaver.py @@ -6,6 +6,7 @@ import random from typing import Optional, Set from b_asic.process import PlainMemoryVariable +from b_asic.resources import ProcessCollection def _insert_delays(inputorder, outputorder, min_lifetime, cyclic): @@ -14,9 +15,7 @@ def _insert_delays(inputorder, outputorder, min_lifetime, cyclic): outputorder = [o - maxdiff + min_lifetime for o in outputorder] maxdelay = max(outputorder[i] - inputorder[i] for i in range(size)) if cyclic: - if maxdelay < size: - outputorder = [o % size for o in outputorder] - else: + if maxdelay >= size: inputorder = inputorder + [i + size for i in inputorder] outputorder = outputorder + [o + size for o in outputorder] return inputorder, outputorder @@ -24,7 +23,7 @@ def _insert_delays(inputorder, outputorder, min_lifetime, cyclic): def generate_random_interleaver( size: int, min_lifetime: int = 0, cyclic: bool = True -) -> Set[PlainMemoryVariable]: +) -> ProcessCollection: """ Generate a ProcessCollection with memory variable corresponding to a random interleaver with length *size*. @@ -48,15 +47,19 @@ def generate_random_interleaver( inputorder = list(range(size)) outputorder = inputorder[:] random.shuffle(outputorder) - print(inputorder, outputorder) inputorder, outputorder = _insert_delays( inputorder, outputorder, min_lifetime, cyclic ) - print(inputorder, outputorder) - return { - PlainMemoryVariable(inputorder[i], 0, {0: outputorder[i]}) - for i in range(size) - } + return ProcessCollection( + { + PlainMemoryVariable( + inputorder[i], 0, {0: outputorder[i] - inputorder[i]} + ) + for i in range(len(inputorder)) + }, + len(inputorder), + cyclic, + ) def generate_matrix_transposer( @@ -64,7 +67,7 @@ def generate_matrix_transposer( width: Optional[int] = None, min_lifetime: int = 0, cyclic: bool = True, -) -> Set[PlainMemoryVariable]: +) -> ProcessCollection: r""" Generate a ProcessCollection with memory variable corresponding to transposing a matrix of size *height* :math:`\times` *width*. If *width* is not provided, a @@ -101,12 +104,19 @@ def generate_matrix_transposer( for col in range(height): outputorder.append(col * width + row) - print(inputorder, outputorder) inputorder, outputorder = _insert_delays( inputorder, outputorder, min_lifetime, cyclic ) - print(inputorder, outputorder) - return { - PlainMemoryVariable(inputorder[i], 0, {0: outputorder[i]}) - for i in range(width * height) - } + return ProcessCollection( + { + PlainMemoryVariable( + inputorder[i], + 0, + {0: outputorder[i] - inputorder[i]}, + name=f"{inputorder[i]}", + ) + for i in range(len(inputorder)) + }, + len(inputorder), + cyclic, + ) diff --git a/b_asic/resources.py b/b_asic/resources.py index eaed928f..121b892d 100644 --- a/b_asic/resources.py +++ b/b_asic/resources.py @@ -1,3 +1,4 @@ +import re from typing import Dict, List, Optional, Set, Tuple, Union import matplotlib.pyplot as plt @@ -8,6 +9,16 @@ from matplotlib.ticker import MaxNLocator from b_asic.process import Process +# From https://stackoverflow.com/questions/2669059/how-to-sort-alpha-numeric-set-in-python +def _sorted_nicely(to_be_sorted): + """Sort the given iterable in the way that humans expect.""" + convert = lambda text: int(text) if text.isdigit() else text + alphanum_key = lambda key: [ + convert(c) for c in re.split('([0-9]+)', str(key)) + ] + return sorted(to_be_sorted, key=alphanum_key) + + def draw_exclusion_graph_coloring( exclusion_graph: nx.Graph, color_dict: Dict[Process, int], @@ -79,14 +90,23 @@ class ProcessCollection: Parameters ---------- - collection : set of :class:`~b_asic.process.Process` objects, optional + collection : set of :class:`~b_asic.process.Process` objects + The Process objects forming this ProcessCollection. + schedule_time : int, default: 0 + Length of the time-axis in the generated graph. + cyclic : bool, default: False + If the processes operates cyclically, i.e., if time 0 == time *schedule_time*. """ - def __init__(self, collection: Optional[Set[Process]] = None): - if collection is None: - self._collection: Set[Process] = set() - else: - self._collection = collection + def __init__( + self, + collection: Set[Process], + schedule_time: int, + cyclic: bool = False, + ): + self._collection = collection + self._schedule_time = schedule_time + self._cyclic = cyclic def add_process(self, process: Process): """ @@ -101,7 +121,6 @@ class ProcessCollection: def draw_lifetime_chart( self, - schedule_time: int = 0, ax: Optional[Axes] = None, show_name: bool = True, ): @@ -110,9 +129,6 @@ class ProcessCollection: Parameters ---------- - schedule_time : int, default: 0 - Length of the time-axis in the generated graph. The time axis will span [0, schedule_time-1]. - If set to zero (which is the default), the ... ax : :class:`matplotlib.axes.Axes`, optional Matplotlib Axes object to draw this lifetime chart onto. If not provided (i.e., set to None), this will return a new axes object on return. @@ -133,26 +149,19 @@ class ProcessCollection: # Draw the lifetime chart PAD_L, PAD_R = 0.05, 0.05 max_execution_time = max( - [process.execution_time for process in self._collection] + process.execution_time for process in self._collection ) - schedule_time = ( - schedule_time - if schedule_time - else max(p.start_time + p.execution_time for p in self._collection) - ) - if max_execution_time > schedule_time: + if max_execution_time > self._schedule_time: # Schedule time needs to be greater than or equal to the maximum process life time raise KeyError( - f'Error: Schedule time: {schedule_time} < Max execution time:' - f' {max_execution_time}' + f'Error: Schedule time: {self._schedule_time} < Max execution' + f' time: {max_execution_time}' ) - for i, process in enumerate( - sorted(self._collection, key=lambda p: str(p)) - ): - bar_start = process.start_time % schedule_time + 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 - ) % schedule_time + ) % self._schedule_time if bar_end > bar_start: _ax.broken_barh( [(PAD_L + bar_start, bar_end - bar_start - PAD_L - PAD_R)], @@ -164,7 +173,7 @@ class ProcessCollection: [ ( PAD_L + bar_start, - schedule_time - bar_start - PAD_L, + self._schedule_time - bar_start - PAD_L, ) ], (i + 0.55, 0.9), @@ -175,7 +184,10 @@ class ProcessCollection: [ ( PAD_L + bar_start, - schedule_time - bar_start - PAD_L - PAD_R, + self._schedule_time + - bar_start + - PAD_L + - PAD_R, ) ], (i + 0.55, 0.9), @@ -190,6 +202,7 @@ class ProcessCollection: _ax.xaxis.set_major_locator(MaxNLocator(integer=True)) _ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + _ax.set_xlim(0, self._schedule_time) return _ax def create_exclusion_graph_from_overlap( @@ -332,12 +345,14 @@ class ProcessCollection: coloring = nx.coloring.greedy_color(exclusion_graph) draw_exclusion_graph_coloring(exclusion_graph, coloring) # process_collection_list = [ProcessCollection()]*(max(coloring.values()) + 1) - process_collection_list = [ - ProcessCollection() for _ in range(max(coloring.values()) + 1) + process_collection_set_list = [ + set() for _ in range(max(coloring.values()) + 1) ] for process, color in coloring.items(): - process_collection_list[color].add_process(process) + process_collection_set_list[color].add(process) return { - process_collection - for process_collection in process_collection_list + ProcessCollection( + process_collection_set, self._schedule_time, self._cyclic + ) + for process_collection_set in process_collection_set_list } diff --git a/test/baseline/test_draw_matrix_transposer_4.png b/test/baseline/test_draw_matrix_transposer_4.png new file mode 100644 index 0000000000000000000000000000000000000000..3e7546972ed9c12dd8441ff39d432604c5a5c4db GIT binary patch literal 21765 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 literal 0 HcmV?d00001 diff --git a/test/fixtures/resources.py b/test/fixtures/resources.py index 6317e4cb..61c8db25 100644 --- a/test/fixtures/resources.py +++ b/test/fixtures/resources.py @@ -16,12 +16,13 @@ def simple_collection(): PlainMemoryVariable(0, NO_PORT, {NO_PORT: 3}), PlainMemoryVariable(0, NO_PORT, {NO_PORT: 2}), PlainMemoryVariable(0, NO_PORT, {NO_PORT: 6}), - } + }, + 8, ) @pytest.fixture() -def collection(): +def cyclic_simple_collection(): NO_PORT = 0 return ProcessCollection( { @@ -32,5 +33,7 @@ def collection(): PlainMemoryVariable(0, NO_PORT, {NO_PORT: 3}), PlainMemoryVariable(0, NO_PORT, {NO_PORT: 2}), PlainMemoryVariable(0, NO_PORT, {NO_PORT: 6}), - } + }, + 6, + True, ) diff --git a/test/test_resources.py b/test/test_resources.py index 67f20cc9..10e401ad 100644 --- a/test/test_resources.py +++ b/test/test_resources.py @@ -2,8 +2,11 @@ import matplotlib.pyplot as plt import networkx as nx import pytest -from b_asic.process import PlainMemoryVariable -from b_asic.resources import ProcessCollection, draw_exclusion_graph_coloring +from b_asic.research.interleaver import ( + generate_matrix_transposer, + generate_random_interleaver, +) +from b_asic.resources import draw_exclusion_graph_coloring class TestProcessCollectionPlainMemoryVariable: @@ -15,7 +18,7 @@ class TestProcessCollectionPlainMemoryVariable: def test_draw_proces_collection(self, simple_collection): _, ax = plt.subplots(1, 2) - simple_collection.draw_lifetime_chart(schedule_time=8, ax=ax[0]) + simple_collection.draw_lifetime_chart(ax=ax[0]) exclusion_graph = ( simple_collection.create_exclusion_graph_from_overlap() ) @@ -27,3 +30,26 @@ class TestProcessCollectionPlainMemoryVariable: 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_generate_random_interleaver(self): + return + for _ in range(10): + for size in range(5, 20, 5): + assert ( + len( + generate_random_interleaver(size).split( + read_ports=1, write_ports=1 + ) + ) + == 1 + ) + assert ( + len(generate_random_interleaver(size).split(total_ports=1)) + == 2 + ) -- GitLab