From 22a8c0d0aff162c6445307303a4397a94210fbd9 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Fri, 9 Sep 2022 22:32:37 +0200
Subject: [PATCH] Fix process and run black for formatting

---
 b_asic/core_operations.py                     |   3 +-
 b_asic/graph_component.py                     |   3 +-
 b_asic/operation.py                           |  20 ++-
 b_asic/port.py                                |   3 +-
 b_asic/process.py                             | 100 +++++++++--
 b_asic/save_load_structure.py                 |   3 +-
 b_asic/schedule.py                            |   3 +-
 b_asic/scheduler_gui/__init__.py              |   2 +-
 b_asic/scheduler_gui/_version.py              |   4 +-
 b_asic/scheduler_gui/compile.py               | 167 ++++++++++--------
 b_asic/scheduler_gui/graphics_axes_item.py    |   1 -
 .../scheduler_gui/graphics_component_item.py  |   6 +-
 b_asic/scheduler_gui/graphics_graph_event.py  |   6 +-
 b_asic/scheduler_gui/graphics_graph_item.py   |   6 +-
 b_asic/scheduler_gui/main_window.py           |   4 +-
 b_asic/scheduler_gui/ui_main_window.py        | 107 ++++++++---
 b_asic/signal.py                              |   3 +-
 b_asic/signal_flow_graph.py                   |  28 +--
 b_asic/simulation.py                          |  18 +-
 docs_sphinx/api/process.rst                   |  12 ++
 pyproject.toml                                |   5 +
 test/test_process.py                          |  11 ++
 22 files changed, 366 insertions(+), 149 deletions(-)
 create mode 100644 docs_sphinx/api/process.rst
 create mode 100644 test/test_process.py

diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py
index be389229..4b79eeed 100644
--- a/b_asic/core_operations.py
+++ b/b_asic/core_operations.py
@@ -426,7 +426,8 @@ class ConstantMultiplication(AbstractOperation):
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
-        """Construct a ConstantMultiplication operation with the given value."""
+        """Construct a ConstantMultiplication operation with the given value.
+        """
         super().__init__(
             input_count=1,
             output_count=1,
diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py
index ac622220..624a42f3 100644
--- a/b_asic/graph_component.py
+++ b/b_asic/graph_component.py
@@ -80,7 +80,8 @@ class GraphComponent(ABC):
 
     @abstractmethod
     def copy_component(self, *args, **kwargs) -> "GraphComponent":
-        """Get a new instance of this graph component type with the same name, id and parameters."""
+        """Get a new instance of this graph component type with the same name, id and parameters.
+        """
         raise NotImplementedError
 
     @property
diff --git a/b_asic/operation.py b/b_asic/operation.py
index 4b06c1b5..33c5b753 100644
--- a/b_asic/operation.py
+++ b/b_asic/operation.py
@@ -251,12 +251,14 @@ class Operation(GraphComponent, SignalSourceProvider):
 
     @abstractmethod
     def inputs_required_for_output(self, output_index: int) -> Iterable[int]:
-        """Get the input indices of all inputs in this operation whose values are required in order to evaluate the output at the given output index."""
+        """Get the input indices of all inputs in this operation whose values are required in order to evaluate the output at the given output index.
+        """
         raise NotImplementedError
 
     @abstractmethod
     def truncate_input(self, index: int, value: Number, bits: int) -> Number:
-        """Truncate the value to be used as input at the given index to a certain bit length."""
+        """Truncate the value to be used as input at the given index to a certain bit length.
+        """
         raise NotImplementedError
 
     @property
@@ -350,7 +352,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
         ] = None,
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
-        execution_time: Optional[int] = None
+        execution_time: Optional[int] = None,
     ):
         """Construct an operation with the given input/output count.
 
@@ -399,7 +401,8 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
     @abstractmethod
     def evaluate(self, *inputs) -> Any:  # pylint: disable=arguments-differ
-        """Evaluate the operation and generate a list of output values given a list of input values."""
+        """Evaluate the operation and generate a list of output values given a list of input values.
+        """
         raise NotImplementedError
 
     def __add__(self, src: Union[SignalSourceProvider, Number]) -> "Addition":
@@ -743,7 +746,8 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
     @property
     def preceding_operations(self) -> Iterable[Operation]:
-        """Returns an Iterable of all Operations that are connected to this Operations input ports."""
+        """Returns an Iterable of all Operations that are connected to this Operations input ports.
+        """
         return [
             signal.source.operation
             for signal in self.input_signals
@@ -752,7 +756,8 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
     @property
     def subsequent_operations(self) -> Iterable[Operation]:
-        """Returns an Iterable of all Operations that are connected to this Operations output ports."""
+        """Returns an Iterable of all Operations that are connected to this Operations output ports.
+        """
         return [
             signal.destination.operation
             for signal in self.output_signals
@@ -777,7 +782,8 @@ class AbstractOperation(Operation, AbstractGraphComponent):
         input_values: Sequence[Number],
         bits_override: Optional[int] = None,
     ) -> Sequence[Number]:
-        """Truncate the values to be used as inputs to the bit lengths specified by the respective signals connected to each input."""
+        """Truncate the values to be used as inputs to the bit lengths specified by the respective signals connected to each input.
+        """
         args = []
         for i, input_port in enumerate(self.inputs):
             value = input_values[i]
diff --git a/b_asic/port.py b/b_asic/port.py
index 780dea50..ab164d75 100644
--- a/b_asic/port.py
+++ b/b_asic/port.py
@@ -47,7 +47,8 @@ class Port(ABC):
     @latency_offset.setter
     @abstractmethod
     def latency_offset(self, latency_offset: int) -> None:
-        """Set the latency_offset of the port to the integer specified value."""
+        """Set the latency_offset of the port to the integer specified value.
+        """
         raise NotImplementedError
 
     @property
diff --git a/b_asic/process.py b/b_asic/process.py
index 2012a67e..53ccf882 100644
--- a/b_asic/process.py
+++ b/b_asic/process.py
@@ -1,42 +1,120 @@
-from typing import Dict, Int, List
+from typing import Dict, Tuple
 
 from b_asic.operation import AbstractOperation
 from b_asic.port import InputPort, OutputPort
 
 
 class Process:
-    def __init__(self, start_time : Int, execution_time : Int):
+    """
+    Object for use in resource allocation. Has a start time and an execution
+    time. Subclasses will in many cases contain additional information for
+    resource assignment.
+    """
+
+    def __init__(self, start_time: int, execution_time: int):
+        """
+        Parameters
+        ----------
+        start_time : int
+            Start time of process.
+        execution_time : int
+            Execution time (lifetime) of process.
+        """
         self._start_time = start_time
         self._execution_time = execution_time
 
     def __lt__(self, other):
         return self._start_time < other.start_time or (
-                    self._start_time == other.start_time and self.execution_time > _execution_time)
+            self._start_time == other.start_time
+            and self.execution_time > other.execution_time
+        )
 
     @property
-    def start_time(self) -> Int:
+    def start_time(self) -> int:
+        """Return the start time."""
         return self._start_time
 
     @property
-    def execution_time(self) -> Int:
+    def execution_time(self) -> int:
+        """Return the execution time."""
         return self._execution_time
 
 
 class OperatorProcess(Process):
-    def __init__(self, start_time : Int, operation : AbstractOperation):
+    """
+    Object that corresponds to usage of an operator.
+    """
+
+    def __init__(self, start_time: int, operation: AbstractOperation):
+        """
+
+        Returns
+        -------
+        object
+        """
         super().__init__(start_time, operation.execution_time)
         self._operation = operation
 
 
 class MemoryVariable(Process):
-    def __init__(self, write_time : Int, write_port : OutputPort, reads : Dict[InputPort, Int]):
-        self._read_ports, self._life_times = reads.values()
-        super().__init__(start_time=write_time, execution_time=max(self._life_times))
+    """
+    Object that corresponds to a memory variable.
+    """
+
+    def __init__(
+        self,
+        write_time: int,
+        write_port: OutputPort,
+        reads: Dict[InputPort, int],
+    ):
+        self._read_ports = tuple(reads.keys())
+        self._life_times = tuple(reads.values())
+        self._write_port = write_port
+        super().__init__(
+            start_time=write_time, execution_time=max(self._life_times)
+        )
 
     @property
-    def life_times(self) -> List[Int]:
+    def life_times(self) -> Tuple[int]:
         return self._life_times
 
     @property
-    def read_ports(self) -> List[InputPort]:
+    def read_ports(self) -> Tuple[InputPort]:
         return self._read_ports
+
+    @property
+    def write_port(self) -> OutputPort:
+        return self._write_port
+
+
+class PlainMemoryVariable(Process):
+    """
+    Object that corresponds to a memory variable which only use numbers for
+    ports. This can be useful when only a plain memory variable is wanted with
+    no connection to a schedule.
+    """
+
+    def __init__(
+        self,
+        write_time: int,
+        write_port: int,
+        reads: Dict[int, int],
+    ):
+        self._read_ports = tuple(reads.keys())
+        self._life_times = tuple(reads.values())
+        self._write_port = write_port
+        super().__init__(
+            start_time=write_time, execution_time=max(self._life_times)
+        )
+
+    @property
+    def life_times(self) -> Tuple[int]:
+        return self._life_times
+
+    @property
+    def read_ports(self) -> Tuple[int]:
+        return self._read_ports
+
+    @property
+    def write_port(self) -> int:
+        return self._write_port
diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py
index 3c8316c3..f85bcfb4 100644
--- a/b_asic/save_load_structure.py
+++ b/b_asic/save_load_structure.py
@@ -110,7 +110,8 @@ def sfg_to_python(sfg: SFG, counter: int = 0, suffix: str = None) -> str:
 
 
 def python_to_sfg(path: str) -> SFG:
-    """Given a serialized file try to deserialize it and load it to the library."""
+    """Given a serialized file try to deserialize it and load it to the library.
+    """
     with open(path) as f:
         code = compile(f.read(), path, "exec")
         exec(code, globals(), locals())
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 3fceb49b..1a01d745 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -64,7 +64,8 @@ class Schedule:
             self._schedule_time = schedule_time
 
     def start_time_of_operation(self, op_id: GraphID) -> int:
-        """Get the start time of the operation with the specified by the op_id."""
+        """Get the start time of the operation with the specified by the op_id.
+        """
         assert (
             op_id in self._start_times
         ), "No operation with the specified op_id in this schedule."
diff --git a/b_asic/scheduler_gui/__init__.py b/b_asic/scheduler_gui/__init__.py
index a7edc030..e2cb7dd6 100644
--- a/b_asic/scheduler_gui/__init__.py
+++ b/b_asic/scheduler_gui/__init__.py
@@ -3,7 +3,7 @@
 Graphical user interface for B-ASIC scheduler.
 """
 
-__author__ = 'Andreas Bolin'
+__author__ = "Andreas Bolin"
 # __all__ = ['main_window', 'graphics_graph', 'component_item', 'graphics_axes', 'graphics_timeline_item']
 from b_asic.scheduler_gui._version import *
 
