From ac8d2c327cb7b4f4431e9801a326d2c3f295986c Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 4 Feb 2025 10:29:41 +0000
Subject: [PATCH] Finalize earliest deadline scheduler

---
 .coveragerc                                   |   4 +
 .gitignore                                    |   1 +
 .gitlab-ci.yml                                |  34 +-
 README.md                                     |   8 +-
 b_asic/GUI/main_window.py                     |   1 -
 b_asic/__init__.py                            |   1 +
 b_asic/schedule.py                            |  40 +-
 b_asic/scheduler.py                           | 294 +++---
 b_asic/scheduler_gui/compile.py               |  45 +-
 b_asic/scheduler_gui/main_window.py           |  24 +-
 b_asic/scheduler_gui/ui_main_window.py        | 848 +++++++++++-------
 b_asic/signal_flow_graph.py                   |   3 +-
 examples/fivepointwinograddft.py              |   3 +-
 examples/folding_example_with_architecture.py |   3 +-
 examples/lwdfallpass.py                       |   3 +-
 examples/secondorderdirectformiir.py          |   3 +-
 .../secondorderdirectformiir_architecture.py  |   3 +-
 examples/thirdorderblwdf.py                   |   3 +-
 examples/threepointwinograddft.py             |   3 +-
 pyproject.toml                                |   4 +
 test/fixtures/schedule.py                     |   9 +-
 test/fixtures/signal_flow_graph.py            |   8 +
 test/test_architecture.py                     |   3 +-
 test/test_schedule.py                         |  83 +-
 test/test_scheduler.py                        | 259 ++++++
 test/test_scheduler_gui.py                    |   3 +-
 26 files changed, 1123 insertions(+), 570 deletions(-)
 create mode 100644 .coveragerc
 create mode 100644 test/test_scheduler.py

diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 00000000..a765f497
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,4 @@
+[report]
+exclude_lines =
+    .*if TYPE_CHECKING:.*
+    raise NotImplementedError
diff --git a/.gitignore b/.gitignore
index 8a416e12..4530b5b1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -117,3 +117,4 @@ b_asic/_version.py
 docs_sphinx/_build/
 docs_sphinx/examples
 result_images/
