From 0cd0723a74b41e7537eed6deb1c1b0f643be7831 Mon Sep 17 00:00:00 2001 From: Mikael Henriksson <mikael.henriksson@liu.se> Date: Thu, 16 Feb 2023 08:44:56 +0000 Subject: [PATCH] Process --- .gitignore | 1 + README.md | 1 + b_asic/process.py | 16 +- b_asic/resources.py | 343 ++++++++++++++++++ docs_sphinx/api/index.rst | 1 + docs_sphinx/api/resources.rst | 7 + docs_sphinx/conf.py | 1 + pyproject.toml | 1 + requirements.txt | 1 + .../baseline/test_draw_process_collection.png | Bin 0 -> 10292 bytes test/conftest.py | 1 + test/fixtures/resources.py | 36 ++ test/test_resources.py | 29 ++ 13 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 b_asic/resources.py create mode 100644 docs_sphinx/api/resources.rst create mode 100644 test/baseline/test_draw_process_collection.png create mode 100644 test/fixtures/resources.py create mode 100644 test/test_resources.py diff --git a/.gitignore b/.gitignore index d251d2bf..d6034647 100644 --- a/.gitignore +++ b/.gitignore @@ -115,3 +115,4 @@ TODO.txt *.log b_asic/_version.py docs_sphinx/_build/ +docs_sphinx/examples diff --git a/README.md b/README.md index f6666690..f2df4a12 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ The following packages are required in order to build the library: - [NumPy](https://numpy.org/) - [QtPy](https://github.com/spyder-ide/qtpy) - [setuptools_scm](https://github.com/pypa/setuptools_scm/) + - [NetworkX](https://networkx.org/) - Qt 5 or 6, with Python bindings, one of: - pyside2 - pyqt5 diff --git a/b_asic/process.py b/b_asic/process.py index 131ad599..d48fa0ee 100644 --- a/b_asic/process.py +++ b/b_asic/process.py @@ -2,7 +2,7 @@ B-ASIC classes representing resource usage. """ -from typing import Dict, Tuple +from typing import Dict, Optional, Tuple from b_asic.operation import Operation from b_asic.port import InputPort, OutputPort @@ -130,10 +130,14 @@ class PlainMemoryVariable(Process): write_time: int, write_port: int, reads: Dict[int, int], + name: Optional[str] = None, ): self._read_ports = tuple(reads.keys()) self._life_times = tuple(reads.values()) self._write_port = write_port + if name is None: + self._name = str(PlainMemoryVariable._name_cnt) + PlainMemoryVariable._name_cnt += 1 super().__init__( start_time=write_time, execution_time=max(self._life_times) ) @@ -149,3 +153,13 @@ class PlainMemoryVariable(Process): @property def write_port(self) -> int: return self._write_port + + @property + def name(self) -> str: + return self._name + + def __str__(self) -> str: + return self._name + + # Static counter for default names + _name_cnt = 0 diff --git a/b_asic/resources.py b/b_asic/resources.py new file mode 100644 index 00000000..eaed928f --- /dev/null +++ b/b_asic/resources.py @@ -0,0 +1,343 @@ +from typing import Dict, List, Optional, Set, Tuple, Union + +import matplotlib.pyplot as plt +import networkx as nx +from matplotlib.axes import Axes +from matplotlib.ticker import MaxNLocator + +from b_asic.process import Process + + +def draw_exclusion_graph_coloring( + exclusion_graph: nx.Graph, + color_dict: Dict[Process, int], + ax: Optional[Axes] = None, + color_list: Optional[ + Union[List[str], List[Tuple[float, float, float]]] + ] = None, +): + """ + Use matplotlib.pyplot and networkx to draw a colored exclusion graph from the memory assigment + + .. code-block:: python + + _, ax = plt.subplots(1, 1) + collection = ProcessCollection(...) + exclusion_graph = collection.create_exclusion_graph_from_overlap() + color_dict = nx.greedy_color(exclusion_graph) + draw_exclusion_graph_coloring(exclusion_graph, color_dict, ax=ax[0]) + plt.show() + + Parameters + ---------- + exclusion_graph : nx.Graph + A nx.Graph exclusion graph object that is to be drawn. + + color_dict : dictionary + A color dictionary where keys are Process objects and where values are integers representing colors. These + dictionaries are automatically generated by :func:`networkx.algorithms.coloring.greedy_color`. + + ax : :class:`matplotlib.axes.Axes`, optional + A Matplotlib Axes object to draw the exclusion graph + + color_list : Optional[Union[List[str], List[Tuple[float,float,float]]]] + """ + COLOR_LIST = [ + '#aa0000', + '#00aa00', + '#0000ff', + '#ff00aa', + '#ffaa00', + '#00ffaa', + '#aaff00', + '#aa00ff', + '#00aaff', + '#ff0000', + '#00ff00', + '#0000aa', + '#aaaa00', + '#aa00aa', + '#00aaaa', + ] + node_color_dict = {} + if color_list is None: + node_color_dict = {k: COLOR_LIST[v] for k, v in color_dict.items()} + else: + node_color_dict = {k: color_list[v] for k, v in color_dict.items()} + node_color_list = [node_color_dict[node] for node in exclusion_graph] + nx.draw_networkx( + exclusion_graph, + node_color=node_color_list, + ax=ax, + pos=nx.spring_layout(exclusion_graph, seed=1), + ) + + +class ProcessCollection: + """ + Collection of one or more processes + + Parameters + ---------- + collection : set of :class:`~b_asic.process.Process` objects, optional + """ + + def __init__(self, collection: Optional[Set[Process]] = None): + if collection is None: + self._collection: Set[Process] = set() + else: + self._collection = collection + + def add_process(self, process: Process): + """ + Add a new process to this process collection. + + Parameters + ---------- + process : Process + The process object to be added to the collection + """ + self._collection.add(process) + + def draw_lifetime_chart( + self, + schedule_time: int = 0, + ax: Optional[Axes] = None, + show_name: bool = True, + ): + """ + Use matplotlib.pyplot to generate a process variable lifetime chart from this process collection. + + 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. + show_name : bool, default: True + Show name of all processes in the lifetime chart. + + Returns + ------- + ax: Associated Matplotlib Axes (or array of Axes) object + """ + + # Setup the Axes object + if ax is None: + _, _ax = plt.subplots() + else: + _ax = ax + + # Draw the lifetime chart + PAD_L, PAD_R = 0.05, 0.05 + max_execution_time = max( + [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: + # 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}' + ) + for i, process in enumerate( + sorted(self._collection, key=lambda p: str(p)) + ): + bar_start = process.start_time % schedule_time + bar_end = ( + process.start_time + process.execution_time + ) % schedule_time + if bar_end > bar_start: + _ax.broken_barh( + [(PAD_L + bar_start, bar_end - bar_start - PAD_L - PAD_R)], + (i + 0.55, 0.9), + ) + else: # bar_end < bar_start + if bar_end != 0: + _ax.broken_barh( + [ + ( + PAD_L + bar_start, + schedule_time - bar_start - PAD_L, + ) + ], + (i + 0.55, 0.9), + ) + _ax.broken_barh([(0, bar_end - PAD_R)], (i + 0.55, 0.9)) + else: + _ax.broken_barh( + [ + ( + PAD_L + bar_start, + schedule_time - bar_start - PAD_L - PAD_R, + ) + ], + (i + 0.55, 0.9), + ) + if show_name: + _ax.annotate( + str(process), + (bar_start + PAD_L + 0.025, i + 1.00), + va="center", + ) + _ax.grid(True) + + _ax.xaxis.set_major_locator(MaxNLocator(integer=True)) + _ax.yaxis.set_major_locator(MaxNLocator(integer=True)) + return _ax + + def create_exclusion_graph_from_overlap( + self, add_name: bool = True + ) -> nx.Graph: + """ + Generate exclusion graph based on processes overlaping in time + + Parameters + ---------- + add_name : bool, default: True + Add name of all processes as a node attribute in the exclusion graph. + + Returns + ------- + An nx.Graph exclusion graph where nodes are processes and arcs + between two processes indicated overlap in time + """ + exclusion_graph = nx.Graph() + exclusion_graph.add_nodes_from(self._collection) + for process1 in self._collection: + for process2 in self._collection: + if process1 == process2: + continue + else: + t1 = set( + range( + process1.start_time, + process1.start_time + process1.execution_time, + ) + ) + t2 = set( + range( + process2.start_time, + process2.start_time + process2.execution_time, + ) + ) + if t1.intersection(t2): + exclusion_graph.add_edge(process1, process2) + return exclusion_graph + + def split( + self, + heuristic: str = "graph_color", + read_ports: Optional[int] = None, + write_ports: Optional[int] = None, + total_ports: Optional[int] = None, + ) -> Set["ProcessCollection"]: + """ + Split this process storage based on some heuristic. + + Parameters + ---------- + heuristic : str, default: "graph_color" + The heuristic used when spliting this ProcessCollection. + Valid options are: + * "graph_color" + * "..." + read_ports : int, optional + The number of read ports used when spliting process collection based on memory variable access. + write_ports : int, optional + The number of write ports used when spliting process collection based on memory variable access. + total_ports : int, optional + The total number of ports used when spliting process collection based on memory variable access. + + Returns + ------- + A set of new ProcessColleciton objects with the process spliting. + """ + if total_ports is None: + if read_ports is None or write_ports is None: + raise ValueError("inteligent quote") + 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( + read_ports, write_ports, total_ports + ) + else: + raise ValueError("Invalid heuristic provided") + + def _split_graph_color( + self, read_ports: int, write_ports: int, total_ports: int + ) -> Set["ProcessCollection"]: + """ + Parameters + ---------- + read_ports : int, optional + The number of read ports used when spliting process collection based on memory variable access. + write_ports : int, optional + The number of write ports used when spliting process collection based on memory variable access. + total_ports : int, optional + The total number of ports used when spliting process collection based on memory variable access. + """ + if read_ports != 1 or write_ports != 1: + raise ValueError( + "Spliting 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-concurent 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) + + # 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 + 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) + ] + for process, color in coloring.items(): + process_collection_list[color].add_process(process) + return { + process_collection + for process_collection in process_collection_list + } diff --git a/docs_sphinx/api/index.rst b/docs_sphinx/api/index.rst index 8ad0965a..c3480c10 100644 --- a/docs_sphinx/api/index.rst +++ b/docs_sphinx/api/index.rst @@ -10,6 +10,7 @@ API operation.rst port.rst process.rst + resources.rst schedule.rst sfg_generators.rst signal.rst diff --git a/docs_sphinx/api/resources.rst b/docs_sphinx/api/resources.rst new file mode 100644 index 00000000..d87fa73b --- /dev/null +++ b/docs_sphinx/api/resources.rst @@ -0,0 +1,7 @@ +******************** +``b_asic.resources`` +******************** + +.. automodule:: b_asic.resources + :members: + :undoc-members: diff --git a/docs_sphinx/conf.py b/docs_sphinx/conf.py index a8ac592d..90b049c7 100644 --- a/docs_sphinx/conf.py +++ b/docs_sphinx/conf.py @@ -39,6 +39,7 @@ intersphinx_mapping = { 'matplotlib': ('https://matplotlib.org/stable/', None), 'numpy': ('https://numpy.org/doc/stable/', None), 'PyQt5': ("https://www.riverbankcomputing.com/static/Docs/PyQt5", None), + 'networkx': ('https://networkx.org/documentation/stable', None), } numpydoc_show_class_members = False diff --git a/pyproject.toml b/pyproject.toml index fef65a9d..be37e866 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ "graphviz>=0.19", "matplotlib", "setuptools_scm[toml]>=6.2", + "networkx", ] classifiers = [ "Intended Audience :: Education", diff --git a/requirements.txt b/requirements.txt index 34397383..1a591a91 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,3 +3,4 @@ qtpy graphviz>=0.19 matplotlib setuptools_scm[toml]>=6.2 +networkx diff --git a/test/baseline/test_draw_process_collection.png b/test/baseline/test_draw_process_collection.png new file mode 100644 index 0000000000000000000000000000000000000000..0ab1996784015b6d0bc3e5bc68e0f3ae21c64d94 GIT binary patch literal 10292 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 literal 0 HcmV?d00001 diff --git a/test/conftest.py b/test/conftest.py index 179a82c2..138fefe0 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -2,6 +2,7 @@ import os from distutils import dir_util from test.fixtures.operation_tree import * from test.fixtures.port import * +from test.fixtures.resources import * from test.fixtures.schedule import * from test.fixtures.signal import signal, signals from test.fixtures.signal_flow_graph import * diff --git a/test/fixtures/resources.py b/test/fixtures/resources.py new file mode 100644 index 00000000..6317e4cb --- /dev/null +++ b/test/fixtures/resources.py @@ -0,0 +1,36 @@ +import pytest + +from b_asic.process import PlainMemoryVariable +from b_asic.resources import ProcessCollection + + +@pytest.fixture() +def simple_collection(): + NO_PORT = 0 + return ProcessCollection( + { + PlainMemoryVariable(4, NO_PORT, {NO_PORT: 2}), + PlainMemoryVariable(2, NO_PORT, {NO_PORT: 6}), + PlainMemoryVariable(3, NO_PORT, {NO_PORT: 5}), + PlainMemoryVariable(6, NO_PORT, {NO_PORT: 2}), + PlainMemoryVariable(0, NO_PORT, {NO_PORT: 3}), + PlainMemoryVariable(0, NO_PORT, {NO_PORT: 2}), + PlainMemoryVariable(0, NO_PORT, {NO_PORT: 6}), + } + ) + + +@pytest.fixture() +def collection(): + NO_PORT = 0 + return ProcessCollection( + { + PlainMemoryVariable(4, NO_PORT, {NO_PORT: 2}), + PlainMemoryVariable(2, NO_PORT, {NO_PORT: 6}), + PlainMemoryVariable(3, NO_PORT, {NO_PORT: 5}), + PlainMemoryVariable(6, NO_PORT, {NO_PORT: 2}), + PlainMemoryVariable(0, NO_PORT, {NO_PORT: 3}), + PlainMemoryVariable(0, NO_PORT, {NO_PORT: 2}), + PlainMemoryVariable(0, NO_PORT, {NO_PORT: 6}), + } + ) diff --git a/test/test_resources.py b/test/test_resources.py new file mode 100644 index 00000000..67f20cc9 --- /dev/null +++ b/test/test_resources.py @@ -0,0 +1,29 @@ +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 + + +class TestProcessCollectionPlainMemoryVariable: + @pytest.mark.mpl_image_compare(style='mpl20') + def test_draw_process_collection(self, simple_collection): + fig, ax = plt.subplots() + simple_collection.draw_lifetime_chart(ax=ax) + return fig + + def test_draw_proces_collection(self, simple_collection): + _, ax = plt.subplots(1, 2) + simple_collection.draw_lifetime_chart(schedule_time=8, 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 -- GitLab