diff --git a/b_asic/scheduler_gui/_version.py b/b_asic/scheduler_gui/_version.py
index b1661f1f..0ef1deb5 100644
--- a/b_asic/scheduler_gui/_version.py
+++ b/b_asic/scheduler_gui/_version.py
@@ -1,2 +1,2 @@
-__version_info__ = ('0', '1')
-__version__ = '.'.join(__version_info__)
+__version_info__ = ("0", "1")
+__version__ = ".".join(__version_info__)
diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py
index 29fe6576..957f5d44 100644
--- a/b_asic/scheduler_gui/compile.py
+++ b/b_asic/scheduler_gui/compile.py
@@ -17,11 +17,13 @@ from setuptools_scm import get_version
 
 try:
     import b_asic.scheduler_gui.logger as logger
+
     log = logger.getLogger()
     sys.excepthook = logger.handle_exceptions
 except ModuleNotFoundError:
     log = None
 
+
 def _check_filenames(*filenames: str) -> None:
     """Check if the filename(s) exist, otherwise raise FileNotFoundError
     exception."""
@@ -33,75 +35,78 @@ def _check_filenames(*filenames: str) -> None:
 def _check_qt_version() -> None:
     """Check if PySide2 or PyQt5 is installed, otherwise raise AssertionError
     exception."""
-    assert uic.PYSIDE2 or uic.PYQT5, 'PySide2 or PyQt5 need to be installed'
+    assert uic.PYSIDE2 or uic.PYQT5, "PySide2 or PyQt5 need to be installed"
 
 
 def replace_qt_bindings(filename: str) -> None:
     """Raplaces qt-binding api in 'filename' from PySide2/PyQt5 to qtpy."""
-    with open(f'{filename}', 'r') as file:
+    with open(f"{filename}", "r") as file:
         filedata = file.read()