+.coverage
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 5f4c6f59..4b33da65 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -36,51 +36,45 @@ before_script:
         path: cov.xml
   coverage: /(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/
 
-run-test-3.10-pyside2:
+run-test-3.10-pyqt6:
   variables:
-    QT_API: pyside2
+    QT_API: pyqt6
   image: python:3.10
   extends: ".run-test"
 
-run-test-3.10-pyqt5:
+run-test-3.10-pyside6:
   variables:
-    QT_API: pyqt5
+    QT_API: pyside6
   image: python:3.10
   extends: ".run-test"
 
-run-test-3.10-pyqt6:
+run-test-3.11-pyqt6:
   variables:
     QT_API: pyqt6
-  image: python:3.10
-  extends: ".run-test"
-
-run-test-3.11-pyqt5:
-  variables:
-    QT_API: pyqt5
   image: python:3.11
   extends: ".run-test"
 
-run-test-3.11-pyqt6:
+run-test-3.11-pyside6:
   variables:
-    QT_API: pyqt6
+    QT_API: pyside6
   image: python:3.11
   extends: ".run-test"
 
-run-test-3.12-pyqt5:
+run-test-3.12-pyqt6:
   variables:
-    QT_API: pyqt5
+    QT_API: pyqt6
   image: python:3.12
   extends: ".run-test"
 
-run-test-3.12-pyqt6:
+run-test-3.12-pyside6:
   variables:
-    QT_API: pyqt6
+    QT_API: pyside6
   image: python:3.12
   extends: ".run-test"
 
 run-vhdl-tests:
   variables:
-    QT_API: pyqt5
+    QT_API: pyqt6
   image: python:3.10
   stage: test
   script:
@@ -92,7 +86,7 @@ run-vhdl-tests:
 
 run-doc-test:
   variables:
-    QT_API: pyside2
+    QT_API: pyside6
   image: python:3.10
   stage: test
   script:
@@ -111,7 +105,7 @@ run-doc-test:
 
 pages:
   variables:
-    QT_API: pyqt5
+    QT_API: pyqt6
   stage: deploy
   image: python:3.10
   script:
diff --git a/README.md b/README.md
index 074f2828..d216f6dc 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ How to build and debug the library during development.
 
 The following packages are required in order to build the library:
 
-- [Python](https://python.org/) 3.8+
+- [Python](https://python.org/) 3.10+
 - Python dependencies (install with `pip install -r requirements.txt` or they will be installed as part of the
   installation process):
   - [Graphviz](https://graphviz.org/)
@@ -24,11 +24,9 @@ The following packages are required in order to build the library:
   - [setuptools_scm](https://github.com/pypa/setuptools_scm/)
   - [NetworkX](https://networkx.org/)
   - [QtAwesome](https://github.com/spyder-ide/qtawesome/)
-- Qt 5 or 6, with Python bindings, one of:
-  - pyside2
-  - pyqt5
-  - pyside6
+- Qt 6, with Python bindings, one of:
   - pyqt6
+  - pyside6
 
 To build a binary distribution, the following additional packages are required:
 
diff --git a/b_asic/GUI/main_window.py b/b_asic/GUI/main_window.py
index b3ab5a76..90146b87 100644
--- a/b_asic/GUI/main_window.py
+++ b/b_asic/GUI/main_window.py
@@ -978,7 +978,6 @@ def start_editor(sfg: Optional[SFG] = None) -> Dict[str, SFG]:
         All SFGs currently in the editor.
     """
     if not QApplication.instance():
-        QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
         app = QApplication(sys.argv)
     else:
         app = QApplication.instance()
diff --git a/b_asic/__init__.py b/b_asic/__init__.py
index fae7aec4..423e4676 100644
--- a/b_asic/__init__.py
+++ b/b_asic/__init__.py
@@ -9,6 +9,7 @@ from b_asic.operation import *
 from b_asic.port import *
 from b_asic.save_load_structure import *
 from b_asic.schedule import *
+from b_asic.scheduler import *
 from b_asic.signal import *
 from b_asic.signal_flow_graph import *
 from b_asic.simulation import *
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index a708fe1a..4c7f01b6 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -33,7 +33,7 @@ from b_asic.operation import Operation
 from b_asic.port import InputPort, OutputPort
 from b_asic.process import MemoryVariable, OperatorProcess
 from b_asic.resources import ProcessCollection
-from b_asic.scheduler import Scheduler, SchedulingAlgorithm
+from b_asic.scheduler import Scheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 from b_asic.types import TypeName
@@ -64,24 +64,19 @@ class Schedule:
     ----------
     sfg : :class:`~b_asic.signal_flow_graph.SFG`
         The signal flow graph to schedule.
+    scheduler : Scheduler, default: None
+        The automatic scheduler to be used.
     schedule_time : int, optional
         The schedule time. If not provided, it will be determined by the scheduling
         algorithm.
     cyclic : bool, default: False
         If the schedule is cyclic.
-    algorithm : SchedulingAlgorithm, default: 'ASAP'
-        The scheduling algorithm to use.
     start_times : dict, optional
         Dictionary with GraphIDs as keys and start times as values.
         Used when *algorithm* is 'provided'.
     laps : dict, optional
         Dictionary with GraphIDs as keys and laps as values.
         Used when *algorithm* is 'provided'.
-    max_resources : dict, optional
-        Dictionary like ``{Addition.type_name(): 2}`` denoting the maximum number of
-        resources for a given operation type if the scheduling algorithm considers
-        that. If not provided, or an operation type is not provided, at most one
-        resource is used.
     """
 
     _sfg: SFG
@@ -94,12 +89,11 @@ class Schedule:
     def __init__(
         self,
         sfg: SFG,
+        scheduler: Optional[Scheduler] = None,
         schedule_time: Optional[int] = None,
         cyclic: bool = False,
-        algorithm: SchedulingAlgorithm = "ASAP",
         start_times: Optional[Dict[GraphID, int]] = None,
         laps: Optional[Dict[GraphID, int]] = None,
-        max_resources: Optional[Dict[TypeName, int]] = None,
     ):
         """Construct a Schedule from an SFG."""
         if not isinstance(sfg, SFG):
@@ -112,14 +106,10 @@ class Schedule:
         self._y_locations = defaultdict(_y_locations_default)
         self._schedule_time = schedule_time
 
-        self.scheduler = Scheduler(self)
-        if algorithm == "ASAP":
-            self.scheduler.schedule_asap()
-        elif algorithm == "ALAP":
-            self.scheduler.schedule_alap()
-        elif algorithm == "earliest_deadline":
-            self.scheduler.schedule_earliest_deadline([])
-        elif algorithm == "provided":
+        if scheduler:
+            self._scheduler = scheduler
+            self._scheduler.apply_scheduling(self)
+        else:
             if start_times is None:
                 raise ValueError("Must provide start_times when using 'provided'")
             if laps is None:
@@ -127,11 +117,8 @@ class Schedule:
             self._start_times = start_times
             self._laps.update(laps)
             self._remove_delays_no_laps()
-        else:
-            raise NotImplementedError(f"No algorithm with name: {algorithm} defined.")
 
         max_end_time = self.get_max_end_time()
-
         if schedule_time is None:
             self._schedule_time = max_end_time
         elif schedule_time < max_end_time:
@@ -399,6 +386,15 @@ class Schedule:
         """The start times of the operations in the schedule."""
         return self._start_times
 
+    @start_times.setter
+    def start_times(self, start_times: dict[GraphID, int]) -> None:
+        if not isinstance(start_times, dict):
+            raise TypeError("start_times must be a dict")
+        for key, value in start_times.items():
+            if not isinstance(key, str) or not isinstance(value, int):
+                raise TypeError("start_times must be a dict[GraphID, int]")
+        self._start_times = start_times
+
     @property
     def laps(self) -> Dict[GraphID, int]:
         """
@@ -770,7 +766,7 @@ class Schedule:
             self._sfg = cast(SFG, self._sfg.remove_operation(delay_op.graph_id))
             delay_list = self._sfg.find_by_type_name(Delay.type_name())
 
-    def _remove_delays(self) -> None:
+    def remove_delays(self) -> None:
         """Remove delay elements and update laps. Used after scheduling algorithm."""
         delay_list = self._sfg.find_by_type_name(Delay.type_name())
         while delay_list:
diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 845859c9..e109d01d 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,30 +1,58 @@
-from enum import Enum
-from typing import TYPE_CHECKING, cast
+import sys
+from abc import ABC, abstractmethod
+from collections import defaultdict
+from typing import TYPE_CHECKING, Optional, cast
 
-from b_asic.operation import Operation
 from b_asic.port import OutputPort
-from b_asic.special_operations import Delay, Output
+from b_asic.special_operations import Delay, Input, Output
+from b_asic.types import TypeName
 
 if TYPE_CHECKING:
     from b_asic.schedule import Schedule
 
 
-class SchedulingAlgorithm(Enum):
-    ASAP = "ASAP"
-    ALAP = "ALAP"
-    EARLIEST_DEADLINE = "earliest_deadline"
-    # LEAST_SLACK = "least_slack" # to be implemented
-    PROVIDED = "provided"
+class Scheduler(ABC):
+    @abstractmethod
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        """Applies the scheduling algorithm on the given Schedule.
 
+        Parameters
+        ----------
+        schedule : Schedule
+            Schedule to apply the scheduling algorithm on.
+        """
+        raise NotImplementedError
 
-class Scheduler:
-    def __init__(self, schedule: "Schedule") -> None:
-        self.schedule = schedule
+    def _handle_outputs(self, schedule, non_schedulable_ops=set()) -> None:
+        for output in schedule.sfg.find_by_type_name(Output.type_name()):
+            output = cast(Output, output)
+            source_port = cast(OutputPort, output.inputs[0].signals[0].source)
+            if source_port.operation.graph_id in non_schedulable_ops:
+                schedule.start_times[output.graph_id] = 0
+            else:
+                if source_port.latency_offset is None:
+                    raise ValueError(
+                        f"Output port {source_port.index} of operation"
+                        f" {source_port.operation.graph_id} has no"
+                        " latency-offset."
+                    )
+                schedule.start_times[output.graph_id] = schedule.start_times[
+                    source_port.operation.graph_id
+                ] + cast(int, source_port.latency_offset)
+
+
+class ASAPScheduler(Scheduler):
+    """Scheduler that implements the as-soon-as-possible (ASAP) algorithm."""
+
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        """Applies the scheduling algorithm on the given Schedule.
 
-    def schedule_asap(self) -> None:
-        """Schedule the operations using as-soon-as-possible scheduling."""
-        sched = self.schedule
-        prec_list = sched.sfg.get_precedence_list()
+        Parameters
+        ----------
+        schedule : Schedule
+            Schedule to apply the scheduling algorithm on.
+        """
+        prec_list = schedule.sfg.get_precedence_list()
         if len(prec_list) < 2:
             raise ValueError("Empty signal flow graph cannot be scheduled.")
 
@@ -34,37 +62,27 @@ class Scheduler:
             operation = outport.operation
             if operation.type_name() == Delay.type_name():
                 non_schedulable_ops.add(operation.graph_id)
-            # elif operation.graph_id not in sched._start_times:
             else:
-                sched._start_times[operation.graph_id] = 0
+                schedule.start_times[operation.graph_id] = 0
 
         # handle second set in precedence graph (first operations)
         for outport in prec_list[1]:
             operation = outport.operation
-            # if operation.graph_id not in sched._start_times:
-            sched._start_times[operation.graph_id] = 0
+            schedule.start_times[operation.graph_id] = 0
 
         # handle the remaining sets
         for outports in prec_list[2:]:
             for outport in outports:
                 operation = outport.operation
-                if operation.graph_id not in sched._start_times:
+                if operation.graph_id not in schedule.start_times:
                     op_start_time = 0
                     for current_input in operation.inputs:
-                        if len(current_input.signals) != 1:
-                            raise ValueError(
-                                "Error in scheduling, dangling input port detected."
-                            )
-                        if current_input.signals[0].source is None:
-                            raise ValueError(
-                                "Error in scheduling, signal with no source detected."
-                            )
                         source_port = current_input.signals[0].source
 
                         if source_port.operation.graph_id in non_schedulable_ops:
                             source_end_time = 0
                         else:
-                            source_op_time = sched._start_times[
+                            source_op_time = schedule.start_times[
                                 source_port.operation.graph_id
                             ]
 
@@ -91,103 +109,161 @@ class Scheduler:
                         )
                         op_start_time = max(op_start_time, op_start_time_from_in)
 
-                    sched._start_times[operation.graph_id] = op_start_time
+                    schedule.start_times[operation.graph_id] = op_start_time
 
-        self._handle_outputs_and_delays(non_schedulable_ops)
+        self._handle_outputs(schedule, non_schedulable_ops)
+        schedule.remove_delays()
 
-    def schedule_alap(self) -> None:
-        """Schedule the operations using as-late-as-possible scheduling."""
-        self.schedule_asap()
-        sched = self.schedule
-        max_end_time = sched.get_max_end_time()
 
-        if sched.schedule_time is None:
-            sched.set_schedule_time(max_end_time)
-        elif sched.schedule_time < max_end_time:
+class ALAPScheduler(Scheduler):
+    """Scheduler that implements the as-late-as-possible (ALAP) algorithm."""
+
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        """Applies the scheduling algorithm on the given Schedule.
+
+        Parameters
+        ----------
+        schedule : Schedule
+            Schedule to apply the scheduling algorithm on.
+        """
+        ASAPScheduler().apply_scheduling(schedule)
+        max_end_time = schedule.get_max_end_time()
+
+        if schedule.schedule_time is None:
+            schedule.set_schedule_time(max_end_time)
+        elif schedule.schedule_time < max_end_time:
             raise ValueError(f"Too short schedule time. Minimum is {max_end_time}.")
 
         # move all outputs ALAP before operations
-        for output in sched.sfg.find_by_type_name(Output.type_name()):
+        for output in schedule.sfg.find_by_type_name(Output.type_name()):
             output = cast(Output, output)
-            sched.move_operation_alap(output.graph_id)
+            schedule.move_operation_alap(output.graph_id)
 
         # move all operations ALAP
-        for step in reversed(sched.sfg.get_precedence_list()):
+        for step in reversed(schedule.sfg.get_precedence_list()):
             for outport in step:
                 if not isinstance(outport.operation, Delay):
-                    sched.move_operation_alap(outport.operation.graph_id)
+                    schedule.move_operation_alap(outport.operation.graph_id)
 
-    def schedule_earliest_deadline(
-        self, process_elements: dict[Operation, int]
-    ) -> None:
-        """Schedule the operations using earliest deadline scheduling."""
 
-        # ACT BASED ON THE NUMBER OF PEs!
+class EarliestDeadlineScheduler(Scheduler):
+    """
+    Scheduler that implements the earliest-deadline-first algorithm.
 
-        sched = self.schedule
-        prec_list = sched.sfg.get_precedence_list()
-        if len(prec_list) < 2:
-            raise ValueError("Empty signal flow graph cannot be scheduled.")
+    Parameters
+    ----------
+    max_resources : dict, optional
+        Dictionary like ``{Addition.type_name(): 2}`` denoting the maximum number of
+        resources for a given operation type if the scheduling algorithm considers
+        that. If not provided, or an operation type is not provided, at most one
+        resource is used.
+    """
 
-        # handle the first set in precedence graph (input and delays)
-        non_schedulable_ops = set()
-        for outport in prec_list[0]:
-            operation = outport.operation
-            if operation.type_name() == Delay.type_name():
-                non_schedulable_ops.add(operation.graph_id)
-            elif operation.graph_id not in sched._start_times:
-                sched._start_times[operation.graph_id] = 0
+    def __init__(self, max_resources: Optional[dict[TypeName, int]] = None) -> None:
+        if max_resources:
+            self._max_resources = max_resources
+        else:
+            self._max_resources = {}
 
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        """Applies the scheduling algorithm on the given Schedule.
+
+        Parameters
+        ----------
+        schedule : Schedule
+            Schedule to apply the scheduling algorithm on.
+        """
+
+        ALAPScheduler().apply_scheduling(schedule)
+
+        # move all inputs ASAP to ensure correct operation
+        for input_op in schedule.sfg.find_by_type_name(Input.type_name()):
+            input_op = cast(Input, input_op)
+            schedule.move_operation_asap(input_op.graph_id)
+
+        # construct the set of remaining operations, excluding inputs
+        remaining_ops = list(schedule.start_times.keys())
+        remaining_ops = [elem for elem in remaining_ops if not elem.startswith("in")]
+
+        # construct a dictionarry for storing how many times until a resource is available again
+        used_resources_ready_times = {}
+
+        # iterate through all remaining operations and schedule them
+        # while not exceeding the available resources
+        remaining_resources = self._max_resources.copy()
         current_time = 0
-        sorted_outports = sorted(
-            prec_list[1], key=lambda outport: outport.operation.latency
-        )
-        for outport in sorted_outports:
-            op = outport.operation
-            sched._start_times[op.graph_id] = current_time
-            current_time += 1
+        while remaining_ops:
+            best_candidate = self._find_best_candidate(
+                schedule, remaining_ops, remaining_resources, current_time
+            )
 
-        for outports in prec_list[2:]:
-            # try all remaining operations for one time step
-            candidates = []
-            current_time -= 1
-            while len(candidates) == 0:
+            if not best_candidate:
                 current_time += 1
-                for outport in outports:
-                    remaining_op = outport.operation
-                    op_is_ready_to_be_scheduled = True
-                    for op_input in remaining_op.inputs:
-                        source_op = op_input.signals[0].source.operation
-                        source_op_time = sched.start_times[source_op.graph_id]
-                        source_end_time = source_op_time + source_op.latency
-                        if source_end_time > current_time:
-                            op_is_ready_to_be_scheduled = False
-                    if op_is_ready_to_be_scheduled:
-                        candidates.append(remaining_op)
-                        # sched._start_times[remaining_op.graph_id] = current_time
-            sorted_candidates = sorted(
-                candidates, key=lambda candidate: candidate.latency
-            )
-            # schedule the best candidate to current time
-            sched._start_times[sorted_candidates[0].graph_id] = current_time
 
-        self._handle_outputs_and_delays(non_schedulable_ops)
+                # update available operators
+                for operation, ready_time in used_resources_ready_times.items():
+                    if ready_time == current_time:
+                        remaining_resources[operation.type_name()] += 1
+                # remaining_resources = self._max_resources.copy()
+                continue
 
-    def _handle_outputs_and_delays(self, non_schedulable_ops) -> None:
-        sched = self.schedule
-        for output in sched._sfg.find_by_type_name(Output.type_name()):
-            output = cast(Output, output)
-            source_port = cast(OutputPort, output.inputs[0].signals[0].source)
-            if source_port.operation.graph_id in non_schedulable_ops:
-                sched._start_times[output.graph_id] = 0
-            else:
-                if source_port.latency_offset is None:
-                    raise ValueError(
-                        f"Output port {source_port.index} of operation"
-                        f" {source_port.operation.graph_id} has no"
-                        " latency-offset."
+            # if the resource is constrained, update remaining resources
+            if best_candidate.type_name() in remaining_resources:
+                remaining_resources[best_candidate.type_name()] -= 1
+                if best_candidate.execution_time:
+                    used_resources_ready_times[best_candidate] = (
+                        current_time + best_candidate.execution_time
                     )
-                sched._start_times[output.graph_id] = sched._start_times[
-                    source_port.operation.graph_id
-                ] + cast(int, source_port.latency_offset)
-        sched._remove_delays()
+                else:
+                    used_resources_ready_times[best_candidate] = (
+                        current_time + best_candidate.latency
+                    )
+
+            # schedule the best candidate to the current time
+            remaining_ops.remove(best_candidate.graph_id)
+            schedule.start_times[best_candidate.graph_id] = current_time
+
+        # move all inputs and outputs ALAP now that operations have moved
+        for input_op in schedule.sfg.find_by_type_name(Input.type_name()):
+            input_op = cast(Input, input_op)
+            schedule.move_operation_alap(input_op.graph_id)
+        self._handle_outputs(schedule)
+
+    @staticmethod
+    def _find_best_candidate(
+        schedule, remaining_ops, remaining_resources, current_time
+    ):
+        sfg = schedule.sfg
+        source_end_times = defaultdict(float)
+
+        # find the best candidate
+        best_candidate = None
+        best_deadline = float('inf')
+        for op_id in remaining_ops:
+            operation = sfg.find_by_id(op_id)
+
+            # compute maximum end times of preceding operations
+            for op_input in operation.inputs:
+                source_op = op_input.signals[0].source.operation
+                if not isinstance(source_op, Delay):
+                    source_end_times[op_id] = max(
+                        source_end_times[op_id],
+                        schedule.start_times[source_op.graph_id] + source_op.latency,
+                    )
+                    # ensure that the source is already scheduled
+                    if source_op.graph_id in remaining_ops:
+                        source_end_times[op_id] = sys.maxsize
+
+            # check resource constraints
+            if operation.type_name() in remaining_resources:
+                if remaining_resources[operation.type_name()] == 0:
+                    continue
+
+            # check if all inputs are available
+            if source_end_times[op_id] <= current_time:
+                operation_deadline = schedule.start_times[op_id] + operation.latency
+                if operation_deadline < best_deadline:
+                    best_candidate = operation
+                    best_deadline = operation_deadline
+
+        return best_candidate
diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py
index 2d9be528..2dc7b719 100644
--- a/b_asic/scheduler_gui/compile.py
+++ b/b_asic/scheduler_gui/compile.py
@@ -2,7 +2,7 @@
 """
 B-ASIC Scheduler-gui Resource and Form Compiler Module.
 
-Compile Qt5 resource and form files. Requires PySide2 or PyQt5 to be installed.
+Compile Qt6 resource and form files. Requires PySide6 or PyQt6 to be installed.
 If no arguments is given, the compiler search for and compiles all form (.ui)
 files.
 """
@@ -160,45 +160,13 @@ def compile_ui(*filenames: str) -> None:
         directory = directory if directory else "."
         outfile = f"{directory}/ui_{file}.py"
 
-        if uic.PYSIDE2:
-            uic_ = shutil.which("pyside2-uic")
-            arguments = f"-g python -o {outfile} {filename}"
-
-            if uic_ is None:
-                uic_ = shutil.which("uic")
-            if uic_ is None:
-                uic_ = shutil.which("pyuic5")
-                arguments = f"-o {outfile} {filename}"
-            assert uic_, (
-                "Qt User Interface Compiler failed, cannot find pyside2-uic,"
-                " uic, or pyuic5"
-            )
-
-            os_ = sys.platform
-            if os_.startswith("linux"):  # Linux
-                cmd = f"{uic_} {arguments}"
-                subprocess.call(cmd.split())
-
-            elif os_.startswith("win32"):  # Windows
-                # TODO: implement
-                log.error("Windows UI compiler not implemented")
-                raise NotImplementedError
-
-            elif os_.startswith("darwin"):  # macOS
-                # TODO: implement
-                log.error("macOS UI compiler not implemented")
-                raise NotImplementedError
-
-            else:  # other OS
-                log.error(f"{os_} UI compiler not supported")
-                raise NotImplementedError
-
-        elif uic.PYQT5 or uic.PYQT6:
+        if uic.PYQT6:
             from qtpy.uic import compileUi
 
             with open(outfile, "w") as ofile:
                 compileUi(filename, ofile)
-        elif uic.PYQT6:
+
+        elif uic.PYSIDE6:
             uic_ = shutil.which("pyside6-uic")
             arguments = f"-g python -o {outfile} {filename}"
 
@@ -218,9 +186,8 @@ def compile_ui(*filenames: str) -> None:
                 subprocess.call(cmd.split())
 
             elif os_.startswith("win32"):  # Windows
-                # TODO: implement
-                log.error("Windows UI compiler not implemented")
-                raise NotImplementedError
+                cmd = f"{uic_} {arguments}"
+                subprocess.call(cmd.split())
 
             elif os_.startswith("darwin"):  # macOS
                 # TODO: implement
diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index f4502f99..c15be267 100644
--- a/b_asic/scheduler_gui/main_window.py
+++ b/b_asic/scheduler_gui/main_window.py
@@ -98,9 +98,9 @@ if __debug__:
     log.debug(f"Qt version (compile time): {QtCore.__version__}")
     log.debug(f"QT_API:                    {QT_API}")
     if QT_API.lower().startswith("pyside"):
-        import PySide2
+        import PySide6
 
-        log.debug(f"PySide version:           {PySide2.__version__}")
+        log.debug(f"PySide version:           {PySide6.__version__}")
     if QT_API.lower().startswith("pyqt"):
         from qtpy.QtCore import PYQT_VERSION_STR
 
@@ -1689,17 +1689,19 @@ def start_scheduler(schedule: Optional[Schedule] = None) -> Optional[Schedule]:
     """
     if not QApplication.instance():
         app = QApplication(sys.argv)
-        # Enforce a light palette regardless of laptop theme
-        palette = QPalette()
-        palette.setColor(QPalette.ColorRole.Window, QtCore.Qt.white)
-        palette.setColor(QPalette.ColorRole.WindowText, QtCore.Qt.black)
-        palette.setColor(QPalette.ColorRole.ButtonText, QtCore.Qt.black)
-        palette.setColor(QPalette.ColorRole.Base, QtCore.Qt.white)
-        palette.setColor(QPalette.ColorRole.AlternateBase, QtCore.Qt.lightGray)
-        palette.setColor(QPalette.ColorRole.Text, QtCore.Qt.black)
-        app.setPalette(palette)
     else:
         app = QApplication.instance()
+
+    # Enforce a light palette regardless of laptop theme
+    palette = QPalette()
+    palette.setColor(QPalette.ColorRole.Window, QtCore.Qt.white)
+    palette.setColor(QPalette.ColorRole.WindowText, QtCore.Qt.black)
+    palette.setColor(QPalette.ColorRole.ButtonText, QtCore.Qt.black)
+    palette.setColor(QPalette.ColorRole.Base, QtCore.Qt.white)
+    palette.setColor(QPalette.ColorRole.AlternateBase, QtCore.Qt.lightGray)
+    palette.setColor(QPalette.ColorRole.Text, QtCore.Qt.black)
+    app.setPalette(palette)
+
     window = ScheduleMainWindow()
     if schedule:
         window.open(schedule)
diff --git a/b_asic/scheduler_gui/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py
index ad3fa898..829aeb12 100644
--- a/b_asic/scheduler_gui/ui_main_window.py
+++ b/b_asic/scheduler_gui/ui_main_window.py
@@ -1,277 +1,337 @@
-# Form implementation generated from reading ui file '.\B-ASIC\b_asic\scheduler_gui\main_window.ui'
-#
-# Created by: PyQt6 UI code generator 6.8.0
-#
-# WARNING: Any manual changes made to this file will be lost when pyuic6 is
-# run again.  Do not edit this file unless you know what you are doing.
+################################################################################
+## Form generated from reading UI file 'main_window.ui'
+##
+## Created by: Qt User Interface Compiler version 6.8.2
+##
+## WARNING! All changes made in this file will be lost when recompiling UI file!
+################################################################################
 
-
-from qtpy import QtCore, QtGui, QtWidgets
+from qtpy.QtCore import (
+    QCoreApplication,
+    QMetaObject,
+    QRect,
+    QSize,
+    Qt,
+)
+from qtpy.QtGui import (
+    QAction,
+    QBrush,
+    QColor,
+    QFont,
+    QIcon,
+    QPainter,
+)
+from qtpy.QtWidgets import (
+    QAbstractItemView,
+    QGraphicsView,
+    QHBoxLayout,
+    QMenu,
+    QMenuBar,
+    QSizePolicy,
+    QSplitter,
+    QStatusBar,
+    QTableWidget,
+    QTableWidgetItem,
+    QToolBar,
+    QWidget,
+)
 
 
 class Ui_MainWindow:
     def setupUi(self, MainWindow):
-        MainWindow.setObjectName("MainWindow")
+        if not MainWindow.objectName():
+            MainWindow.setObjectName("MainWindow")
         MainWindow.resize(800, 600)
-        sizePolicy = QtWidgets.QSizePolicy(
-            QtWidgets.QSizePolicy.Policy.Preferred,
-            QtWidgets.QSizePolicy.Policy.Preferred,
+        sizePolicy = QSizePolicy(
+            QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Preferred
         )
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
         MainWindow.setSizePolicy(sizePolicy)
-        icon = QtGui.QIcon()
-        icon.addPixmap(
-            QtGui.QPixmap(":/icons/basic/small_logo.png"),
-            QtGui.QIcon.Mode.Normal,
-            QtGui.QIcon.State.Off,
+        icon = QIcon()
+        icon.addFile(
+            ":/icons/basic/small_logo.png", QSize(), QIcon.Mode.Normal, QIcon.State.Off
         )
         MainWindow.setWindowIcon(icon)
-        self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
-        sizePolicy = QtWidgets.QSizePolicy(
-            QtWidgets.QSizePolicy.Policy.Preferred,
-            QtWidgets.QSizePolicy.Policy.Preferred,
-        )
-        sizePolicy.setHorizontalStretch(0)
-        sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(
-            self.centralwidget.sizePolicy().hasHeightForWidth()
-        )
-        self.centralwidget.setSizePolicy(sizePolicy)
-        self.centralwidget.setObjectName("centralwidget")
-        self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
-        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
-        self.horizontalLayout.setSpacing(0)
-        self.horizontalLayout.setObjectName("horizontalLayout")
-        self.splitter = QtWidgets.QSplitter(parent=self.centralwidget)
-        self.splitter.setOrientation(QtCore.Qt.Orientation.Horizontal)
-        self.splitter.setHandleWidth(0)
-        self.splitter.setObjectName("splitter")
-        self.view = QtWidgets.QGraphicsView(parent=self.splitter)
-        self.view.setAlignment(
-            QtCore.Qt.AlignmentFlag.AlignLeading
-            | QtCore.Qt.AlignmentFlag.AlignLeft
-            | QtCore.Qt.AlignmentFlag.AlignTop
-        )
-        self.view.setRenderHints(
-            QtGui.QPainter.RenderHint.Antialiasing
-            | QtGui.QPainter.RenderHint.TextAntialiasing
-        )
-        self.view.setViewportUpdateMode(
-            QtWidgets.QGraphicsView.ViewportUpdateMode.FullViewportUpdate
-        )
-        self.view.setObjectName("view")
-        self.info_table = QtWidgets.QTableWidget(parent=self.splitter)
-        self.info_table.setStyleSheet(
-            "alternate-background-color: #fadefb;background-color: #ebebeb;"
-        )
-        self.info_table.setEditTriggers(
-            QtWidgets.QAbstractItemView.EditTrigger.NoEditTriggers
-        )
-        self.info_table.setAlternatingRowColors(True)
-        self.info_table.setSelectionBehavior(
-            QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows
-        )
-        self.info_table.setRowCount(2)
-        self.info_table.setColumnCount(2)
-        self.info_table.setObjectName("info_table")
-        item = QtWidgets.QTableWidgetItem()
-        self.info_table.setVerticalHeaderItem(0, item)
-        item = QtWidgets.QTableWidgetItem()
-        self.info_table.setVerticalHeaderItem(1, item)
-        item = QtWidgets.QTableWidgetItem()
-        item.setTextAlignment(
-            QtCore.Qt.AlignmentFlag.AlignLeading | QtCore.Qt.AlignmentFlag.AlignVCenter
-        )
-        font = QtGui.QFont()
-        font.setBold(False)
-        font.setWeight(50)
-        item.setFont(font)
-        self.info_table.setHorizontalHeaderItem(0, item)
-        item = QtWidgets.QTableWidgetItem()
-        item.setTextAlignment(
-            QtCore.Qt.AlignmentFlag.AlignLeading | QtCore.Qt.AlignmentFlag.AlignVCenter
-        )
-        self.info_table.setHorizontalHeaderItem(1, item)
-        item = QtWidgets.QTableWidgetItem()
-        font = QtGui.QFont()
-        font.setBold(False)
-        font.setWeight(50)
-        font.setKerning(True)
-        item.setFont(font)
-        brush = QtGui.QBrush(QtGui.QColor(160, 160, 164))
-        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
-        item.setBackground(brush)
-        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
-        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
-        item.setForeground(brush)
-        item.setFlags(
-            QtCore.Qt.ItemFlag.ItemIsSelectable
-            | QtCore.Qt.ItemFlag.ItemIsEditable
-            | QtCore.Qt.ItemFlag.ItemIsDragEnabled
-            | QtCore.Qt.ItemFlag.ItemIsDropEnabled
-            | QtCore.Qt.ItemFlag.ItemIsUserCheckable
-        )
-        self.info_table.setItem(0, 0, item)
-        item = QtWidgets.QTableWidgetItem()
-        font = QtGui.QFont()
-        font.setBold(False)
-        font.setWeight(50)
-        item.setFont(font)
-        brush = QtGui.QBrush(QtGui.QColor(160, 160, 164))
-        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
-        item.setBackground(brush)
-        brush = QtGui.QBrush(QtGui.QColor(255, 255, 255))
-        brush.setStyle(QtCore.Qt.BrushStyle.SolidPattern)
-        item.setForeground(brush)
-        item.setFlags(
-            QtCore.Qt.ItemFlag.ItemIsSelectable
-            | QtCore.Qt.ItemFlag.ItemIsEditable
-            | QtCore.Qt.ItemFlag.ItemIsDragEnabled
-            | QtCore.Qt.ItemFlag.ItemIsDropEnabled
-            | QtCore.Qt.ItemFlag.ItemIsUserCheckable
-        )
-        self.info_table.setItem(1, 0, item)
-        self.info_table.horizontalHeader().setHighlightSections(False)
-        self.info_table.horizontalHeader().setStretchLastSection(True)
-        self.info_table.verticalHeader().setVisible(False)
-        self.info_table.verticalHeader().setDefaultSectionSize(24)
-        self.horizontalLayout.addWidget(self.splitter)
-        MainWindow.setCentralWidget(self.centralwidget)
-        self.menubar = QtWidgets.QMenuBar(parent=MainWindow)
-        self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 20))
-        self.menubar.setObjectName("menubar")
-        self.menuFile = QtWidgets.QMenu(parent=self.menubar)
-        self.menuFile.setObjectName("menuFile")
-        self.menu_Recent_Schedule = QtWidgets.QMenu(parent=self.menuFile)
-        self.menu_Recent_Schedule.setObjectName("menu_Recent_Schedule")
-        self.menuView = QtWidgets.QMenu(parent=self.menubar)
-        self.menuView.setObjectName("menuView")
-        self.menu_view_execution_times = QtWidgets.QMenu(parent=self.menuView)
-        self.menu_view_execution_times.setEnabled(False)
-        self.menu_view_execution_times.setObjectName("menu_view_execution_times")
-        self.menu_Edit = QtWidgets.QMenu(parent=self.menubar)
-        self.menu_Edit.setObjectName("menu_Edit")
-        self.menuWindow = QtWidgets.QMenu(parent=self.menubar)
-        self.menuWindow.setObjectName("menuWindow")
-        self.menuHelp = QtWidgets.QMenu(parent=self.menubar)
-        self.menuHelp.setObjectName("menuHelp")
-        MainWindow.setMenuBar(self.menubar)
-        self.statusbar = QtWidgets.QStatusBar(parent=MainWindow)
-        self.statusbar.setObjectName("statusbar")
-        MainWindow.setStatusBar(self.statusbar)
-        self.toolBar = QtWidgets.QToolBar(parent=MainWindow)
-        self.toolBar.setObjectName("toolBar")
-        MainWindow.addToolBar(QtCore.Qt.ToolBarArea.TopToolBarArea, self.toolBar)
-        self.menu_load_from_file = QtGui.QAction(parent=MainWindow)
-        icon = QtGui.QIcon.fromTheme("document-open-folder")
-        self.menu_load_from_file.setIcon(icon)
-        self.menu_load_from_file.setStatusTip("")
+        self.menu_load_from_file = QAction(MainWindow)
         self.menu_load_from_file.setObjectName("menu_load_from_file")
-        self.menu_save = QtGui.QAction(parent=MainWindow)
-        self.menu_save.setEnabled(False)
-        icon = QtGui.QIcon.fromTheme("document-save")
-        self.menu_save.setIcon(icon)
+        icon1 = QIcon()
+        iconThemeName = "document-open-folder"
+        if QIcon.hasThemeIcon(iconThemeName):
+            icon1 = QIcon.fromTheme(iconThemeName)
+        else:
+            icon1.addFile(
+                "../../../.designer/backup",
+                QSize(),
+                QIcon.Mode.Normal,
+                QIcon.State.Off,
+            )
+
+        self.menu_load_from_file.setIcon(icon1)
+        self.menu_save = QAction(MainWindow)
         self.menu_save.setObjectName("menu_save")
-        self.menu_node_info = QtGui.QAction(parent=MainWindow)
+        self.menu_save.setEnabled(False)
+        icon2 = QIcon()
+        iconThemeName = "document-save"
+        if QIcon.hasThemeIcon(iconThemeName):
+            icon2 = QIcon.fromTheme(iconThemeName)
+        else:
+            icon2.addFile(
+                "../../../.designer/backup",
+                QSize(),
+                QIcon.Mode.Normal,
+                QIcon.State.Off,
+            )
+
+        self.menu_save.setIcon(icon2)
+        self.menu_node_info = QAction(MainWindow)
+        self.menu_node_info.setObjectName("menu_node_info")
         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.Mode.Normal,
-            QtGui.QIcon.State.Off,
-        )
-        icon1.addPixmap(
-            QtGui.QPixmap(":/icons/misc/right_filled_panel.svg"),
-            QtGui.QIcon.Mode.Normal,
-            QtGui.QIcon.State.On,
-        )
-        self.menu_node_info.setIcon(icon1)
+        icon3 = QIcon()
+        icon3.addFile(
+            ":/icons/misc/right_panel.svg", QSize(), QIcon.Mode.Normal, QIcon.State.Off
+        )
+        icon3.addFile(
+            ":/icons/misc/right_filled_panel.svg",
+            QSize(),
+            QIcon.Mode.Normal,
+            QIcon.State.On,
+        )
+        self.menu_node_info.setIcon(icon3)
         self.menu_node_info.setIconVisibleInMenu(False)
-        self.menu_node_info.setObjectName("menu_node_info")
-        self.menu_quit = QtGui.QAction(parent=MainWindow)
-        icon = QtGui.QIcon.fromTheme("application-exit")
-        self.menu_quit.setIcon(icon)
+        self.menu_quit = QAction(MainWindow)
         self.menu_quit.setObjectName("menu_quit")
-        self.menu_save_as = QtGui.QAction(parent=MainWindow)
-        self.menu_save_as.setEnabled(False)
-        icon = QtGui.QIcon.fromTheme("document-save-as")
-        self.menu_save_as.setIcon(icon)
+        icon4 = QIcon()
+        iconThemeName = "application-exit"
+        if QIcon.hasThemeIcon(iconThemeName):
+            icon4 = QIcon.fromTheme(iconThemeName)
+        else:
+            icon4.addFile(
+                "../../../.designer/backup",
+                QSize(),
+                QIcon.Mode.Normal,
+                QIcon.State.Off,
+            )
+
+        self.menu_quit.setIcon(icon4)
+        self.menu_save_as = QAction(MainWindow)
         self.menu_save_as.setObjectName("menu_save_as")
-        self.menu_exit_dialog = QtGui.QAction(parent=MainWindow)
+        self.menu_save_as.setEnabled(False)
+        icon5 = QIcon()
+        iconThemeName = "document-save-as"
+        if QIcon.hasThemeIcon(iconThemeName):
+            icon5 = QIcon.fromTheme(iconThemeName)
+        else:
+            icon5.addFile(
+                "../../../.designer/backup",
+                QSize(),
+                QIcon.Mode.Normal,
+                QIcon.State.Off,
+            )
+
+        self.menu_save_as.setIcon(icon5)
+        self.menu_exit_dialog = QAction(MainWindow)
+        self.menu_exit_dialog.setObjectName("menu_exit_dialog")
         self.menu_exit_dialog.setCheckable(True)
         self.menu_exit_dialog.setChecked(True)
-        icon = QtGui.QIcon.fromTheme("view-close")
-        self.menu_exit_dialog.setIcon(icon)
-        self.menu_exit_dialog.setObjectName("menu_exit_dialog")
-        self.menu_close_schedule = QtGui.QAction(parent=MainWindow)
-        self.menu_close_schedule.setEnabled(False)
-        icon = QtGui.QIcon.fromTheme("view-close")
-        self.menu_close_schedule.setIcon(icon)
+        icon6 = QIcon()
+        iconThemeName = "view-close"
+        if QIcon.hasThemeIcon(iconThemeName):
+            icon6 = QIcon.fromTheme(iconThemeName)
+        else:
+            icon6.addFile(
+                "../../../.designer/backup",
+                QSize(),
+                QIcon.Mode.Normal,
+                QIcon.State.Off,
+            )
+
+        self.menu_exit_dialog.setIcon(icon6)
+        self.menu_close_schedule = QAction(MainWindow)
         self.menu_close_schedule.setObjectName("menu_close_schedule")
-        self.actionAbout = QtGui.QAction(parent=MainWindow)
+        self.menu_close_schedule.setEnabled(False)
+        self.menu_close_schedule.setIcon(icon6)
+        self.actionAbout = QAction(MainWindow)
         self.actionAbout.setObjectName("actionAbout")
-        self.actionDocumentation = QtGui.QAction(parent=MainWindow)
+        self.actionDocumentation = QAction(MainWindow)
         self.actionDocumentation.setObjectName("actionDocumentation")
-        self.actionReorder = QtGui.QAction(parent=MainWindow)
+        self.actionReorder = QAction(MainWindow)
         self.actionReorder.setObjectName("actionReorder")
-        self.actionPlot_schedule = QtGui.QAction(parent=MainWindow)
+        self.actionPlot_schedule = QAction(MainWindow)
         self.actionPlot_schedule.setObjectName("actionPlot_schedule")
-        self.action_view_variables = QtGui.QAction(parent=MainWindow)
-        self.action_view_variables.setEnabled(False)
+        self.action_view_variables = QAction(MainWindow)
         self.action_view_variables.setObjectName("action_view_variables")
-        self.action_view_port_accesses = QtGui.QAction(parent=MainWindow)
-        self.action_view_port_accesses.setEnabled(False)
+        self.action_view_variables.setEnabled(False)
+        self.action_view_port_accesses = QAction(MainWindow)
         self.action_view_port_accesses.setObjectName("action_view_port_accesses")
-        self.actionUndo = QtGui.QAction(parent=MainWindow)
-        self.actionUndo.setEnabled(False)
+        self.action_view_port_accesses.setEnabled(False)
+        self.actionUndo = QAction(MainWindow)
         self.actionUndo.setObjectName("actionUndo")
-        self.actionRedo = QtGui.QAction(parent=MainWindow)
-        self.actionRedo.setEnabled(False)
+        self.actionUndo.setEnabled(False)
+        self.actionRedo = QAction(MainWindow)
         self.actionRedo.setObjectName("actionRedo")
-        self.actionIncrease_time_resolution = QtGui.QAction(parent=MainWindow)
+        self.actionRedo.setEnabled(False)
+        self.actionIncrease_time_resolution = QAction(MainWindow)
         self.actionIncrease_time_resolution.setObjectName(
             "actionIncrease_time_resolution"
         )
-        self.actionDecrease_time_resolution = QtGui.QAction(parent=MainWindow)
+        self.actionDecrease_time_resolution = QAction(MainWindow)
         self.actionDecrease_time_resolution.setObjectName(
             "actionDecrease_time_resolution"
         )
-        self.actionZoom_to_fit = QtGui.QAction(parent=MainWindow)
+        self.actionZoom_to_fit = QAction(MainWindow)
         self.actionZoom_to_fit.setObjectName("actionZoom_to_fit")
-        self.actionStatus_bar = QtGui.QAction(parent=MainWindow)
+        self.actionStatus_bar = QAction(MainWindow)
+        self.actionStatus_bar.setObjectName("actionStatus_bar")
         self.actionStatus_bar.setCheckable(True)
         self.actionStatus_bar.setChecked(True)
-        self.actionStatus_bar.setObjectName("actionStatus_bar")
-        self.actionToolbar = QtGui.QAction(parent=MainWindow)
+        self.actionToolbar = QAction(MainWindow)
+        self.actionToolbar.setObjectName("actionToolbar")
         self.actionToolbar.setCheckable(True)
         self.actionToolbar.setChecked(True)
-        self.actionToolbar.setObjectName("actionToolbar")
-        self.action_show_port_numbers = QtGui.QAction(parent=MainWindow)
+        self.action_show_port_numbers = QAction(MainWindow)
+        self.action_show_port_numbers.setObjectName("action_show_port_numbers")
         self.action_show_port_numbers.setCheckable(True)
         self.action_show_port_numbers.setChecked(False)
         self.action_show_port_numbers.setIconVisibleInMenu(False)
-        self.action_show_port_numbers.setObjectName("action_show_port_numbers")
-        self.action_incorrect_execution_time = QtGui.QAction(parent=MainWindow)
-        self.action_incorrect_execution_time.setCheckable(True)
-        self.action_incorrect_execution_time.setChecked(True)
-        self.action_incorrect_execution_time.setIconVisibleInMenu(False)
+        self.action_incorrect_execution_time = QAction(MainWindow)
         self.action_incorrect_execution_time.setObjectName(
             "action_incorrect_execution_time"
         )
-        self.menu_open = QtGui.QAction(parent=MainWindow)
-        icon = QtGui.QIcon.fromTheme("personal")
-        self.menu_open.setIcon(icon)
+        self.action_incorrect_execution_time.setCheckable(True)
+        self.action_incorrect_execution_time.setChecked(True)
+        self.action_incorrect_execution_time.setIconVisibleInMenu(False)
+        self.menu_open = QAction(MainWindow)
         self.menu_open.setObjectName("menu_open")
-        self.actionToggle_full_screen = QtGui.QAction(parent=MainWindow)
-        self.actionToggle_full_screen.setCheckable(True)
+        icon7 = QIcon(QIcon.fromTheme("personal"))
+        self.menu_open.setIcon(icon7)
+        self.actionToggle_full_screen = QAction(MainWindow)
         self.actionToggle_full_screen.setObjectName("actionToggle_full_screen")
-        self.actionPreferences = QtGui.QAction(parent=MainWindow)
-        icon = QtGui.QIcon.fromTheme("preferences-desktop-personal")
-        self.actionPreferences.setIcon(icon)
+        self.actionToggle_full_screen.setCheckable(True)
+        self.actionPreferences = QAction(MainWindow)
         self.actionPreferences.setObjectName("actionPreferences")
+        icon8 = QIcon(QIcon.fromTheme("preferences-desktop-personal"))
+        self.actionPreferences.setIcon(icon8)
+        self.centralwidget = QWidget(MainWindow)
+        self.centralwidget.setObjectName("centralwidget")
+        sizePolicy.setHeightForWidth(
+            self.centralwidget.sizePolicy().hasHeightForWidth()
+        )
+        self.centralwidget.setSizePolicy(sizePolicy)
+        self.horizontalLayout = QHBoxLayout(self.centralwidget)
+        self.horizontalLayout.setSpacing(0)
+        self.horizontalLayout.setObjectName("horizontalLayout")
+        self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
+        self.splitter = QSplitter(self.centralwidget)
+        self.splitter.setObjectName("splitter")
+        self.splitter.setOrientation(Qt.Horizontal)
+        self.splitter.setHandleWidth(0)
+        self.view = QGraphicsView(self.splitter)
+        self.view.setObjectName("view")
+        self.view.setAlignment(Qt.AlignLeading | Qt.AlignLeft | Qt.AlignTop)
+        self.view.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing)
+        self.view.setViewportUpdateMode(QGraphicsView.FullViewportUpdate)
+        self.splitter.addWidget(self.view)
+        self.info_table = QTableWidget(self.splitter)
+        if self.info_table.columnCount() < 2:
+            self.info_table.setColumnCount(2)
+        font = QFont()
+        font.setBold(False)
+        __qtablewidgetitem = QTableWidgetItem()
+        __qtablewidgetitem.setTextAlignment(Qt.AlignLeading | Qt.AlignVCenter)
+        __qtablewidgetitem.setFont(font)
+        self.info_table.setHorizontalHeaderItem(0, __qtablewidgetitem)
+        __qtablewidgetitem1 = QTableWidgetItem()
+        __qtablewidgetitem1.setTextAlignment(Qt.AlignLeading | Qt.AlignVCenter)
+        self.info_table.setHorizontalHeaderItem(1, __qtablewidgetitem1)
+        if self.info_table.rowCount() < 2:
+            self.info_table.setRowCount(2)
+        __qtablewidgetitem2 = QTableWidgetItem()
+        self.info_table.setVerticalHeaderItem(0, __qtablewidgetitem2)
+        __qtablewidgetitem3 = QTableWidgetItem()
+        self.info_table.setVerticalHeaderItem(1, __qtablewidgetitem3)
+        brush = QBrush(QColor(255, 255, 255, 255))
+        brush.setStyle(Qt.SolidPattern)
+        brush1 = QBrush(QColor(160, 160, 164, 255))
+        brush1.setStyle(Qt.SolidPattern)
+        font1 = QFont()
+        font1.setBold(False)
+        font1.setKerning(True)
+        __qtablewidgetitem4 = QTableWidgetItem()
+        __qtablewidgetitem4.setFont(font1)
+        __qtablewidgetitem4.setBackground(brush1)
+        __qtablewidgetitem4.setForeground(brush)
+        __qtablewidgetitem4.setFlags(
+            Qt.ItemIsSelectable
+            | Qt.ItemIsEditable
+            | Qt.ItemIsDragEnabled
+            | Qt.ItemIsDropEnabled
+            | Qt.ItemIsUserCheckable
+        )
+        self.info_table.setItem(0, 0, __qtablewidgetitem4)
+        __qtablewidgetitem5 = QTableWidgetItem()
+        __qtablewidgetitem5.setFont(font)
+        __qtablewidgetitem5.setBackground(brush1)
+        __qtablewidgetitem5.setForeground(brush)
+        __qtablewidgetitem5.setFlags(
+            Qt.ItemIsSelectable
+            | Qt.ItemIsEditable
+            | Qt.ItemIsDragEnabled
+            | Qt.ItemIsDropEnabled
+            | Qt.ItemIsUserCheckable
+        )
+        self.info_table.setItem(1, 0, __qtablewidgetitem5)
+        self.info_table.setObjectName("info_table")
+        self.info_table.setStyleSheet(
+            "alternate-background-color: #fadefb;background-color: #ebebeb;"
+        )
+        self.info_table.setEditTriggers(QAbstractItemView.NoEditTriggers)
+        self.info_table.setAlternatingRowColors(True)
+        self.info_table.setSelectionBehavior(QAbstractItemView.SelectRows)
+        self.info_table.setRowCount(2)
+        self.info_table.setColumnCount(2)
+        self.splitter.addWidget(self.info_table)
+        self.info_table.horizontalHeader().setHighlightSections(False)
+        self.info_table.horizontalHeader().setStretchLastSection(True)
+        self.info_table.verticalHeader().setVisible(False)
+        self.info_table.verticalHeader().setDefaultSectionSize(24)
+
+        self.horizontalLayout.addWidget(self.splitter)
+
+        MainWindow.setCentralWidget(self.centralwidget)
+        self.menubar = QMenuBar(MainWindow)
+        self.menubar.setObjectName("menubar")
+        self.menubar.setGeometry(QRect(0, 0, 800, 20))
+        self.menuFile = QMenu(self.menubar)
+        self.menuFile.setObjectName("menuFile")
+        self.menu_Recent_Schedule = QMenu(self.menuFile)
+        self.menu_Recent_Schedule.setObjectName("menu_Recent_Schedule")
+        self.menuView = QMenu(self.menubar)
+        self.menuView.setObjectName("menuView")
+        self.menu_view_execution_times = QMenu(self.menuView)
+        self.menu_view_execution_times.setObjectName("menu_view_execution_times")
+        self.menu_view_execution_times.setEnabled(False)
+        self.menu_Edit = QMenu(self.menubar)
+        self.menu_Edit.setObjectName("menu_Edit")
+        self.menuWindow = QMenu(self.menubar)
+        self.menuWindow.setObjectName("menuWindow")
+        self.menuHelp = QMenu(self.menubar)
+        self.menuHelp.setObjectName("menuHelp")
+        MainWindow.setMenuBar(self.menubar)
+        self.statusbar = QStatusBar(MainWindow)
+        self.statusbar.setObjectName("statusbar")
+        MainWindow.setStatusBar(self.statusbar)
+        self.toolBar = QToolBar(MainWindow)
+        self.toolBar.setObjectName("toolBar")
+        MainWindow.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.toolBar)
+
+        self.menubar.addAction(self.menuFile.menuAction())
+        self.menubar.addAction(self.menu_Edit.menuAction())
+        self.menubar.addAction(self.menuView.menuAction())
+        self.menubar.addAction(self.menuWindow.menuAction())
+        self.menubar.addAction(self.menuHelp.menuAction())
         self.menuFile.addAction(self.menu_open)
         self.menuFile.addAction(self.menu_Recent_Schedule.menuAction())
         self.menuFile.addAction(self.menu_load_from_file)
@@ -306,11 +366,6 @@ class Ui_MainWindow:
         self.menuHelp.addAction(self.actionDocumentation)
         self.menuHelp.addSeparator()
         self.menuHelp.addAction(self.actionAbout)
-        self.menubar.addAction(self.menuFile.menuAction())
-        self.menubar.addAction(self.menu_Edit.menuAction())
-        self.menubar.addAction(self.menuView.menuAction())
-        self.menubar.addAction(self.menuWindow.menuAction())
-        self.menubar.addAction(self.menuHelp.menuAction())
         self.toolBar.addAction(self.menu_open)
         self.toolBar.addAction(self.menu_save)
         self.toolBar.addAction(self.menu_save_as)
@@ -324,127 +379,300 @@ class Ui_MainWindow:
         self.toolBar.addAction(self.actionReorder)
 
         self.retranslateUi(MainWindow)
-        QtCore.QMetaObject.connectSlotsByName(MainWindow)
+
+        QMetaObject.connectSlotsByName(MainWindow)
+
+    # setupUi
 
     def retranslateUi(self, MainWindow):
-        _translate = QtCore.QCoreApplication.translate
-        item = self.info_table.verticalHeaderItem(0)
-        item.setText(_translate("MainWindow", "1"))
-        item = self.info_table.verticalHeaderItem(1)
-        item.setText(_translate("MainWindow", "2"))
-        item = self.info_table.horizontalHeaderItem(0)
-        item.setText(_translate("MainWindow", "Property"))
-        item = self.info_table.horizontalHeaderItem(1)
-        item.setText(_translate("MainWindow", "Value"))
-        __sortingEnabled = self.info_table.isSortingEnabled()
-        self.info_table.setSortingEnabled(False)
-        item = self.info_table.item(0, 0)
-        item.setText(_translate("MainWindow", "Schedule"))
-        item = self.info_table.item(1, 0)
-        item.setText(_translate("MainWindow", "Operator"))
-        self.info_table.setSortingEnabled(__sortingEnabled)
-        self.menuFile.setTitle(_translate("MainWindow", "&File"))
-        self.menu_Recent_Schedule.setTitle(_translate("MainWindow", "Open &recent"))
-        self.menuView.setTitle(_translate("MainWindow", "&View"))
-        self.menu_view_execution_times.setTitle(
-            _translate("MainWindow", "View execution times of type")
-        )
-        self.menu_Edit.setTitle(_translate("MainWindow", "&Edit"))
-        self.menuWindow.setTitle(_translate("MainWindow", "&Window"))
-        self.menuHelp.setTitle(_translate("MainWindow", "&Help"))
-        self.toolBar.setWindowTitle(_translate("MainWindow", "toolBar"))
         self.menu_load_from_file.setText(
-            _translate("MainWindow", "&Import schedule from file...")
+            QCoreApplication.translate(
+                "MainWindow", "&Import schedule from file...", None
+            )
         )
+        # if QT_CONFIG(tooltip)
         self.menu_load_from_file.setToolTip(
-            _translate("MainWindow", "Import schedule from python script")
+            QCoreApplication.translate(
+                "MainWindow", "Import schedule from python script", None
+            )
         )
-        self.menu_load_from_file.setShortcut(_translate("MainWindow", "Ctrl+I"))
-        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"))
+        # endif // QT_CONFIG(tooltip)
+        # if QT_CONFIG(statustip)
+        self.menu_load_from_file.setStatusTip("")
+        # endif // QT_CONFIG(statustip)
+        # if QT_CONFIG(shortcut)
+        self.menu_load_from_file.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+I", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.menu_save.setText(QCoreApplication.translate("MainWindow", "&Save", None))
+        # if QT_CONFIG(tooltip)
+        self.menu_save.setToolTip(
+            QCoreApplication.translate("MainWindow", "Save schedule", None)
+        )
+        # endif // QT_CONFIG(tooltip)
+        # if QT_CONFIG(shortcut)
+        self.menu_save.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+S", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.menu_node_info.setText(
+            QCoreApplication.translate("MainWindow", "&Node info", None)
+        )
+        # if QT_CONFIG(tooltip)
         self.menu_node_info.setToolTip(
-            _translate("MainWindow", "Show/hide node information")
+            QCoreApplication.translate("MainWindow", "Show/hide node information", None)
+        )
+        # endif // QT_CONFIG(tooltip)
+        # if QT_CONFIG(shortcut)
+        self.menu_node_info.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+N", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.menu_quit.setText(QCoreApplication.translate("MainWindow", "&Quit", None))
+        # if QT_CONFIG(shortcut)
+        self.menu_quit.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+Q", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.menu_save_as.setText(
+            QCoreApplication.translate("MainWindow", "Save &as...", None)
         )
-        self.menu_node_info.setShortcut(_translate("MainWindow", "Ctrl+N"))
-        self.menu_quit.setText(_translate("MainWindow", "&Quit"))
-        self.menu_quit.setShortcut(_translate("MainWindow", "Ctrl+Q"))
-        self.menu_save_as.setText(_translate("MainWindow", "Save &as..."))
+        # if QT_CONFIG(tooltip)
         self.menu_save_as.setToolTip(
-            _translate("MainWindow", "Save schedule with new file name")
-        )
-        self.menu_save_as.setShortcut(_translate("MainWindow", "Ctrl+Shift+S"))
-        self.menu_exit_dialog.setText(_translate("MainWindow", "&Hide exit dialog"))
-        self.menu_exit_dialog.setToolTip(_translate("MainWindow", "Hide exit dialog"))
-        self.menu_close_schedule.setText(_translate("MainWindow", "&Close schedule"))
-        self.menu_close_schedule.setShortcut(_translate("MainWindow", "Ctrl+W"))
-        self.actionAbout.setText(_translate("MainWindow", "&About"))
-        self.actionAbout.setToolTip(_translate("MainWindow", "Open about window"))
-        self.actionDocumentation.setText(_translate("MainWindow", "&Documentation"))
+            QCoreApplication.translate(
+                "MainWindow", "Save schedule with new file name", None
+            )
+        )
+        # endif // QT_CONFIG(tooltip)
+        # if QT_CONFIG(shortcut)
+        self.menu_save_as.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+Shift+S", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.menu_exit_dialog.setText(
+            QCoreApplication.translate("MainWindow", "&Hide exit dialog", None)
+        )
+        # if QT_CONFIG(tooltip)
+        self.menu_exit_dialog.setToolTip(
+            QCoreApplication.translate("MainWindow", "Hide exit dialog", None)
+        )
+        # endif // QT_CONFIG(tooltip)
+        self.menu_close_schedule.setText(
+            QCoreApplication.translate("MainWindow", "&Close schedule", None)
+        )
+        # if QT_CONFIG(shortcut)
+        self.menu_close_schedule.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+W", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.actionAbout.setText(
+            QCoreApplication.translate("MainWindow", "&About", None)
+        )
+        # if QT_CONFIG(tooltip)
+        self.actionAbout.setToolTip(
+            QCoreApplication.translate("MainWindow", "Open about window", None)
+        )
+        # endif // QT_CONFIG(tooltip)
+        self.actionDocumentation.setText(
+            QCoreApplication.translate("MainWindow", "&Documentation", None)
+        )
+        # if QT_CONFIG(tooltip)
         self.actionDocumentation.setToolTip(
-            _translate("MainWindow", "Open documentation")
+            QCoreApplication.translate("MainWindow", "Open documentation", None)
         )