-        filedata = filedata.replace('from PyQt5', 'from qtpy')
-        filedata = filedata.replace('from PySide2', 'from qtpy')
-    with open(f'{filename}', 'w') as file:
+        filedata = filedata.replace("from PyQt5", "from qtpy")
+        filedata = filedata.replace("from PySide2", "from qtpy")
+    with open(f"{filename}", "w") as file:
         file.write(filedata)
 
 
 def compile_rc(*filenames: str) -> None:
     """Compile resource file(s) given by 'filenames'. If no arguments are given,
-    the compiler will search for resource (.qrc) files and compile accordingly."""
+    the compiler will search for resource (.qrc) files and compile accordingly.
+    """
     _check_qt_version()
 
     def compile(filename: str = None) -> None:
-        outfile = f'{os.path.splitext(filename)[0]}_rc.py'
-        rcc = shutil.which('pyside2-rcc')
-        args = f'-g python -o {outfile} {filename}'
+        outfile = f"{os.path.splitext(filename)[0]}_rc.py"
+        rcc = shutil.which("pyside2-rcc")
+        args = f"-g python -o {outfile} {filename}"
 
         if rcc is None:
-            rcc = shutil.which('rcc')
+            rcc = shutil.which("rcc")
         if rcc is None:
-            rcc = shutil.which('pyrcc5')
-            args = f'-o {outfile} {filename}'
+            rcc = shutil.which("pyrcc5")
+            args = f"-o {outfile} {filename}"
         assert rcc, "PySide2 compiler failed, can't find rcc"
 
         os_ = sys.platform
         if os_.startswith("linux"):  # Linux
-            cmd = f'{rcc} {args}'
+            cmd = f"{rcc} {args}"
             subprocess.call(cmd.split())
 
         elif os_.startswith("win32"):  # Windows
             # TODO: implement
             if log is not None:
-                log.error('Windows RC compiler not implemented')
+                log.error("Windows RC compiler not implemented")
             else:
-                print('Windows RC compiler not implemented')
+                print("Windows RC compiler not implemented")
             raise NotImplementedError
 
         elif os_.startswith("darwin"):  # macOS
             # TODO: implement
             if log is not None:
-                log.error('macOS RC compiler not implemented')
+                log.error("macOS RC compiler not implemented")
             else:
-                print('macOS RC compiler not implemented')
+                print("macOS RC compiler not implemented")
             raise NotImplementedError
 
         else:  # other OS
             if log is not None:
-                log.error(f'{os_} RC compiler not supported')
+                log.error(f"{os_} RC compiler not supported")
             else:
-                print(f'{os_} RC compiler not supported')
+                print(f"{os_} RC compiler not supported")
             raise NotImplementedError
 
         replace_qt_bindings(outfile)  # replace qt-bindings with qtpy
 
     if not filenames:
-        rc_files = [os.path.join(root, name)
-                    for root, _, files in os.walk('.')
-                        for name in files
-                            if name.endswith(('.qrc'))]
-        
+        rc_files = [
+            os.path.join(root, name)
+            for root, _, files in os.walk(".")
+            for name in files
+            if name.endswith(".qrc")
+        ]
+
         for filename in rc_files:
             compile(filename)
-        
+
     else:
         _check_filenames(*filenames)
         for filename in filenames:
@@ -116,51 +121,54 @@ def compile_ui(*filenames: str) -> None:
     def compile(filename: str) -> None:
         dir, file = os.path.split(filename)
         file = os.path.splitext(file)[0]
-        dir = dir if dir else '.'
-        outfile = f'{dir}/ui_{file}.py'
+        dir = dir if dir else "."
+        outfile = f"{dir}/ui_{file}.py"
 
         if uic.PYSIDE2:
-            uic_ = shutil.which('pyside2-uic')
-            args = f'-g python -o {outfile} {filename}'
+            uic_ = shutil.which("pyside2-uic")
+            args = f"-g python -o {outfile} {filename}"
 
             if uic_ is None:
-                uic_ = shutil.which('uic')
+                uic_ = shutil.which("uic")
             if uic_ is None:
-                uic_ = shutil.which('pyuic5')
-                args = f'-o {outfile} {filename}'
+                uic_ = shutil.which("pyuic5")
+                args = f"-o {outfile} {filename}"
             assert uic_, "PySide2 compiler failed, can't find uic"
 
             os_ = sys.platform
             if os_.startswith("linux"):  # Linux
-                cmd = f'{uic_} {args}'
+                cmd = f"{uic_} {args}"
                 subprocess.call(cmd.split())
 
             elif os_.startswith("win32"):  # Windows
                 # TODO: implement
-                log.error('Windows UI compiler not implemented')
+                log.error("Windows UI compiler not implemented")
                 raise NotImplementedError
 
             elif os_.startswith("darwin"):  # macOS
                 # TODO: implement
-                log.error('macOS UI compiler not implemented')
+                log.error("macOS UI compiler not implemented")
                 raise NotImplementedError
 
             else:  # other OS
-                log.error(f'{os_} UI compiler not supported')
+                log.error(f"{os_} UI compiler not supported")
                 raise NotImplementedError
 
-        else:   # uic.PYQT5
+        else:  # uic.PYQT5
             from qtpy.uic import compileUi
-            with open(outfile, 'w') as ofile:
+
+            with open(outfile, "w") as ofile:
                 compileUi(filename, ofile)
 
         replace_qt_bindings(outfile)  # replace qt-bindings with qtpy
 
     if not filenames:
-        ui_files = [os.path.join(root, name)
-                    for root, _, files in os.walk('.')
-                        for name in files
-                            if name.endswith(('.ui'))]
+        ui_files = [
+            os.path.join(root, name)
+            for root, _, files in os.walk(".")
+            for name in files
+            if name.endswith(".ui")
+        ]
         for filename in ui_files:
             compile(filename)
     else:
@@ -176,43 +184,54 @@ def compile_all():
     compile_ui()
 
 
-if __name__ == '__main__':
-    parser = argparse.ArgumentParser(description=f'{__doc__}',
-                                     formatter_class=argparse.RawTextHelpFormatter)
-
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser(
+        description=f"{__doc__}", formatter_class=argparse.RawTextHelpFormatter
+    )
 
-    version = get_version(root='../..', relative_to=__file__)
+    version = get_version(root="../..", relative_to=__file__)
 
-    parser.add_argument('-v', '--version',
-                        action='version',
-                        version=f'%(prog)s v{version}')
+    parser.add_argument(
+        "-v", "--version", action="version", version=f"%(prog)s v{version}"
+    )
 
     if sys.version_info >= (3, 8):
-        parser.add_argument('--ui',
-                            metavar='<file>',
-                            action='extend',
-                            nargs='*',
-                            help="compile form file(s) if <file> is given, otherwise search\n"
-                            "for all form (*.ui) files and compile them all (default)")
-        parser.add_argument('--rc',
-                            metavar='<file>',
-                            action='extend',
-                            nargs='*',
-                            help="compile resource file(s) if <file> is given, otherwise\n"
-                            "search for all resource (*.ui) files and compile them all")
+        parser.add_argument(
+            "--ui",
+            metavar="<file>",
+            action="extend",
+            nargs="*",
+            help=(
+                "compile form file(s) if <file> is given, otherwise search\n"
+                "for all form (*.ui) files and compile them all (default)"
+            ),
+        )
+        parser.add_argument(
+            "--rc",
+            metavar="<file>",
+            action="extend",
+            nargs="*",
+            help=(
+                "compile resource file(s) if <file> is given, otherwise\n"
+                "search for all resource (*.ui) files and compile them all"
+            ),
+        )
     else:
-        parser.add_argument('--ui',
-                            metavar='<file>',
-                            action='append',
-                            help="compile form file")
-        parser.add_argument('--rc',
-                            metavar='<file>',
-                            action='append',
-                            help="compile resource file")
-
-    parser.add_argument('--all',
-                        action='store_true',
-                        help="search and compile all resource and form file(s)")
+        parser.add_argument(
+            "--ui", metavar="<file>", action="append", help="compile form file"
+        )
+        parser.add_argument(
+            "--rc",
+            metavar="<file>",
+            action="append",
+            help="compile resource file",
+        )
+
+    parser.add_argument(
+        "--all",
+        action="store_true",
+        help="search and compile all resource and form file(s)",
+    )
 
     if len(sys.argv) == 1:
         compile_ui()
diff --git a/b_asic/scheduler_gui/graphics_axes_item.py b/b_asic/scheduler_gui/graphics_axes_item.py
index 6a7da2ed..de660c26 100644
--- a/b_asic/scheduler_gui/graphics_axes_item.py
+++ b/b_asic/scheduler_gui/graphics_axes_item.py
@@ -265,7 +265,6 @@ class GraphicsAxesItem(QGraphicsItemGroup):
             self._x_scale[index + 1].setX(self._x_scale[index + 1].x() + 1)
 
     def _make_base(self) -> None:
-
         # x axis
         self._x_axis.setLine(0, 0, self._width_indent + self._width_padding, 0)
         self._x_axis.setPen(self._base_pen)
diff --git a/b_asic/scheduler_gui/graphics_component_item.py b/b_asic/scheduler_gui/graphics_component_item.py
index b78a3c37..d69df550 100644
--- a/b_asic/scheduler_gui/graphics_component_item.py
+++ b/b_asic/scheduler_gui/graphics_component_item.py
@@ -49,7 +49,8 @@ class GraphicsComponentItem(QGraphicsItemGroup):
         height: float = 0.75,
         parent: Optional[QGraphicsItem] = None,
     ):
-        """Constructs a GraphicsComponentItem. 'parent' is passed to QGraphicsItemGroup's constructor."""
+        """Constructs a GraphicsComponentItem. 'parent' is passed to QGraphicsItemGroup's constructor.
+        """
         super().__init__(parent=parent)
         self._operation = operation
         self._height = height
@@ -155,7 +156,8 @@ class GraphicsComponentItem(QGraphicsItemGroup):
 
         ## component path
         def draw_component_path(keys: List[str], reversed: bool) -> None:
-            """Draws component path and also register port positions in self._ports dictionary."""
+            """Draws component path and also register port positions in self._ports dictionary.
+            """
             nonlocal x
             nonlocal y
             nonlocal old_x
diff --git a/b_asic/scheduler_gui/graphics_graph_event.py b/b_asic/scheduler_gui/graphics_graph_event.py
index c2a48705..2cd0f8ac 100644
--- a/b_asic/scheduler_gui/graphics_graph_event.py
+++ b/b_asic/scheduler_gui/graphics_graph_event.py
@@ -214,7 +214,8 @@ class GraphicsGraphEvent:  # PyQt5
         """Changes the cursor to ClosedHandCursor when grabbing an object and
         stores the current position in item's parent coordinates. 'event' will
         by default be accepted, and this item is then the mouse grabber. This
-        allows the item to receive future move, release and double-click events."""
+        allows the item to receive future move, release and double-click events.
+        """
         item: GraphicsComponentItem = self.scene().mouseGrabberItem()
         self._signals.component_selected.emit(item.op_id)
         # self.component_selected.emit(item.op_id)
@@ -267,7 +268,8 @@ class GraphicsGraphEvent:  # PyQt5
     ) -> None:
         """Stores the current position in item's parent coordinates. 'event' will
         by default be accepted, and this item is then the mouse grabber. This
-        allows the item to receive future move, release and double-click events."""
+        allows the item to receive future move, release and double-click events.
+        """
         item: GraphicsTimelineItem = self.scene().mouseGrabberItem()
         self._delta_time = 0
         item.set_text(self._delta_time)
diff --git a/b_asic/scheduler_gui/graphics_graph_item.py b/b_asic/scheduler_gui/graphics_graph_item.py
index f0a46623..8a3c43dd 100644
--- a/b_asic/scheduler_gui/graphics_graph_item.py
+++ b/b_asic/scheduler_gui/graphics_graph_item.py
@@ -41,7 +41,8 @@ class GraphicsGraphItem(
     def __init__(
         self, schedule: Schedule, parent: Optional[QGraphicsItem] = None
     ):
-        """Constructs a GraphicsGraphItem. 'parent' is passed to QGraphicsItemGroup's constructor."""
+        """Constructs a GraphicsGraphItem. 'parent' is passed to QGraphicsItemGroup's constructor.
+        """
         # QGraphicsItemGroup.__init__(self, self)
         # GraphicsGraphEvent.__init__(self)
         super().__init__(parent=parent)
@@ -59,7 +60,8 @@ class GraphicsGraphItem(
         self._make_graph()
 
     def clear(self) -> None:
-        """Sets all children's parent to 'None' and delete the children objects."""
+        """Sets all children's parent to 'None' and delete the children objects.
+        """
         self._event_items = []
         for item in self.childItems():
             item.setParentItem(None)
diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index 42b2cc58..6d834b0b 100644
--- a/b_asic/scheduler_gui/main_window.py
+++ b/b_asic/scheduler_gui/main_window.py
@@ -47,6 +47,7 @@ from b_asic.schedule import Schedule
 from b_asic.scheduler_gui.graphics_axes_item import GraphicsAxesItem
 from b_asic.scheduler_gui.graphics_component_item import GraphicsComponentItem
 from b_asic.scheduler_gui.graphics_graph_item import GraphicsGraphItem
+
 sys.path.insert(0, "icons/")  # Needed for *.rc.py files in ui_main_window
 from b_asic.scheduler_gui.ui_main_window import Ui_MainWindow
 
@@ -424,7 +425,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
         self.update_statusbar(self.tr("Schedule loaded successfully"))
 
     def update_statusbar(self, msg: str) -> None:
-        """Takes in an str and write 'msg' to the statusbar with temporarily policy."""
+        """Takes in an str and write 'msg' to the statusbar with temporarily policy.
+        """
         self.statusbar.showMessage(msg)
 
     def _write_settings(self) -> None:
diff --git a/b_asic/scheduler_gui/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py
index b84799fa..f197afcd 100644
--- a/b_asic/scheduler_gui/ui_main_window.py
+++ b/b_asic/scheduler_gui/ui_main_window.py
@@ -15,19 +15,31 @@ class Ui_MainWindow(object):
     def setupUi(self, MainWindow):
         MainWindow.setObjectName("MainWindow")
         MainWindow.resize(800, 600)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+        sizePolicy = QtWidgets.QSizePolicy(
+            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
+        )
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
+        sizePolicy.setHeightForWidth(
+            MainWindow.sizePolicy().hasHeightForWidth()
+        )
         MainWindow.setSizePolicy(sizePolicy)
         icon = QtGui.QIcon()
-        icon.addPixmap(QtGui.QPixmap(":/icons/basic/small_logo.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
+        icon.addPixmap(
+            QtGui.QPixmap(":/icons/basic/small_logo.png"),
+            QtGui.QIcon.Normal,
+            QtGui.QIcon.Off,
+        )
         MainWindow.setWindowIcon(icon)
         self.centralwidget = QtWidgets.QWidget(MainWindow)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred)
+        sizePolicy = QtWidgets.QSizePolicy(
+            QtWidgets.QSizePolicy.Preferred, QtWidgets.QSizePolicy.Preferred
+        )
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
+        sizePolicy.setHeightForWidth(
+            self.centralwidget.sizePolicy().hasHeightForWidth()
+        )
         self.centralwidget.setSizePolicy(sizePolicy)
         self.centralwidget.setObjectName("centralwidget")
         self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
@@ -39,15 +51,28 @@ class Ui_MainWindow(object):
         self.splitter.setHandleWidth(0)
         self.splitter.setObjectName("splitter")
         self.view = QtWidgets.QGraphicsView(self.splitter)
-        self.view.setAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignLeft|QtCore.Qt.AlignTop)
-        self.view.setRenderHints(QtGui.QPainter.HighQualityAntialiasing|QtGui.QPainter.TextAntialiasing)
-        self.view.setViewportUpdateMode(QtWidgets.QGraphicsView.FullViewportUpdate)
+        self.view.setAlignment(
+            QtCore.Qt.AlignLeading | QtCore.Qt.AlignLeft | QtCore.Qt.AlignTop
+        )
+        self.view.setRenderHints(
+            QtGui.QPainter.HighQualityAntialiasing
+            | QtGui.QPainter.TextAntialiasing
+        )
+        self.view.setViewportUpdateMode(
+            QtWidgets.QGraphicsView.FullViewportUpdate
+        )
         self.view.setObjectName("view")
         self.info_table = QtWidgets.QTableWidget(self.splitter)
-        self.info_table.setStyleSheet("alternate-background-color: #fadefb;background-color: #ebebeb;")
-        self.info_table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers)
+        self.info_table.setStyleSheet(
+            "alternate-background-color: #fadefb;background-color: #ebebeb;"
+        )
+        self.info_table.setEditTriggers(
+            QtWidgets.QAbstractItemView.NoEditTriggers
+        )
         self.info_table.setAlternatingRowColors(True)
-        self.info_table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows)
+        self.info_table.setSelectionBehavior(
+            QtWidgets.QAbstractItemView.SelectRows
+        )
         self.info_table.setRowCount(2)
         self.info_table.setColumnCount(2)
         self.info_table.setObjectName("info_table")