-        self.actionReorder.setText(_translate("MainWindow", "Reorder"))
+        # endif // QT_CONFIG(tooltip)
+        self.actionReorder.setText(
+            QCoreApplication.translate("MainWindow", "Reorder", None)
+        )
+        # if QT_CONFIG(tooltip)
         self.actionReorder.setToolTip(
-            _translate("MainWindow", "Reorder schedule based on start time")
+            QCoreApplication.translate(
+                "MainWindow", "Reorder schedule based on start time", None
+            )
+        )
+        # endif // QT_CONFIG(tooltip)
+        # if QT_CONFIG(shortcut)
+        self.actionReorder.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+R", None)
         )
-        self.actionReorder.setShortcut(_translate("MainWindow", "Ctrl+R"))
-        self.actionPlot_schedule.setText(_translate("MainWindow", "&Plot schedule"))
-        self.actionPlot_schedule.setToolTip(_translate("MainWindow", "Plot schedule"))
+        # endif // QT_CONFIG(shortcut)
+        self.actionPlot_schedule.setText(
+            QCoreApplication.translate("MainWindow", "&Plot schedule", None)
+        )
+        # if QT_CONFIG(tooltip)
+        self.actionPlot_schedule.setToolTip(
+            QCoreApplication.translate("MainWindow", "Plot schedule", None)
+        )
+        # endif // QT_CONFIG(tooltip)
         self.action_view_variables.setText(
-            _translate("MainWindow", "View execution times of variables")
+            QCoreApplication.translate(
+                "MainWindow", "View execution times of variables", None
+            )
         )