@@ -56,10 +81,10 @@ class Ui_MainWindow(object):
         item = QtWidgets.QTableWidgetItem()
         self.info_table.setVerticalHeaderItem(1, item)
         item = QtWidgets.QTableWidgetItem()
-        item.setTextAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignVCenter)
+        item.setTextAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignVCenter)
         self.info_table.setHorizontalHeaderItem(0, item)
         item = QtWidgets.QTableWidgetItem()
-        item.setTextAlignment(QtCore.Qt.AlignLeading|QtCore.Qt.AlignVCenter)
+        item.setTextAlignment(QtCore.Qt.AlignLeading | QtCore.Qt.AlignVCenter)
         self.info_table.setHorizontalHeaderItem(1, item)
         item = QtWidgets.QTableWidgetItem()
         font = QtGui.QFont()
@@ -72,7 +97,13 @@ class Ui_MainWindow(object):
         brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
         brush.setStyle(QtCore.Qt.SolidPattern)
         item.setForeground(brush)
-        item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEditable|QtCore.Qt.ItemIsDragEnabled|QtCore.Qt.ItemIsDropEnabled|QtCore.Qt.ItemIsUserCheckable)
+        item.setFlags(
+            QtCore.Qt.ItemIsSelectable
+            | QtCore.Qt.ItemIsEditable
+            | QtCore.Qt.ItemIsDragEnabled
+            | QtCore.Qt.ItemIsDropEnabled
+            | QtCore.Qt.ItemIsUserCheckable
+        )
         self.info_table.setItem(0, 0, item)
         item = QtWidgets.QTableWidgetItem()
         font = QtGui.QFont()
@@ -85,7 +116,13 @@ class Ui_MainWindow(object):
         brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
         brush.setStyle(QtCore.Qt.SolidPattern)
         item.setForeground(brush)
-        item.setFlags(QtCore.Qt.ItemIsSelectable|QtCore.Qt.ItemIsEditable|QtCore.Qt.ItemIsDragEnabled|QtCore.Qt.ItemIsDropEnabled|QtCore.Qt.ItemIsUserCheckable)
+        item.setFlags(
+            QtCore.Qt.ItemIsSelectable
+            | QtCore.Qt.ItemIsEditable
+            | QtCore.Qt.ItemIsDragEnabled
+            | QtCore.Qt.ItemIsDropEnabled
+            | QtCore.Qt.ItemIsUserCheckable
+        )
         self.info_table.setItem(1, 0, item)
         self.info_table.horizontalHeader().setHighlightSections(False)
         self.info_table.horizontalHeader().setStretchLastSection(True)
@@ -125,8 +162,16 @@ class Ui_MainWindow(object):
         self.menu_node_info.setCheckable(True)
         self.menu_node_info.setChecked(True)
         icon1 = QtGui.QIcon()
-        icon1.addPixmap(QtGui.QPixmap(":/icons/misc/right_panel.svg"), QtGui.QIcon.Normal, QtGui.QIcon.Off)
-        icon1.addPixmap(QtGui.QPixmap(":/icons/misc/right_filled_panel.svg"), QtGui.QIcon.Normal, QtGui.QIcon.On)
+        icon1.addPixmap(
+            QtGui.QPixmap(":/icons/misc/right_panel.svg"),
+            QtGui.QIcon.Normal,
+            QtGui.QIcon.Off,
+        )
+        icon1.addPixmap(
+            QtGui.QPixmap(":/icons/misc/right_filled_panel.svg"),
+            QtGui.QIcon.Normal,
+            QtGui.QIcon.On,
+        )
         self.menu_node_info.setIcon(icon1)
         self.menu_node_info.setIconVisibleInMenu(False)
         self.menu_node_info.setObjectName("menu_node_info")
@@ -195,19 +240,33 @@ class Ui_MainWindow(object):
         self.menu_Edit.setTitle(_translate("MainWindow", "&Edit"))
         self.menuWindow.setTitle(_translate("MainWindow", "&Window"))
         self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
-        self.menu_load_from_file.setText(_translate("MainWindow", "&Load Schedule From File..."))
-        self.menu_load_from_file.setToolTip(_translate("MainWindow", "Load schedule from python script"))
-        self.menu_load_from_file.setShortcut(_translate("MainWindow", "Ctrl+O"))
+        self.menu_load_from_file.setText(
+            _translate("MainWindow", "&Load Schedule From File...")
+        )
+        self.menu_load_from_file.setToolTip(
+            _translate("MainWindow", "Load schedule from python script")
+        )
+        self.menu_load_from_file.setShortcut(
+            _translate("MainWindow", "Ctrl+O")
+        )
         self.menu_save.setText(_translate("MainWindow", "&Save"))
         self.menu_save.setToolTip(_translate("MainWindow", "Save schedule"))
         self.menu_save.setShortcut(_translate("MainWindow", "Ctrl+S"))
         self.menu_node_info.setText(_translate("MainWindow", "&Node Info"))
-        self.menu_node_info.setToolTip(_translate("MainWindow", "Show node information"))
+        self.menu_node_info.setToolTip(
+            _translate("MainWindow", "Show node information")
+        )
         self.menu_node_info.setShortcut(_translate("MainWindow", "Ctrl+I"))
         self.menu_quit.setText(_translate("MainWindow", "&Quit"))
         self.menu_quit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
         self.menu_save_as.setText(_translate("MainWindow", "Save &As..."))
-        self.menu_exit_dialog.setText(_translate("MainWindow", "&Hide exit dialog"))
-        self.menu_exit_dialog.setToolTip(_translate("MainWindow", "Hide exit dialog"))
+        self.menu_exit_dialog.setText(
+            _translate("MainWindow", "&Hide exit dialog")
+        )
+        self.menu_exit_dialog.setToolTip(
+            _translate("MainWindow", "Hide exit dialog")
+        )
         self.actionT.setText(_translate("MainWindow", "T"))
-        self.menu_close_schedule.setText(_translate("MainWindow", "&Close Schedule"))
+        self.menu_close_schedule.setText(
+            _translate("MainWindow", "&Close Schedule")
+        )
diff --git a/b_asic/signal.py b/b_asic/signal.py
index 594afa3c..90af747f 100644
--- a/b_asic/signal.py
+++ b/b_asic/signal.py
@@ -90,7 +90,8 @@ class Signal(AbstractGraphComponent):
 
     def remove_source(self) -> None:
         """Disconnect the source OutputPort of the signal. If the source port
-        still is connected to this signal then also disconnect the source port."""
+        still is connected to this signal then also disconnect the source port.
+        """
         src = self._source
         if src is not None:
             self._source = None
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index c200dc69..b724437b 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -297,7 +297,8 @@ class SFG(AbstractOperation):
     def __call__(
         self, *src: Optional[SignalSourceProvider], name: Name = ""
     ) -> "SFG":
-        """Get a new independent SFG instance that is identical to this SFG except without any of its external connections."""
+        """Get a new independent SFG instance that is identical to this SFG except without any of its external connections.
+        """
         return SFG(
             inputs=self._input_operations,
             outputs=self._output_operations,
@@ -379,7 +380,8 @@ class SFG(AbstractOperation):
 
     def connect_external_signals_to_components(self) -> bool:
         """Connects any external signals to this SFG's internal operations. This SFG becomes unconnected to the SFG