+        # if QT_CONFIG(tooltip)
         self.action_view_variables.setToolTip(
-            _translate("MainWindow", "View all variables")
+            QCoreApplication.translate("MainWindow", "View all variables", None)
         )
+        # endif // QT_CONFIG(tooltip)
         self.action_view_port_accesses.setText(
-            _translate("MainWindow", "View port access statistics")
+            QCoreApplication.translate(
+                "MainWindow", "View port access statistics", None
+            )
         )
+        # if QT_CONFIG(tooltip)
         self.action_view_port_accesses.setToolTip(
-            _translate("MainWindow", "View port access statistics for storage")
+            QCoreApplication.translate(
+                "MainWindow", "View port access statistics for storage", None
+            )
+        )
+        # endif // QT_CONFIG(tooltip)
+        self.actionUndo.setText(QCoreApplication.translate("MainWindow", "Undo", None))
+        # if QT_CONFIG(shortcut)
+        self.actionUndo.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+Z", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.actionRedo.setText(QCoreApplication.translate("MainWindow", "Redo", None))
+        # if QT_CONFIG(shortcut)
+        self.actionRedo.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+Y, Ctrl+Shift+Z", None)
         )
-        self.actionUndo.setText(_translate("MainWindow", "Undo"))
-        self.actionUndo.setShortcut(_translate("MainWindow", "Ctrl+Z"))
-        self.actionRedo.setText(_translate("MainWindow", "Redo"))
-        self.actionRedo.setShortcut(_translate("MainWindow", "Ctrl+Y, Ctrl+Shift+Z"))
+        # endif // QT_CONFIG(shortcut)
         self.actionIncrease_time_resolution.setText(
-            _translate("MainWindow", "Increase time resolution...")
+            QCoreApplication.translate(
+                "MainWindow", "Increase time resolution...", None
+            )
         )
         self.actionDecrease_time_resolution.setText(
-            _translate("MainWindow", "Decrease time resolution...")
+            QCoreApplication.translate(
+                "MainWindow", "Decrease time resolution...", None
+            )
+        )
+        self.actionZoom_to_fit.setText(
+            QCoreApplication.translate("MainWindow", "Zoom to &fit", None)
         )