-        it is a component off, causing it to become invalid afterwards. Returns True if succesful, False otherwise."""
+        it is a component off, causing it to become invalid afterwards. Returns True if succesful, False otherwise.
+        """
         if len(self.inputs) != len(self.input_operations):
             raise IndexError(
                 f"Number of inputs does not match the number of"
@@ -411,12 +413,14 @@ class SFG(AbstractOperation):
 
     @property
     def input_operations(self) -> Sequence[Operation]:
-        """Get the internal input operations in the same order as their respective input ports."""
+        """Get the internal input operations in the same order as their respective input ports.
+        """
         return self._input_operations
 
     @property
     def output_operations(self) -> Sequence[Operation]:
-        """Get the internal output operations in the same order as their respective output ports."""
+        """Get the internal output operations in the same order as their respective output ports.
+        """
         return self._output_operations
 
     def split(self) -> Iterable[Operation]:
@@ -471,7 +475,8 @@ class SFG(AbstractOperation):
 
     @property
     def id_number_offset(self) -> GraphIDNumber:
-        """Get the graph id number offset of the graph id generator for this SFG."""
+        """Get the graph id number offset of the graph id generator for this SFG.
+        """
         return self._graph_id_generator.id_number_offset
 
     @property
@@ -612,7 +617,8 @@ class SFG(AbstractOperation):
     def remove_operation(self, operation_id: GraphID) -> "SFG":
         """Returns a version of the SFG where the operation with the specified GraphID removed.
         The operation has to have the same amount of input- and output ports or a ValueError will
-        be raised. If no operation with the entered operation_id is found then returns None and does nothing."""
+        be raised. If no operation with the entered operation_id is found then returns None and does nothing.
+        """
         sfg_copy = self()
         operation = sfg_copy.find_by_id(operation_id)
         if operation is None:
@@ -756,7 +762,8 @@ class SFG(AbstractOperation):
     def get_operations_topological_order(self) -> Iterable[Operation]:
         """Returns an Iterable of the Operations in the SFG in Topological Order.
         Feedback loops makes an absolutely correct Topological order impossible, so an
-        approximative Topological Order is returned in such cases in this implementation."""
+        approximative Topological Order is returned in such cases in this implementation.
+        """
         if self._operations_topological_order:
             return self._operations_topological_order
 
@@ -791,7 +798,6 @@ class SFG(AbstractOperation):
 
         while operations_left > 0:
             while not p_queue.empty():
-
                 op = p_queue.get()[2]
 
                 operations_left -= 1
@@ -869,14 +875,16 @@ class SFG(AbstractOperation):
     def set_execution_time_of_type(
         self, type_name: TypeName, execution_time: int
     ) -> None:
-        """Set the execution time of all components with the given type name."""
+        """Set the execution time of all components with the given type name.
+        """
         for op in self.find_by_type_name(type_name):
             op.execution_time = execution_time
 
     def set_latency_offsets_of_type(
         self, type_name: TypeName, latency_offsets: Dict[str, int]
     ) -> None:
-        """Set the latency offset of all components with the given type name."""
+        """Set the latency offset of all components with the given type name.
+        """
         for op in self.find_by_type_name(type_name):
             op.set_latency_offsets(latency_offsets)
 
diff --git a/b_asic/simulation.py b/b_asic/simulation.py
index c4b35937..99d470c7 100644
--- a/b_asic/simulation.py
+++ b/b_asic/simulation.py
@@ -68,7 +68,8 @@ class Simulation:
             self.set_inputs(input_providers)
 
     def set_input(self, index: int, input_provider: InputProvider) -> None:
-        """Set the input function used to get values for the specific input at the given index to the internal SFG."""
+        """Set the input function used to get values for the specific input at the given index to the internal SFG.
+        """
         if index < 0 or index >= len(self._input_functions):
             raise IndexError(
                 "Input index out of range (expected"
@@ -91,7 +92,8 @@ class Simulation:
     def set_inputs(
         self, input_providers: Sequence[Optional[InputProvider]]
     ) -> None:
-        """Set the input functions used to get values for the inputs to the internal SFG."""
+        """Set the input functions used to get values for the inputs to the internal SFG.
+        """
         if len(input_providers) != self._sfg.input_count:
             raise ValueError(
                 "Wrong number of inputs supplied to simulation (expected"
@@ -107,7 +109,8 @@ class Simulation:
         bits_override: Optional[int] = None,
         truncate: bool = True,
     ) -> Sequence[Number]:
-        """Run one iteration of the simulation and return the resulting output values."""
+        """Run one iteration of the simulation and return the resulting output values.
+        """
         return self.run_for(1, save_results, bits_override, truncate)
 
     def run_until(
@@ -148,7 +151,8 @@ class Simulation:
         bits_override: Optional[int] = None,
         truncate: bool = True,
     ) -> Sequence[Number]:
-        """Run a given number of iterations of the simulation and return the output values of the last iteration."""
+        """Run a given number of iterations of the simulation and return the output values of the last iteration.
+        """
         return self.run_until(
             self._iteration + iterations, save_results, bits_override, truncate
         )
@@ -159,7 +163,8 @@ class Simulation:
         bits_override: Optional[int] = None,
         truncate: bool = True,
     ) -> Sequence[Number]:
-        """Run the simulation until the end of its input arrays and return the output values of the last iteration."""
+        """Run the simulation until the end of its input arrays and return the output values of the last iteration.
+        """
         if self._input_length is None:
             raise IndexError("Tried to run unlimited simulation")
         return self.run_until(
@@ -185,5 +190,6 @@ class Simulation:
         self._results.clear()
 
     def clear_state(self) -> None:
-        """Clear all current state of the simulation, except for the results and iteration."""
+        """Clear all current state of the simulation, except for the results and iteration.
+        """
         self._delays.clear()
diff --git a/docs_sphinx/api/process.rst b/docs_sphinx/api/process.rst
new file mode 100644
index 00000000..97a1bc65
--- /dev/null
+++ b/docs_sphinx/api/process.rst
@@ -0,0 +1,12 @@
+******************
+``b_asic.process``
+******************
+
+.. inheritance-diagram:: b_asic.process
+   :parts: 1
+   :top-classes: b_asic.process.Process
+
+.. automodule:: b_asic.process
+   :members:
+   :undoc-members:
+   :show-inheritance:
diff --git a/pyproject.toml b/pyproject.toml
index b08dd9b0..6fba3216 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -51,3 +51,8 @@ fallback_version = "0.0+UNKNOWN"
 [project.urls]
 homepage = "https://gitlab.liu.se/da/B-ASIC"
 documenation = "https://da.gitlab-pages.liu.se/B-ASIC/"
+
+[tool.black]
+skip-string-normalization = true
+preview = true
+line-length = 79
diff --git a/test/test_process.py b/test/test_process.py
new file mode 100644
index 00000000..80dd2ab6
--- /dev/null
+++ b/test/test_process.py
@@ -0,0 +1,11 @@
+import pytest
+
+from b_asic.process import PlainMemoryVariable
+
+def test_PlainMemoryVariable():
+    mem = PlainMemoryVariable(3, 0, {4: 1, 5: 2})
+    assert mem.write_port == 0
+    assert mem.start_time == 3
+    assert mem.execution_time == 2
+    assert mem.life_times == (1, 2)
+    assert mem.read_ports == (4, 5)
-- 
GitLab