-        self.actionZoom_to_fit.setText(_translate("MainWindow", "Zoom to &fit"))
-        self.actionStatus_bar.setText(_translate("MainWindow", "&Status bar"))
+        self.actionStatus_bar.setText(
+            QCoreApplication.translate("MainWindow", "&Status bar", None)
+        )
+        # if QT_CONFIG(tooltip)
         self.actionStatus_bar.setToolTip(
-            _translate("MainWindow", "Show/hide status bar")
+            QCoreApplication.translate("MainWindow", "Show/hide status bar", None)
+        )
+        # endif // QT_CONFIG(tooltip)
+        self.actionToolbar.setText(
+            QCoreApplication.translate("MainWindow", "&Toolbar", None)
+        )
+        # if QT_CONFIG(tooltip)
+        self.actionToolbar.setToolTip(
+            QCoreApplication.translate("MainWindow", "Show/hide toolbar", None)
         )
-        self.actionToolbar.setText(_translate("MainWindow", "&Toolbar"))
-        self.actionToolbar.setToolTip(_translate("MainWindow", "Show/hide toolbar"))
+        # endif // QT_CONFIG(tooltip)
         self.action_show_port_numbers.setText(
-            _translate("MainWindow", "S&how port numbers")
+            QCoreApplication.translate("MainWindow", "S&how port numbers", None)
         )
+        # if QT_CONFIG(tooltip)
         self.action_show_port_numbers.setToolTip(
-            _translate("MainWindow", "Show port numbers of operation")
+            QCoreApplication.translate(
+                "MainWindow", "Show port numbers of operation", None
+            )
         )
+        # endif // QT_CONFIG(tooltip)
         self.action_incorrect_execution_time.setText(
-            _translate("MainWindow", "&Incorrect execution time")
+            QCoreApplication.translate("MainWindow", "&Incorrect execution time", None)
         )
+        # if QT_CONFIG(tooltip)
         self.action_incorrect_execution_time.setToolTip(
-            _translate(
+            QCoreApplication.translate(
                 "MainWindow",
                 "Highlight processes with execution time longer than schedule time",
+                None,
             )
         )
-        self.menu_open.setText(_translate("MainWindow", "&Open..."))
+        # endif // QT_CONFIG(tooltip)
+        self.menu_open.setText(
+            QCoreApplication.translate("MainWindow", "&Open...", None)
+        )
+        # if QT_CONFIG(tooltip)
         self.menu_open.setToolTip(
-            _translate("MainWindow", "Open previously saved schedule")
+            QCoreApplication.translate(
+                "MainWindow", "Open previously saved schedule", None
+            )
+        )
+        # endif // QT_CONFIG(tooltip)
+        # if QT_CONFIG(shortcut)
+        self.menu_open.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+O", None)
         )
-        self.menu_open.setShortcut(_translate("MainWindow", "Ctrl+O"))
+        # endif // QT_CONFIG(shortcut)
         self.actionToggle_full_screen.setText(
-            _translate("MainWindow", "Toggle f&ull screen")
+            QCoreApplication.translate("MainWindow", "Toggle f&ull screen", None)
+        )
+        # if QT_CONFIG(shortcut)
+        self.actionToggle_full_screen.setShortcut(
+            QCoreApplication.translate("MainWindow", "F11", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        self.actionPreferences.setText(
+            QCoreApplication.translate("MainWindow", "Preferences", None)
+        )
+        # if QT_CONFIG(tooltip)
+        self.actionPreferences.setToolTip(
+            QCoreApplication.translate("MainWindow", "Color and Fonts", None)
+        )
+        # endif // QT_CONFIG(tooltip)
+        # if QT_CONFIG(shortcut)
+        self.actionPreferences.setShortcut(
+            QCoreApplication.translate("MainWindow", "Ctrl+M", None)
+        )
+        # endif // QT_CONFIG(shortcut)
+        ___qtablewidgetitem = self.info_table.horizontalHeaderItem(0)
+        ___qtablewidgetitem.setText(
+            QCoreApplication.translate("MainWindow", "Property", None)
+        )
+        ___qtablewidgetitem1 = self.info_table.horizontalHeaderItem(1)
+        ___qtablewidgetitem1.setText(
+            QCoreApplication.translate("MainWindow", "Value", None)
+        )
+        ___qtablewidgetitem2 = self.info_table.verticalHeaderItem(0)
+        ___qtablewidgetitem2.setText(
+            QCoreApplication.translate("MainWindow", "1", None)
+        )
+        ___qtablewidgetitem3 = self.info_table.verticalHeaderItem(1)
+        ___qtablewidgetitem3.setText(
+            QCoreApplication.translate("MainWindow", "2", None)
+        )
+
+        __sortingEnabled = self.info_table.isSortingEnabled()
+        self.info_table.setSortingEnabled(False)
+        ___qtablewidgetitem4 = self.info_table.item(0, 0)
+        ___qtablewidgetitem4.setText(
+            QCoreApplication.translate("MainWindow", "Schedule", None)
+        )
+        ___qtablewidgetitem5 = self.info_table.item(1, 0)
+        ___qtablewidgetitem5.setText(
+            QCoreApplication.translate("MainWindow", "Operator", None)
+        )
+        self.info_table.setSortingEnabled(__sortingEnabled)
+
+        self.menuFile.setTitle(QCoreApplication.translate("MainWindow", "&File", None))
+        self.menu_Recent_Schedule.setTitle(
+            QCoreApplication.translate("MainWindow", "Open &recent", None)
         )
-        self.actionToggle_full_screen.setShortcut(_translate("MainWindow", "F11"))
-        self.actionPreferences.setText(_translate("MainWindow", "Preferences"))
-        self.actionPreferences.setToolTip(_translate("MainWindow", "Color and Fonts"))
-        self.actionPreferences.setShortcut(_translate("MainWindow", "Ctrl+M"))
+        self.menuView.setTitle(QCoreApplication.translate("MainWindow", "&View", None))
+        self.menu_view_execution_times.setTitle(
+            QCoreApplication.translate(
+                "MainWindow", "View execution times of type", None
+            )
+        )
+        self.menu_Edit.setTitle(QCoreApplication.translate("MainWindow", "&Edit", None))
+        self.menuWindow.setTitle(
+            QCoreApplication.translate("MainWindow", "&Window", None)
+        )
+        self.menuHelp.setTitle(QCoreApplication.translate("MainWindow", "&Help", None))
+        self.toolBar.setWindowTitle(
+            QCoreApplication.translate("MainWindow", "toolBar", None)
+        )
+        pass
+
+    # retranslateUi
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index d3600a68..c1ff7ee6 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -38,6 +38,7 @@ from b_asic.operation import (
     ResultKey,
 )
 from b_asic.port import InputPort, OutputPort, SignalSourceProvider
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal import Signal
 from b_asic.special_operations import Delay, Input, Output
 from b_asic.types import GraphID, GraphIDNumber, Name, Num, TypeName
@@ -1709,7 +1710,7 @@ class SFG(AbstractOperation):
         # Import here needed to avoid circular imports
         from b_asic.schedule import Schedule
 
-        return Schedule(self, algorithm="ASAP").schedule_time
+        return Schedule(self, ASAPScheduler()).schedule_time
 
     def _dfs(self, graph, start, end):
         """
diff --git a/examples/fivepointwinograddft.py b/examples/fivepointwinograddft.py
index f9afd0e6..d2156151 100644
--- a/examples/fivepointwinograddft.py
+++ b/examples/fivepointwinograddft.py
@@ -14,6 +14,7 @@ import networkx as nx
 from b_asic.architecture import Architecture, Memory, ProcessingElement
 from b_asic.core_operations import AddSub, Butterfly, ConstantMultiplication
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Input, Output
 
@@ -75,7 +76,7 @@ sfg.set_execution_time_of_type(Butterfly.type_name(), 1)
 
 # %%
 # Generate schedule
-schedule = Schedule(sfg, cyclic=True)
+schedule = Schedule(sfg, scheduler=ASAPScheduler(), cyclic=True)
 schedule.show()
 
 # %%
diff --git a/examples/folding_example_with_architecture.py b/examples/folding_example_with_architecture.py
index 9baf92e0..43bdc798 100644
--- a/examples/folding_example_with_architecture.py
+++ b/examples/folding_example_with_architecture.py
@@ -17,6 +17,7 @@ shorter than the scheduling period.
 from b_asic.architecture import Architecture, Memory, ProcessingElement
 from b_asic.core_operations import Addition, ConstantMultiplication
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 
@@ -48,7 +49,7 @@ sfg.set_execution_time_of_type(Addition.type_name(), 1)
 
 # %%
 # Create schedule
-schedule = Schedule(sfg, cyclic=True)
+schedule = Schedule(sfg, scheduler=ASAPScheduler(), cyclic=True)
 schedule.show(title='Original schedule')
 
 # %%
diff --git a/examples/lwdfallpass.py b/examples/lwdfallpass.py
index 281856fe..6bbde629 100644
--- a/examples/lwdfallpass.py
+++ b/examples/lwdfallpass.py
@@ -9,6 +9,7 @@ This has different latency offsets for the different inputs/outputs.
 
 from b_asic.core_operations import SymmetricTwoportAdaptor
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 
@@ -22,5 +23,5 @@ d0 <<= adaptor0.output(1)
 out0 = Output(adaptor0.output(0))
 adaptor0.execution_time = 2
 sfg = SFG([in0], [out0])
-schedule = Schedule(sfg)
+schedule = Schedule(sfg, scheduler=ASAPScheduler())
 schedule.show()
diff --git a/examples/secondorderdirectformiir.py b/examples/secondorderdirectformiir.py
index b4eee825..aa84c26b 100644
--- a/examples/secondorderdirectformiir.py
+++ b/examples/secondorderdirectformiir.py
@@ -7,6 +7,7 @@ Second-order IIR Filter with Schedule
 
 from b_asic.core_operations import Addition, ConstantMultiplication
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 
@@ -43,5 +44,5 @@ sfg.set_execution_time_of_type(Addition.type_name(), 1)
 # %%
 # Create schedule
 
-schedule = Schedule(sfg, cyclic=True)
+schedule = Schedule(sfg, scheduler=ASAPScheduler(), cyclic=True)
 schedule.show()
diff --git a/examples/secondorderdirectformiir_architecture.py b/examples/secondorderdirectformiir_architecture.py
index 1a234a36..a7d72fb3 100644
--- a/examples/secondorderdirectformiir_architecture.py
+++ b/examples/secondorderdirectformiir_architecture.py
@@ -8,6 +8,7 @@ Second-order IIR Filter with Architecture
 from b_asic.architecture import Architecture, Memory, ProcessingElement
 from b_asic.core_operations import Addition, ConstantMultiplication
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 
@@ -42,7 +43,7 @@ sfg.set_execution_time_of_type(Addition.type_name(), 1)
 
 # %%
 # Create schedule.
-schedule = Schedule(sfg, cyclic=True)
+schedule = Schedule(sfg, scheduler=ASAPScheduler(), cyclic=True)
 schedule.show(title='Original schedule')
 
 # %%
diff --git a/examples/thirdorderblwdf.py b/examples/thirdorderblwdf.py
index 7496b078..d29fd215 100644
--- a/examples/thirdorderblwdf.py
+++ b/examples/thirdorderblwdf.py
@@ -11,6 +11,7 @@ from mplsignal.freq_plots import freqz_fir
 
 from b_asic.core_operations import Addition, SymmetricTwoportAdaptor
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.signal_generator import Impulse
 from b_asic.simulation import Simulation
@@ -49,5 +50,5 @@ freqz_fir(np.array(sim.results['0']) / 2)
 
 # %%
 # Create and display schedule
-schedule = Schedule(sfg, cyclic=True)
+schedule = Schedule(sfg, scheduler=ASAPScheduler(), cyclic=True)
 schedule.show()
diff --git a/examples/threepointwinograddft.py b/examples/threepointwinograddft.py
index 79bb7fb9..7e7a58ac 100644
--- a/examples/threepointwinograddft.py
+++ b/examples/threepointwinograddft.py
@@ -12,6 +12,7 @@ import networkx as nx
 from b_asic.architecture import Architecture, Memory, ProcessingElement
 from b_asic.core_operations import AddSub, ConstantMultiplication
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Input, Output
 
@@ -54,7 +55,7 @@ sfg.set_execution_time_of_type(AddSub.type_name(), 1)
 
 # %%
 # Generate schedule
-schedule = Schedule(sfg, cyclic=True)
+schedule = Schedule(sfg, scheduler=ASAPScheduler(), cyclic=True)
 schedule.show()
 
 # %%
diff --git a/pyproject.toml b/pyproject.toml
index a9074708..bffec3c7 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,6 +30,10 @@ classifiers = [
 ]
 dynamic = ["version", "authors"]
 
+[project.optional-dependencies]
+pyqt6 = ["pyqt6"]
+pyside6 = ["pyside6"]
+
 [tool.setuptools]
 zip-safe = false
 
diff --git a/test/fixtures/schedule.py b/test/fixtures/schedule.py
index e11b95c9..39b375ed 100644
--- a/test/fixtures/schedule.py
+++ b/test/fixtures/schedule.py
@@ -2,6 +2,7 @@ import pytest
 
 from b_asic.core_operations import Addition, ConstantMultiplication
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.signal_flow_graph import SFG
 
 
@@ -10,7 +11,7 @@ def secondorder_iir_schedule(precedence_sfg_delays):
     precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4)
     precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-    schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+    schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
     return schedule
 
 
@@ -23,7 +24,7 @@ def secondorder_iir_schedule_with_execution_times(precedence_sfg_delays):
         ConstantMultiplication.type_name(), 1
     )
 
-    schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+    schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
     return schedule
 
 
@@ -37,7 +38,9 @@ def schedule_direct_form_iir_lp_filter(sfg_direct_form_iir_lp_filter: SFG):
     sfg_direct_form_iir_lp_filter.set_execution_time_of_type(
         ConstantMultiplication.type_name(), 1
     )
-    schedule = Schedule(sfg_direct_form_iir_lp_filter, algorithm="ASAP", cyclic=True)
+    schedule = Schedule(
+        sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler(), cyclic=True
+    )
     schedule.move_operation('cmul3', -1)
     schedule.move_operation('cmul2', -1)
     schedule.move_operation('cmul3', -10)
diff --git a/test/fixtures/signal_flow_graph.py b/test/fixtures/signal_flow_graph.py
index 21c07d0f..7eaccbe7 100644
--- a/test/fixtures/signal_flow_graph.py
+++ b/test/fixtures/signal_flow_graph.py
@@ -332,3 +332,11 @@ def sfg_direct_form_iir_lp_filter():
     d1.input(0).connect(d0)
     y <<= a1 * d0 + a2 * d1 + a0 * top_node
     return SFG(inputs=[x], outputs=[y], name='Direct Form 2 IIR Lowpass filter')
+
+
+@pytest.fixture
+def sfg_empty():
+    """Empty SFG consisting of an Input followed by an Output."""
+    in0 = Input()
+    out0 = Output(in0)
+    return SFG(inputs=[in0], outputs=[out0])
diff --git a/test/test_architecture.py b/test/test_architecture.py
index 2dac82ff..2ab9c539 100644
--- a/test/test_architecture.py
+++ b/test/test_architecture.py
@@ -9,6 +9,7 @@ from b_asic.core_operations import Addition, ConstantMultiplication
 from b_asic.process import PlainMemoryVariable
 from b_asic.resources import ProcessCollection
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 from b_asic.special_operations import Input, Output
 
 
@@ -253,7 +254,7 @@ def test_resource_errors(precedence_sfg_delays):
         ConstantMultiplication.type_name(), 1
     )
 
-    schedule = Schedule(precedence_sfg_delays)
+    schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
     operations = schedule.get_operations()
     additions = operations.get_by_type_name(Addition.type_name())
     with pytest.raises(
diff --git a/test/test_schedule.py b/test/test_schedule.py
index c307798a..bbd98c36 100644
--- a/test/test_schedule.py
+++ b/test/test_schedule.py
@@ -10,6 +10,7 @@ import pytest
 from b_asic.core_operations import Addition, Butterfly, ConstantMultiplication
 from b_asic.process import OperatorProcess
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ALAPScheduler, ASAPScheduler
 from b_asic.sfg_generators import direct_form_fir
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
@@ -20,7 +21,7 @@ class TestInit:
         sfg_simple_filter.set_latency_of_type(Addition.type_name(), 5)
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 4)
 
-        schedule = Schedule(sfg_simple_filter)
+        schedule = Schedule(sfg_simple_filter, scheduler=ASAPScheduler())
 
         assert schedule._start_times == {
             "in0": 0,
@@ -39,7 +40,7 @@ class TestInit:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
 
         start_times_names = {}
         for op_id, start_time in schedule._start_times.items():
@@ -69,7 +70,7 @@ class TestInit:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ALAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ALAPScheduler())
 
         start_times_names = {}
         for op_id in schedule.start_times:
@@ -99,7 +100,9 @@ class TestInit:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, schedule_time=25, algorithm="ALAP")
+        schedule = Schedule(
+            precedence_sfg_delays, schedule_time=25, scheduler=ALAPScheduler()
+        )
 
         start_times_names = {}
         for op_id in schedule.start_times:
@@ -129,7 +132,7 @@ class TestInit:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
         with pytest.raises(ValueError, match="Too short schedule time. Minimum is 21."):
-            Schedule(precedence_sfg_delays, schedule_time=19, algorithm="ALAP")
+            Schedule(precedence_sfg_delays, schedule_time=19, scheduler=ALAPScheduler())
 
     def test_complicated_single_outputs_normal_latency_from_fixture(
         self, secondorder_iir_schedule
@@ -193,7 +196,7 @@ class TestInit:
             {"in0": 6, "in1": 7, "out0": 9}
         )
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
 
         start_times_names = {}
         for op_id, start_time in schedule._start_times.items():
@@ -221,7 +224,7 @@ class TestInit:
     def test_independent_sfg(self, sfg_two_inputs_two_outputs_independent_with_cmul):
         schedule = Schedule(
             sfg_two_inputs_two_outputs_independent_with_cmul,
-            algorithm="ASAP",
+            scheduler=ASAPScheduler(),
         )
 
         start_times_names = {}
@@ -250,7 +253,7 @@ class TestSlacks:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         assert (
             schedule.forward_slack(
                 precedence_sfg_delays.find_by_name("ADD3")[0].graph_id
@@ -279,7 +282,7 @@ class TestSlacks:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         assert schedule.slacks(
             precedence_sfg_delays.find_by_name("ADD3")[0].graph_id
         ) == (0, 7)
@@ -291,7 +294,7 @@ class TestSlacks:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         schedule.print_slacks()
         captured = capsys.readouterr()
         assert (
@@ -319,7 +322,7 @@ out0     |        0 |       oo
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         schedule.print_slacks(1)
         captured = capsys.readouterr()
         assert (
@@ -347,7 +350,7 @@ in0      |       oo |        0
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         with pytest.raises(
             ValueError, match="No operation with graph_id 'foo' in schedule"
         ):
@@ -367,7 +370,7 @@ class TestRescheduling:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 4)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
 
         schedule.move_operation(
             precedence_sfg_delays.find_by_name("ADD3")[0].graph_id, 4
@@ -404,7 +407,7 @@ class TestRescheduling:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         add3_id = precedence_sfg_delays.find_by_name("ADD3")[0].graph_id
         schedule.move_operation(add3_id, 4)
         assert schedule.forward_slack(add3_id) == 3
@@ -426,7 +429,7 @@ class TestRescheduling:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         with pytest.raises(
             ValueError,
             match="Operation 'add3' got incorrect move: -4. Must be between 0 and 7.",
@@ -439,7 +442,7 @@ class TestRescheduling:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         with pytest.raises(
             ValueError,
             match="Operation 'add3' got incorrect move: 10. Must be between 0 and 7.",
@@ -452,7 +455,7 @@ class TestRescheduling:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         assert schedule.backward_slack('cmul5') == 16
         assert schedule.forward_slack('cmul5') == 0
         schedule.move_operation_asap('cmul5')
@@ -465,7 +468,7 @@ class TestRescheduling:
         precedence_sfg_delays.set_latency_of_type(Addition.type_name(), 1)
         precedence_sfg_delays.set_latency_of_type(ConstantMultiplication.type_name(), 3)
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         old_laps = schedule.laps['in0']
         schedule.move_operation_asap('in0')
         assert schedule.start_time_of_operation('in0') == 0
@@ -479,7 +482,7 @@ class TestRescheduling:
         d <<= a
         sfg = SFG([in0], [out0])
         sfg.set_latency_of_type(Addition.type_name(), 1)
-        schedule = Schedule(sfg, cyclic=True)
+        schedule = Schedule(sfg, scheduler=ASAPScheduler(), cyclic=True)
 
         # Check initial conditions
         assert schedule.laps[sfg.find_by_id("add0").input(0).signals[0].graph_id] == 1
@@ -538,11 +541,11 @@ class TestRescheduling:
             ConstantMultiplication.type_name(), 3
         )
 
-        schedule = Schedule(precedence_sfg_delays, algorithm="ASAP")
+        schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         sfg = schedule.sfg
         assert precedence_sfg_delays.evaluate(5) == sfg.evaluate(5)
 
-        schedule = Schedule(sfg_direct_form_iir_lp_filter, algorithm="ASAP")
+        schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler())
         sfg = schedule.sfg
         assert sfg_direct_form_iir_lp_filter.evaluate(5) == sfg.evaluate(5)
 
@@ -551,7 +554,7 @@ class TestRescheduling:
             mult_properties={'latency': 2, 'execution_time': 1},
             add_properties={'latency': 2, 'execution_time': 1},
         )
-        schedule = Schedule(fir_sfg, algorithm="ASAP")
+        schedule = Schedule(fir_sfg, scheduler=ASAPScheduler())
         sfg = schedule.sfg
         assert fir_sfg.evaluate(5) == sfg.evaluate(5)
 
@@ -562,7 +565,7 @@ class TestTimeResolution:
     ):
         schedule = Schedule(
             sfg_two_inputs_two_outputs_independent_with_cmul,
-            algorithm="ASAP",
+            scheduler=ASAPScheduler(),
         )
         old_schedule_time = schedule.schedule_time
         assert schedule.get_possible_time_resolution_decrements() == [1]
@@ -596,7 +599,7 @@ class TestTimeResolution:
     ):
         schedule = Schedule(
             sfg_two_inputs_two_outputs_independent_with_cmul,
-            algorithm="ASAP",
+            scheduler=ASAPScheduler(),
         )
         old_schedule_time = schedule.schedule_time
 
@@ -633,7 +636,7 @@ class TestTimeResolution:
     ):
         schedule = Schedule(
             sfg_two_inputs_two_outputs_independent_with_cmul,
-            algorithm="ASAP",
+            scheduler=ASAPScheduler(),
         )
         old_schedule_time = schedule.schedule_time
         assert schedule.get_possible_time_resolution_decrements() == [1]
@@ -713,7 +716,7 @@ class TestErrors:
             ValueError,
             match="Input port 0 of operation add0 has no latency-offset.",
         ):
-            Schedule(sfg_simple_filter)
+            Schedule(sfg_simple_filter, scheduler=ASAPScheduler())
 
     def test_no_output_latency(self):
         in1 = Input()
@@ -726,7 +729,7 @@ class TestErrors:
             ValueError,
             match="Output port 1 of operation bfly0 has no latency-offset.",
         ):
-            Schedule(sfg)
+            Schedule(sfg, scheduler=ASAPScheduler())
         in1 = Input()
         in2 = Input()
         bfly1 = Butterfly(in1, in2, latency_offsets={"in0": 4, "in1": 2, "out1": 10})
@@ -742,28 +745,28 @@ class TestErrors:
             ValueError,
             match="Output port 0 of operation bfly0 has no latency-offset.",
         ):
-            Schedule(sfg)
+            Schedule(sfg, scheduler=ASAPScheduler())
 
     def test_too_short_schedule_time(self, sfg_simple_filter):
         sfg_simple_filter.set_latency_of_type(Addition.type_name(), 5)
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 4)
         with pytest.raises(ValueError, match="Too short schedule time. Minimum is 9."):
-            Schedule(sfg_simple_filter, schedule_time=3)
+            Schedule(sfg_simple_filter, scheduler=ASAPScheduler(), schedule_time=3)
 
-        schedule = Schedule(sfg_simple_filter)
+        schedule = Schedule(sfg_simple_filter, scheduler=ASAPScheduler())
         with pytest.raises(
             ValueError,
             match=re.escape("New schedule time (3) too short, minimum: 9."),
         ):
             schedule.set_schedule_time(3)
 
-    def test_incorrect_scheduling_algorithm(self, sfg_simple_filter):
-        sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1)
-        sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
-        with pytest.raises(
-            NotImplementedError, match="No algorithm with name: foo defined."
-        ):
-            Schedule(sfg_simple_filter, algorithm="foo")
+    # def test_incorrect_scheduling_algorithm(self, sfg_simple_filter):
+    #     sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1)
+    #     sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
+    #     with pytest.raises(
+    #         NotImplementedError, match="No algorithm with name: foo defined."
+    #     ):
+    #         Schedule(sfg_simple_filter, algorithm="foo")
 
     def test_no_sfg(self):
         with pytest.raises(TypeError, match="An SFG must be provided"):
@@ -775,13 +778,13 @@ class TestErrors:
         with pytest.raises(
             ValueError, match="Must provide start_times when using 'provided'"
         ):
-            Schedule(sfg_simple_filter, algorithm="provided")
+            Schedule(sfg_simple_filter)
 
     def test_provided_no_laps(self, sfg_simple_filter):
         sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1)
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
         with pytest.raises(ValueError, match="Must provide laps when using 'provided'"):
-            Schedule(sfg_simple_filter, algorithm="provided", start_times={'in0': 0})
+            Schedule(sfg_simple_filter, start_times={'in0': 0})
 
 
 class TestGetUsedTypeNames:
@@ -798,7 +801,7 @@ class TestYLocations:
     def test_provided_no_laps(self, sfg_simple_filter):
         sfg_simple_filter.set_latency_of_type(Addition.type_name(), 1)
         sfg_simple_filter.set_latency_of_type(ConstantMultiplication.type_name(), 2)
-        schedule = Schedule(sfg_simple_filter)
+        schedule = Schedule(sfg_simple_filter, ASAPScheduler())
         # Assign locations
         schedule.show()
         print(schedule._y_locations)
diff --git a/test/test_scheduler.py b/test/test_scheduler.py
new file mode 100644
index 00000000..0959b2cb
--- /dev/null
+++ b/test/test_scheduler.py
@@ -0,0 +1,259 @@
+import pytest
+
+from b_asic.core_operations import Addition, ConstantMultiplication
+from b_asic.schedule import Schedule
+from b_asic.scheduler import ALAPScheduler, ASAPScheduler, EarliestDeadlineScheduler
+
+
+class TestASAPScheduler:
+    def test_empty_sfg(self, sfg_empty):
+        with pytest.raises(
+            ValueError, match="Empty signal flow graph cannot be scheduled."
+        ):
+            Schedule(sfg_empty, scheduler=ASAPScheduler())
+
+    def test_direct_form_2_iir(self, sfg_direct_form_iir_lp_filter):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 4
+        )
+
+        schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler())
+
+        assert schedule._start_times == {
+            "in0": 0,
+            "cmul1": 0,
+            "cmul4": 0,
+            "cmul2": 0,
+            "cmul3": 0,
+            "add3": 4,
+            "add1": 4,
+            "add0": 9,
+            "cmul0": 14,
+            "add2": 18,
+            "out0": 23,
+        }
+        assert schedule.schedule_time == 23
+
+    def test_direct_form_2_iir_with_scheduling_time(
+        self, sfg_direct_form_iir_lp_filter
+    ):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 4
+        )
+
+        schedule = Schedule(
+            sfg_direct_form_iir_lp_filter, scheduler=ASAPScheduler(), schedule_time=30
+        )
+
+        assert schedule._start_times == {
+            "in0": 0,
+            "cmul1": 0,
+            "cmul4": 0,
+            "cmul2": 0,
+            "cmul3": 0,
+            "add3": 4,
+            "add1": 4,
+            "add0": 9,
+            "cmul0": 14,
+            "add2": 18,
+            "out0": 23,
+        }
+        assert schedule.schedule_time == 30
+
+
+class TestALAPScheduler:
+    def test_empty_sfg(self, sfg_empty):
+        with pytest.raises(
+            ValueError, match="Empty signal flow graph cannot be scheduled."
+        ):
+            Schedule(sfg_empty, scheduler=ALAPScheduler())
+
+    def test_direct_form_2_iir(self, sfg_direct_form_iir_lp_filter):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 4
+        )
+
+        schedule = Schedule(sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler())
+
+        assert schedule._start_times == {
+            "cmul3": 0,
+            "cmul4": 0,
+            "add1": 4,
+            "in0": 9,
+            "cmul2": 9,
+            "cmul1": 9,
+            "add0": 9,
+            "add3": 13,
+            "cmul0": 14,
+            "add2": 18,
+            "out0": 23,
+        }
+        assert schedule.schedule_time == 23
+
+    def test_direct_form_2_iir_with_scheduling_time(
+        self, sfg_direct_form_iir_lp_filter
+    ):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 4
+        )
+
+        schedule = Schedule(
+            sfg_direct_form_iir_lp_filter, scheduler=ALAPScheduler(), schedule_time=30
+        )
+
+        assert schedule._start_times == {
+            "cmul3": 7,
+            "cmul4": 7,
+            "add1": 11,
+            "in0": 16,
+            "cmul2": 16,
+            "cmul1": 16,
+            "add0": 16,
+            "add3": 20,
+            "cmul0": 21,
+            "add2": 25,
+            "out0": 30,
+        }
+        assert schedule.schedule_time == 30
+
+
+class TestEarliestDeadlineScheduler:
+    def test_empty_sfg(self, sfg_empty):
+        with pytest.raises(
+            ValueError, match="Empty signal flow graph cannot be scheduled."
+        ):
+            Schedule(sfg_empty, scheduler=EarliestDeadlineScheduler())
+
+    def test_direct_form_2_iir_inf_resources_no_exec_time(
+        self, sfg_direct_form_iir_lp_filter
+    ):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 4
+        )
+
+        schedule = Schedule(
+            sfg_direct_form_iir_lp_filter, scheduler=EarliestDeadlineScheduler()
+        )
+
+        # should be the same as for ASAP due to infinite resources, except for input
+        assert schedule._start_times == {
+            "in0": 9,
+            "cmul1": 0,
+            "cmul4": 0,
+            "cmul2": 0,
+            "cmul3": 0,
+            "add3": 4,
+            "add1": 4,
+            "add0": 9,
+            "cmul0": 14,
+            "add2": 18,
+            "out0": 23,
+        }
+        assert schedule.schedule_time == 23
+
+    def test_direct_form_2_iir_1_add_1_mul_no_exec_time(
+        self, sfg_direct_form_iir_lp_filter
+    ):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 5)
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 4
+        )
+
+        max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1}
+
+        schedule = Schedule(
+            sfg_direct_form_iir_lp_filter,
+            scheduler=EarliestDeadlineScheduler(max_resources),
+        )
+        assert schedule._start_times == {
+            "cmul4": 0,
+            "cmul3": 4,
+            "cmul1": 8,
+            "add1": 8,
+            "cmul2": 12,
+            "in0": 13,
+            "add0": 13,
+            "add3": 18,
+            "cmul0": 18,
+            "add2": 23,
+            "out0": 28,
+        }
+
+        assert schedule.schedule_time == 28
+
+    def test_direct_form_2_iir_1_add_1_mul_exec_time_1(
+        self, sfg_direct_form_iir_lp_filter
+    ):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 3
+        )
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 2)
+        sfg_direct_form_iir_lp_filter.set_execution_time_of_type(
+            ConstantMultiplication.type_name(), 1
+        )
+        sfg_direct_form_iir_lp_filter.set_execution_time_of_type(
+            Addition.type_name(), 1
+        )
+
+        max_resources = {ConstantMultiplication.type_name(): 1, Addition.type_name(): 1}
+
+        schedule = Schedule(
+            sfg_direct_form_iir_lp_filter,
+            scheduler=EarliestDeadlineScheduler(max_resources),
+        )
+        assert schedule._start_times == {
+            "cmul4": 0,
+            "cmul3": 1,
+            "cmul1": 2,
+            "cmul2": 3,
+            "add1": 4,
+            "in0": 6,
+            "add0": 6,
+            "add3": 7,
+            "cmul0": 8,
+            "add2": 11,
+            "out0": 13,
+        }
+
+        assert schedule.schedule_time == 13
+
+    def test_direct_form_2_iir_2_add_3_mul_exec_time_1(
+        self, sfg_direct_form_iir_lp_filter
+    ):
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(
+            ConstantMultiplication.type_name(), 3
+        )
+        sfg_direct_form_iir_lp_filter.set_latency_of_type(Addition.type_name(), 2)
+        sfg_direct_form_iir_lp_filter.set_execution_time_of_type(
+            ConstantMultiplication.type_name(), 1
+        )
+        sfg_direct_form_iir_lp_filter.set_execution_time_of_type(
+            Addition.type_name(), 1
+        )
+
+        max_resources = {ConstantMultiplication.type_name(): 3, Addition.type_name(): 2}
+
+        schedule = Schedule(
+            sfg_direct_form_iir_lp_filter,
+            scheduler=EarliestDeadlineScheduler(max_resources),
+        )
+        assert schedule._start_times == {
+            "cmul1": 0,
+            "cmul4": 0,
+            "cmul3": 0,
+            "cmul2": 1,
+            "add1": 3,
+            "add3": 4,
+            "in0": 5,
+            "add0": 5,
+            "cmul0": 7,
+            "add2": 10,
+            "out0": 12,
+        }
+
+        assert schedule.schedule_time == 12
diff --git a/test/test_scheduler_gui.py b/test/test_scheduler_gui.py
index d6ca2517..02e9d36e 100644
--- a/test/test_scheduler_gui.py
+++ b/test/test_scheduler_gui.py
@@ -2,6 +2,7 @@ import pytest
 
 from b_asic.core_operations import Addition, ConstantMultiplication
 from b_asic.schedule import Schedule
+from b_asic.scheduler import ASAPScheduler
 
 try:
     from b_asic.scheduler_gui.main_window import ScheduleMainWindow
@@ -22,6 +23,6 @@ def test_load_schedule(qtbot, sfg_simple_filter):
 
     widget = ScheduleMainWindow()
     qtbot.addWidget(widget)
-    schedule = Schedule(sfg_simple_filter)
+    schedule = Schedule(sfg_simple_filter, ASAPScheduler())
     widget.open(schedule)
     assert widget.statusbar.currentMessage() == "Schedule loaded successfully"
-- 
GitLab