From d7649bf570a86da31da9353eee6faf6b12e7e96a Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 22 Jan 2025 15:04:53 +0100
Subject: [PATCH 01/52] bumped to qtpy6

---
 b_asic/scheduler_gui/main_window.py    |  16 +-
 b_asic/scheduler_gui/ui_main_window.py | 796 ++++++++-----------------
 2 files changed, 268 insertions(+), 544 deletions(-)

diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index bd03b5f2..4de6e4cd 100644
--- a/b_asic/scheduler_gui/main_window.py
+++ b/b_asic/scheduler_gui/main_window.py
@@ -31,7 +31,7 @@ from qtpy.QtCore import (
     Qt,
     Slot,
 )
-from qtpy.QtGui import QCloseEvent, QColor, QFont, QIcon, QIntValidator
+from qtpy.QtGui import QCloseEvent, QColor, QFont, QIcon, QIntValidator, QPalette
 from qtpy.QtWidgets import (
     QAbstractButton,
     QAction,
@@ -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
 
@@ -1688,8 +1688,16 @@ def start_scheduler(schedule: Optional[Schedule] = None) -> Optional[Schedule]:
         The edited schedule.
     """
     if not QApplication.instance():
-        QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
         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()
     window = ScheduleMainWindow()
diff --git a/b_asic/scheduler_gui/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py
index ec174369..7def5879 100644
--- a/b_asic/scheduler_gui/ui_main_window.py
+++ b/b_asic/scheduler_gui/ui_main_window.py
@@ -1,293 +1,220 @@
-################################################################################
-## Form generated from reading UI file 'main_window.ui'
-##
-## Created by: Qt User Interface Compiler version 5.15.8
-##
-## WARNING! All changes made in this file will be lost when recompiling UI file!
-################################################################################
+# 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.
 
-from qtpy.QtCore import QSize, QCoreApplication, QRect, QMetaObject
-from qtpy.QtGui import QIcon, QColor, QFont, QBrush, Qt, QPainter
-from qtpy.QtWidgets import (
-    QSizePolicy,
-    QAction,
-    QMenu,
-    QMenuBar,
-    QToolBar,
-    QHBoxLayout,
-    QWidget,
-    QGraphicsView,
-    QSplitter,
-    QTableWidgetItem,
-    QTableWidget,
-    QAbstractItemView,
-    QStatusBar,
-)
 
+from qtpy import QtCore, QtGui, QtWidgets
 
-class Ui_MainWindow:
+
+class Ui_MainWindow(object):
     def setupUi(self, MainWindow):
-        if not MainWindow.objectName():
-            MainWindow.setObjectName("MainWindow")
+        MainWindow.setObjectName("MainWindow")
         MainWindow.resize(800, 600)
-        sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Preferred)
+        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
         sizePolicy.setHeightForWidth(MainWindow.sizePolicy().hasHeightForWidth())
         MainWindow.setSizePolicy(sizePolicy)
-        icon = QIcon()
-        icon.addFile(":/icons/basic/small_logo.png", QSize(), QIcon.Normal, QIcon.Off)
+        icon = QtGui.QIcon()
+        icon.addPixmap(QtGui.QPixmap(":/icons/basic/small_logo.png"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
         MainWindow.setWindowIcon(icon)
-        self.menu_load_from_file = QAction(MainWindow)
+        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.setObjectName("menu_load_from_file")
-        icon1 = QIcon()
-        iconThemeName = "document-open-folder"
-        if QIcon.hasThemeIcon(iconThemeName):
-            icon1 = QIcon.fromTheme(iconThemeName)
-        else:
-            icon1.addFile("../../../.designer/backup", QSize(), QIcon.Normal, QIcon.Off)
-
-        self.menu_load_from_file.setIcon(icon1)
-        self.menu_save = QAction(MainWindow)
-        self.menu_save.setObjectName("menu_save")
+        self.menu_save = 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.Normal, QIcon.Off)
-
-        self.menu_save.setIcon(icon2)
-        self.menu_node_info = QAction(MainWindow)
-        self.menu_node_info.setObjectName("menu_node_info")
+        icon = QtGui.QIcon.fromTheme("document-save")
+        self.menu_save.setIcon(icon)
+        self.menu_save.setObjectName("menu_save")
+        self.menu_node_info = QtGui.QAction(parent=MainWindow)
         self.menu_node_info.setCheckable(True)
         self.menu_node_info.setChecked(True)
-        icon3 = QIcon()
-        icon3.addFile(":/icons/misc/right_panel.svg", QSize(), QIcon.Normal, QIcon.Off)
-        icon3.addFile(
-            ":/icons/misc/right_filled_panel.svg", QSize(), QIcon.Normal, QIcon.On
-        )
-        self.menu_node_info.setIcon(icon3)
+        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)
         self.menu_node_info.setIconVisibleInMenu(False)
-        self.menu_quit = QAction(MainWindow)
+        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.setObjectName("menu_quit")
-        icon4 = QIcon()
-        iconThemeName = "application-exit"
-        if QIcon.hasThemeIcon(iconThemeName):
-            icon4 = QIcon.fromTheme(iconThemeName)
-        else:
-            icon4.addFile("../../../.designer/backup", QSize(), QIcon.Normal, QIcon.Off)
-
-        self.menu_quit.setIcon(icon4)
-        self.menu_save_as = QAction(MainWindow)
-        self.menu_save_as.setObjectName("menu_save_as")
+        self.menu_save_as = 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.Normal, QIcon.Off)
-
-        self.menu_save_as.setIcon(icon5)
-        self.menu_exit_dialog = QAction(MainWindow)
-        self.menu_exit_dialog.setObjectName("menu_exit_dialog")
+        icon = QtGui.QIcon.fromTheme("document-save-as")
+        self.menu_save_as.setIcon(icon)
+        self.menu_save_as.setObjectName("menu_save_as")
+        self.menu_exit_dialog = QtGui.QAction(parent=MainWindow)
         self.menu_exit_dialog.setCheckable(True)
         self.menu_exit_dialog.setChecked(True)
-        icon6 = QIcon()
-        iconThemeName = "view-close"
-        if QIcon.hasThemeIcon(iconThemeName):
-            icon6 = QIcon.fromTheme(iconThemeName)
-        else:
-            icon6.addFile("../../../.designer/backup", QSize(), QIcon.Normal, QIcon.Off)
-
-        self.menu_exit_dialog.setIcon(icon6)
-        self.menu_close_schedule = QAction(MainWindow)
-        self.menu_close_schedule.setObjectName("menu_close_schedule")
+        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)
-        self.menu_close_schedule.setIcon(icon6)
-        self.actionAbout = QAction(MainWindow)
+        icon = QtGui.QIcon.fromTheme("view-close")
+        self.menu_close_schedule.setIcon(icon)
+        self.menu_close_schedule.setObjectName("menu_close_schedule")
+        self.actionAbout = QtGui.QAction(parent=MainWindow)
         self.actionAbout.setObjectName("actionAbout")
-        self.actionDocumentation = QAction(MainWindow)
+        self.actionDocumentation = QtGui.QAction(parent=MainWindow)
         self.actionDocumentation.setObjectName("actionDocumentation")
-        self.actionReorder = QAction(MainWindow)
+        self.actionReorder = QtGui.QAction(parent=MainWindow)
         self.actionReorder.setObjectName("actionReorder")
-        self.actionPlot_schedule = QAction(MainWindow)
+        self.actionPlot_schedule = QtGui.QAction(parent=MainWindow)
         self.actionPlot_schedule.setObjectName("actionPlot_schedule")
-        self.action_view_variables = QAction(MainWindow)
-        self.action_view_variables.setObjectName("action_view_variables")
+        self.action_view_variables = QtGui.QAction(parent=MainWindow)
         self.action_view_variables.setEnabled(False)
-        self.action_view_port_accesses = QAction(MainWindow)
-        self.action_view_port_accesses.setObjectName("action_view_port_accesses")
+        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.actionUndo = QAction(MainWindow)
-        self.actionUndo.setObjectName("actionUndo")
+        self.action_view_port_accesses.setObjectName("action_view_port_accesses")
+        self.actionUndo = QtGui.QAction(parent=MainWindow)
         self.actionUndo.setEnabled(False)
-        self.actionRedo = QAction(MainWindow)
-        self.actionRedo.setObjectName("actionRedo")
+        self.actionUndo.setObjectName("actionUndo")
+        self.actionRedo = 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 = QAction(MainWindow)
-        self.actionDecrease_time_resolution.setObjectName(
-            "actionDecrease_time_resolution"
-        )
-        self.actionZoom_to_fit = QAction(MainWindow)
+        self.actionRedo.setObjectName("actionRedo")
+        self.actionIncrease_time_resolution = QtGui.QAction(parent=MainWindow)
+        self.actionIncrease_time_resolution.setObjectName("actionIncrease_time_resolution")
+        self.actionDecrease_time_resolution = QtGui.QAction(parent=MainWindow)
+        self.actionDecrease_time_resolution.setObjectName("actionDecrease_time_resolution")
+        self.actionZoom_to_fit = QtGui.QAction(parent=MainWindow)
         self.actionZoom_to_fit.setObjectName("actionZoom_to_fit")
-        self.actionStatus_bar = QAction(MainWindow)
-        self.actionStatus_bar.setObjectName("actionStatus_bar")
+        self.actionStatus_bar = QtGui.QAction(parent=MainWindow)
         self.actionStatus_bar.setCheckable(True)
         self.actionStatus_bar.setChecked(True)
-        self.actionToolbar = QAction(MainWindow)
-        self.actionToolbar.setObjectName("actionToolbar")
+        self.actionStatus_bar.setObjectName("actionStatus_bar")
+        self.actionToolbar = QtGui.QAction(parent=MainWindow)
         self.actionToolbar.setCheckable(True)
         self.actionToolbar.setChecked(True)
-        self.action_show_port_numbers = QAction(MainWindow)
-        self.action_show_port_numbers.setObjectName("action_show_port_numbers")
+        self.actionToolbar.setObjectName("actionToolbar")
+        self.action_show_port_numbers = QtGui.QAction(parent=MainWindow)
         self.action_show_port_numbers.setCheckable(True)
         self.action_show_port_numbers.setChecked(False)
         self.action_show_port_numbers.setIconVisibleInMenu(False)
-        self.action_incorrect_execution_time = QAction(MainWindow)
-        self.action_incorrect_execution_time.setObjectName(
-            "action_incorrect_execution_time"
-        )
+        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.menu_open = 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.menu_open.setObjectName("menu_open")
-        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.actionToggle_full_screen = QtGui.QAction(parent=MainWindow)
         self.actionToggle_full_screen.setCheckable(True)
-        self.actionPreferences = 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.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)
-        font.setWeight(50)
-        __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.setWeight(50)
-        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.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)
@@ -322,6 +249,11 @@ 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)
@@ -335,300 +267,84 @@ class Ui_MainWindow:
         self.toolBar.addAction(self.actionReorder)
 
         self.retranslateUi(MainWindow)
-
-        QMetaObject.connectSlotsByName(MainWindow)
-
-    # setupUi
+        QtCore.QMetaObject.connectSlotsByName(MainWindow)
 
     def retranslateUi(self, MainWindow):
-        self.menu_load_from_file.setText(
-            QCoreApplication.translate(
-                "MainWindow", "&Import schedule from file...", None
-            )
-        )
-        # if QT_CONFIG(tooltip)
-        self.menu_load_from_file.setToolTip(
-            QCoreApplication.translate(
-                "MainWindow", "Import schedule from python script", None
-            )
-        )
-        # 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(
-            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)
-        )
-        # if QT_CONFIG(tooltip)
-        self.menu_save_as.setToolTip(
-            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(
-            QCoreApplication.translate("MainWindow", "Open documentation", None)
-        )
-        # endif // QT_CONFIG(tooltip)
-        self.actionReorder.setText(
-            QCoreApplication.translate("MainWindow", "Reorder", None)
-        )
-        # if QT_CONFIG(tooltip)
-        self.actionReorder.setToolTip(
-            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)
-        )
-        # 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(
-            QCoreApplication.translate(
-                "MainWindow", "View execution times of variables", None
-            )
-        )
-        # if QT_CONFIG(tooltip)
-        self.action_view_variables.setToolTip(
-            QCoreApplication.translate("MainWindow", "View all variables", None)
-        )
-        # endif // QT_CONFIG(tooltip)
-        self.action_view_port_accesses.setText(
-            QCoreApplication.translate(
-                "MainWindow", "View port access statistics", None
-            )
-        )
-        # if QT_CONFIG(tooltip)
-        self.action_view_port_accesses.setToolTip(
-            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)
-        )
-        # endif // QT_CONFIG(shortcut)
-        self.actionIncrease_time_resolution.setText(
-            QCoreApplication.translate(
-                "MainWindow", "Increase time resolution...", None
-            )
-        )
-        self.actionDecrease_time_resolution.setText(
-            QCoreApplication.translate(
-                "MainWindow", "Decrease time resolution...", None
-            )
-        )
-        self.actionZoom_to_fit.setText(
-            QCoreApplication.translate("MainWindow", "Zoom to &fit", None)
-        )
-        self.actionStatus_bar.setText(
-            QCoreApplication.translate("MainWindow", "&Status bar", None)
-        )
-        # if QT_CONFIG(tooltip)
-        self.actionStatus_bar.setToolTip(
-            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)
-        )
-        # endif // QT_CONFIG(tooltip)
-        self.action_show_port_numbers.setText(
-            QCoreApplication.translate("MainWindow", "S&how port numbers", None)
-        )
-        # if QT_CONFIG(tooltip)
-        self.action_show_port_numbers.setToolTip(
-            QCoreApplication.translate(
-                "MainWindow", "Show port numbers of operation", None
-            )
-        )
-        # endif // QT_CONFIG(tooltip)
-        self.action_incorrect_execution_time.setText(
-            QCoreApplication.translate("MainWindow", "&Incorrect execution time", None)
-        )
-        # if QT_CONFIG(tooltip)
-        self.action_incorrect_execution_time.setToolTip(
-            QCoreApplication.translate(
-                "MainWindow",
-                "Highlight processes with execution time longer than schedule time",
-                None,
-            )
-        )
-        # endif // QT_CONFIG(tooltip)
-        self.menu_open.setText(
-            QCoreApplication.translate("MainWindow", "&Open...", None)
-        )
-        # if QT_CONFIG(tooltip)
-        self.menu_open.setToolTip(
-            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)
-        )
-        # endif // QT_CONFIG(shortcut)
-        self.actionToggle_full_screen.setText(
-            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)
-        )
-
+        _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)
-        ___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)
-        )
+        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(QCoreApplication.translate("MainWindow", "&File", None))
-        self.menu_Recent_Schedule.setTitle(
-            QCoreApplication.translate("MainWindow", "Open &recent", None)
-        )
-        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
+        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..."))
+        self.menu_load_from_file.setToolTip(_translate("MainWindow", "Import schedule from python script"))
+        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"))
+        self.menu_node_info.setToolTip(_translate("MainWindow", "Show/hide node information"))
+        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..."))
+        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"))
+        self.actionDocumentation.setToolTip(_translate("MainWindow", "Open documentation"))
+        self.actionReorder.setText(_translate("MainWindow", "Reorder"))
+        self.actionReorder.setToolTip(_translate("MainWindow", "Reorder schedule based on start time"))
+        self.actionReorder.setShortcut(_translate("MainWindow", "Ctrl+R"))
+        self.actionPlot_schedule.setText(_translate("MainWindow", "&Plot schedule"))
+        self.actionPlot_schedule.setToolTip(_translate("MainWindow", "Plot schedule"))
+        self.action_view_variables.setText(_translate("MainWindow", "View execution times of variables"))
+        self.action_view_variables.setToolTip(_translate("MainWindow", "View all variables"))
+        self.action_view_port_accesses.setText(_translate("MainWindow", "View port access statistics"))
+        self.action_view_port_accesses.setToolTip(_translate("MainWindow", "View port access statistics for storage"))
+        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"))
+        self.actionIncrease_time_resolution.setText(_translate("MainWindow", "Increase time resolution..."))
+        self.actionDecrease_time_resolution.setText(_translate("MainWindow", "Decrease time resolution..."))
+        self.actionZoom_to_fit.setText(_translate("MainWindow", "Zoom to &fit"))
+        self.actionStatus_bar.setText(_translate("MainWindow", "&Status bar"))
+        self.actionStatus_bar.setToolTip(_translate("MainWindow", "Show/hide status bar"))
+        self.actionToolbar.setText(_translate("MainWindow", "&Toolbar"))
+        self.actionToolbar.setToolTip(_translate("MainWindow", "Show/hide toolbar"))
+        self.action_show_port_numbers.setText(_translate("MainWindow", "S&how port numbers"))
+        self.action_show_port_numbers.setToolTip(_translate("MainWindow", "Show port numbers of operation"))
+        self.action_incorrect_execution_time.setText(_translate("MainWindow", "&Incorrect execution time"))
+        self.action_incorrect_execution_time.setToolTip(_translate("MainWindow", "Highlight processes with execution time longer than schedule time"))
+        self.menu_open.setText(_translate("MainWindow", "&Open..."))
+        self.menu_open.setToolTip(_translate("MainWindow", "Open previously saved schedule"))
+        self.menu_open.setShortcut(_translate("MainWindow", "Ctrl+O"))
+        self.actionToggle_full_screen.setText(_translate("MainWindow", "Toggle f&ull screen"))
+        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"))
-- 
GitLab


From bf03b089c551e8177ccc78bc45e39cc8cd96f4ed Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 22 Jan 2025 15:53:13 +0100
Subject: [PATCH 02/52] fixes from last time, also ran post-commit on all files

---
 b_asic/scheduler_gui/main_window.py    |   8 +-
 b_asic/scheduler_gui/ui_main_window.py | 180 +++++++++++++++++++------
 2 files changed, 144 insertions(+), 44 deletions(-)

diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index 4de6e4cd..f4502f99 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 PySide6
+        import PySide2
 
-        log.debug(f"PySide version:           {PySide6.__version__}")
+        log.debug(f"PySide version:           {PySide2.__version__}")
     if QT_API.lower().startswith("pyqt"):
         from qtpy.QtCore import PYQT_VERSION_STR
 
@@ -1694,8 +1694,8 @@ def start_scheduler(schedule: Optional[Schedule] = None) -> Optional[Schedule]:
         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.Base, QtCore.Qt.white)
+        palette.setColor(QPalette.ColorRole.AlternateBase, QtCore.Qt.lightGray)
         palette.setColor(QPalette.ColorRole.Text, QtCore.Qt.black)
         app.setPalette(palette)
     else:
diff --git a/b_asic/scheduler_gui/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py
index 7def5879..ad3fa898 100644
--- a/b_asic/scheduler_gui/ui_main_window.py
+++ b/b_asic/scheduler_gui/ui_main_window.py
@@ -9,23 +9,35 @@
 from qtpy import QtCore, QtGui, QtWidgets
 
 
-class Ui_MainWindow(object):
+class Ui_MainWindow:
     def setupUi(self, MainWindow):
         MainWindow.setObjectName("MainWindow")
         MainWindow.resize(800, 600)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
+        sizePolicy = QtWidgets.QSizePolicy(
+            QtWidgets.QSizePolicy.Policy.Preferred,
+            QtWidgets.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.addPixmap(
+            QtGui.QPixmap(":/icons/basic/small_logo.png"),
+            QtGui.QIcon.Mode.Normal,
+            QtGui.QIcon.State.Off,
+        )
         MainWindow.setWindowIcon(icon)
         self.centralwidget = QtWidgets.QWidget(parent=MainWindow)
-        sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Policy.Preferred, QtWidgets.QSizePolicy.Policy.Preferred)
+        sizePolicy = QtWidgets.QSizePolicy(
+            QtWidgets.QSizePolicy.Policy.Preferred,
+            QtWidgets.QSizePolicy.Policy.Preferred,
+        )
         sizePolicy.setHorizontalStretch(0)
         sizePolicy.setVerticalStretch(0)
-        sizePolicy.setHeightForWidth(self.centralwidget.sizePolicy().hasHeightForWidth())
+        sizePolicy.setHeightForWidth(
+            self.centralwidget.sizePolicy().hasHeightForWidth()
+        )
         self.centralwidget.setSizePolicy(sizePolicy)
         self.centralwidget.setObjectName("centralwidget")
         self.horizontalLayout = QtWidgets.QHBoxLayout(self.centralwidget)
@@ -37,15 +49,30 @@ class Ui_MainWindow(object):
         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.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.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.setSelectionBehavior(
+            QtWidgets.QAbstractItemView.SelectionBehavior.SelectRows
+        )
         self.info_table.setRowCount(2)
         self.info_table.setColumnCount(2)
         self.info_table.setObjectName("info_table")
@@ -54,14 +81,18 @@ class Ui_MainWindow(object):
         item = QtWidgets.QTableWidgetItem()
         self.info_table.setVerticalHeaderItem(1, item)
         item = QtWidgets.QTableWidgetItem()
-        item.setTextAlignment(QtCore.Qt.AlignmentFlag.AlignLeading|QtCore.Qt.AlignmentFlag.AlignVCenter)
+        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)
+        item.setTextAlignment(
+            QtCore.Qt.AlignmentFlag.AlignLeading | QtCore.Qt.AlignmentFlag.AlignVCenter
+        )
         self.info_table.setHorizontalHeaderItem(1, item)
         item = QtWidgets.QTableWidgetItem()
         font = QtGui.QFont()
@@ -75,7 +106,13 @@ class Ui_MainWindow(object):
         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)
+        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()
@@ -88,7 +125,13 @@ class Ui_MainWindow(object):
         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)
+        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)
@@ -135,8 +178,16 @@ class Ui_MainWindow(object):
         self.menu_node_info.setCheckable(True)
         self.menu_node_info.setChecked(True)
         icon1 = QtGui.QIcon()
-        icon1.addPixmap(QtGui.QPixmap(":/icons/misc/right_panel.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.Off)
-        icon1.addPixmap(QtGui.QPixmap(":/icons/misc/right_filled_panel.svg"), QtGui.QIcon.Mode.Normal, QtGui.QIcon.State.On)
+        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)
         self.menu_node_info.setIconVisibleInMenu(False)
         self.menu_node_info.setObjectName("menu_node_info")
@@ -181,9 +232,13 @@ class Ui_MainWindow(object):
         self.actionRedo.setEnabled(False)
         self.actionRedo.setObjectName("actionRedo")
         self.actionIncrease_time_resolution = QtGui.QAction(parent=MainWindow)
-        self.actionIncrease_time_resolution.setObjectName("actionIncrease_time_resolution")
+        self.actionIncrease_time_resolution.setObjectName(
+            "actionIncrease_time_resolution"
+        )
         self.actionDecrease_time_resolution = QtGui.QAction(parent=MainWindow)
-        self.actionDecrease_time_resolution.setObjectName("actionDecrease_time_resolution")
+        self.actionDecrease_time_resolution.setObjectName(
+            "actionDecrease_time_resolution"
+        )
         self.actionZoom_to_fit = QtGui.QAction(parent=MainWindow)
         self.actionZoom_to_fit.setObjectName("actionZoom_to_fit")
         self.actionStatus_bar = QtGui.QAction(parent=MainWindow)
@@ -203,7 +258,9 @@ class Ui_MainWindow(object):
         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.setObjectName("action_incorrect_execution_time")
+        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)
@@ -289,24 +346,34 @@ class Ui_MainWindow(object):
         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_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..."))
-        self.menu_load_from_file.setToolTip(_translate("MainWindow", "Import schedule from python script"))
+        self.menu_load_from_file.setText(
+            _translate("MainWindow", "&Import schedule from file...")
+        )
+        self.menu_load_from_file.setToolTip(
+            _translate("MainWindow", "Import schedule from python script")
+        )
         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"))
-        self.menu_node_info.setToolTip(_translate("MainWindow", "Show/hide node information"))
+        self.menu_node_info.setToolTip(
+            _translate("MainWindow", "Show/hide node information")
+        )
         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..."))
-        self.menu_save_as.setToolTip(_translate("MainWindow", "Save schedule with new file name"))
+        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"))
@@ -315,35 +382,68 @@ class Ui_MainWindow(object):
         self.actionAbout.setText(_translate("MainWindow", "&About"))
         self.actionAbout.setToolTip(_translate("MainWindow", "Open about window"))
         self.actionDocumentation.setText(_translate("MainWindow", "&Documentation"))
-        self.actionDocumentation.setToolTip(_translate("MainWindow", "Open documentation"))
+        self.actionDocumentation.setToolTip(
+            _translate("MainWindow", "Open documentation")
+        )
         self.actionReorder.setText(_translate("MainWindow", "Reorder"))
-        self.actionReorder.setToolTip(_translate("MainWindow", "Reorder schedule based on start time"))
+        self.actionReorder.setToolTip(
+            _translate("MainWindow", "Reorder schedule based on start time")
+        )
         self.actionReorder.setShortcut(_translate("MainWindow", "Ctrl+R"))
         self.actionPlot_schedule.setText(_translate("MainWindow", "&Plot schedule"))
         self.actionPlot_schedule.setToolTip(_translate("MainWindow", "Plot schedule"))
-        self.action_view_variables.setText(_translate("MainWindow", "View execution times of variables"))
-        self.action_view_variables.setToolTip(_translate("MainWindow", "View all variables"))
-        self.action_view_port_accesses.setText(_translate("MainWindow", "View port access statistics"))
-        self.action_view_port_accesses.setToolTip(_translate("MainWindow", "View port access statistics for storage"))
+        self.action_view_variables.setText(
+            _translate("MainWindow", "View execution times of variables")
+        )
+        self.action_view_variables.setToolTip(
+            _translate("MainWindow", "View all variables")
+        )
+        self.action_view_port_accesses.setText(
+            _translate("MainWindow", "View port access statistics")
+        )
+        self.action_view_port_accesses.setToolTip(
+            _translate("MainWindow", "View port access statistics for storage")
+        )
         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"))
-        self.actionIncrease_time_resolution.setText(_translate("MainWindow", "Increase time resolution..."))
-        self.actionDecrease_time_resolution.setText(_translate("MainWindow", "Decrease time resolution..."))
+        self.actionIncrease_time_resolution.setText(
+            _translate("MainWindow", "Increase time resolution...")
+        )
+        self.actionDecrease_time_resolution.setText(
+            _translate("MainWindow", "Decrease time resolution...")
+        )
         self.actionZoom_to_fit.setText(_translate("MainWindow", "Zoom to &fit"))
         self.actionStatus_bar.setText(_translate("MainWindow", "&Status bar"))
-        self.actionStatus_bar.setToolTip(_translate("MainWindow", "Show/hide status bar"))
+        self.actionStatus_bar.setToolTip(
+            _translate("MainWindow", "Show/hide status bar")
+        )
         self.actionToolbar.setText(_translate("MainWindow", "&Toolbar"))
         self.actionToolbar.setToolTip(_translate("MainWindow", "Show/hide toolbar"))
-        self.action_show_port_numbers.setText(_translate("MainWindow", "S&how port numbers"))
-        self.action_show_port_numbers.setToolTip(_translate("MainWindow", "Show port numbers of operation"))
-        self.action_incorrect_execution_time.setText(_translate("MainWindow", "&Incorrect execution time"))
-        self.action_incorrect_execution_time.setToolTip(_translate("MainWindow", "Highlight processes with execution time longer than schedule time"))
+        self.action_show_port_numbers.setText(
+            _translate("MainWindow", "S&how port numbers")
+        )
+        self.action_show_port_numbers.setToolTip(
+            _translate("MainWindow", "Show port numbers of operation")
+        )
+        self.action_incorrect_execution_time.setText(
+            _translate("MainWindow", "&Incorrect execution time")
+        )
+        self.action_incorrect_execution_time.setToolTip(
+            _translate(
+                "MainWindow",
+                "Highlight processes with execution time longer than schedule time",
+            )
+        )
         self.menu_open.setText(_translate("MainWindow", "&Open..."))
-        self.menu_open.setToolTip(_translate("MainWindow", "Open previously saved schedule"))
+        self.menu_open.setToolTip(
+            _translate("MainWindow", "Open previously saved schedule")
+        )
         self.menu_open.setShortcut(_translate("MainWindow", "Ctrl+O"))
-        self.actionToggle_full_screen.setText(_translate("MainWindow", "Toggle f&ull screen"))
+        self.actionToggle_full_screen.setText(
+            _translate("MainWindow", "Toggle f&ull screen")
+        )
         self.actionToggle_full_screen.setShortcut(_translate("MainWindow", "F11"))
         self.actionPreferences.setText(_translate("MainWindow", "Preferences"))
         self.actionPreferences.setToolTip(_translate("MainWindow", "Color and Fonts"))
-- 
GitLab


From 6fd1f5f987fc6c190e7ce40c6822d080c26d8e3f Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 22 Jan 2025 15:03:18 +0000
Subject: [PATCH 03/52] Update .gitlab-ci.yml file

---
 .gitlab-ci.yml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 32946f44..2e59a32d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -91,6 +91,12 @@ run-test-3.12-pyqt5:
   image: python:3.12
   extends: ".run-test"
 
+run-test-3.12-pyqt6:
+  variables:
+    QT_API: pyqt6
+  image: python:3.12
+  extends: ".run-test"
+
 # Seemingly works with Qt6, but tests stall on closing scheduler GUI due to modal dialog(?)
 #run-test-3.10-pyside6:
 #  variables:
-- 
GitLab


From 97342c36a63c7917c270d18ef84e071fbedb4585 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Thu, 23 Jan 2025 16:11:05 +0100
Subject: [PATCH 04/52] moved alap and asap to seperate class and changed some
 of the code

---
 b_asic/schedule.py             | 386 +++++++++++++++++++++++----------
 b_asic/scheduling_algorithm.py |   8 +
 2 files changed, 274 insertions(+), 120 deletions(-)
 create mode 100644 b_asic/scheduling_algorithm.py

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 4b34d580..3f8a0592 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -36,6 +36,7 @@ from b_asic.resources import ProcessCollection
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 from b_asic.types import TypeName
+from b_asic.scheduling_algorithm import SchedAlg
 
 # Need RGB from 0 to 1
 _EXECUTION_TIME_COLOR: Tuple[float, ...] = tuple(
@@ -68,13 +69,8 @@ class Schedule:
         algorithm.
     cyclic : bool, default: False
         If the schedule is cyclic.
-    algorithm : {'ASAP', 'ALAP', 'provided'}, default: 'ASAP'
-        The scheduling algorithm to use. The following algorithm are available:
-
-        * ``'ASAP'``: As-soon-as-possible scheduling.
-        * ``'ALAP'``: As-late-as-possible scheduling.
-
-        If 'provided', use provided *start_times*  and *laps* dictionaries.
+    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'.
@@ -100,7 +96,7 @@ class Schedule:
         sfg: SFG,
         schedule_time: Optional[int] = None,
         cyclic: bool = False,
-        algorithm: Literal["ASAP", "ALAP", "provided"] = "ASAP",
+        algorithm: SchedAlg = "ASAP",
         start_times: Optional[Dict[GraphID, int]] = None,
         laps: Optional[Dict[GraphID, int]] = None,
         max_resources: Optional[Dict[TypeName, int]] = None,
@@ -115,10 +111,14 @@ class Schedule:
         self._cyclic = cyclic
         self._y_locations = defaultdict(_y_locations_default)
         self._schedule_time = schedule_time
+
+        self.scheduler = Scheduler(self)
         if algorithm == "ASAP":
-            self._schedule_asap()
+            self.scheduler.schedule_asap()
         elif algorithm == "ALAP":
-            self._schedule_alap()
+            self.scheduler.schedule_alap()
+        elif algorithm == "earliest_deadline":
+            self.scheduler.schedule_earliest_deadline()
         elif algorithm == "provided":
             if start_times is None:
                 raise ValueError("Must provide start_times when using 'provided'")
@@ -797,116 +797,116 @@ class Schedule:
                 new_sfg = new_sfg.insert_operation_before(op, Delay(), port)
         return new_sfg()
 
-    def _schedule_alap(self) -> None:
-        """Schedule the operations using as-late-as-possible scheduling."""
-        precedence_list = self._sfg.get_precedence_list()
-        self._schedule_asap()
-        max_end_time = self.get_max_end_time()
-
-        if self.schedule_time is None:
-            self._schedule_time = max_end_time
-        elif self.schedule_time < max_end_time:
-            raise ValueError(f"Too short schedule time. Minimum is {max_end_time}.")
-
-        for output in self._sfg.find_by_type_name(Output.type_name()):
-            output = cast(Output, output)
-            self.move_operation_alap(output.graph_id)
-        for step in reversed(precedence_list):
-            graph_ids = {
-                outport.operation.graph_id
-                for outport in step
-                if not isinstance(outport.operation, Delay)
-            }
-            for graph_id in graph_ids:
-                self.move_operation_alap(graph_id)
-
-    def _schedule_asap(self) -> None:
-        """Schedule the operations using as-soon-as-possible scheduling."""
-        precedence_list = self._sfg.get_precedence_list()
-
-        if len(precedence_list) < 2:
-            raise ValueError("Empty signal flow graph cannot be scheduled.")
-
-        non_schedulable_ops = set()
-        for outport in precedence_list[0]:
-            operation = outport.operation
-            if operation.type_name() not in [Delay.type_name()]:
-                if operation.graph_id not in self._start_times:
-                    # Set start time of all operations in the first iter to 0
-                    self._start_times[operation.graph_id] = 0
-            else:
-                non_schedulable_ops.add(operation.graph_id)
-
-        for outport in precedence_list[1]:
-            operation = outport.operation
-            if operation.graph_id not in self._start_times:
-                # Set start time of all operations in the first iter to 0
-                self._start_times[operation.graph_id] = 0
-
-        for outports in precedence_list[2:]:
-            for outport in outports:
-                operation = outport.operation
-                if operation.graph_id not in self._start_times:
-                    # Schedule the operation if it does not have a start time yet.
-                    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 = self._start_times[
-                                source_port.operation.graph_id
-                            ]
-
-                            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."
-                                )
-
-                            source_end_time = (
-                                source_op_time + source_port.latency_offset
-                            )
-
-                        if current_input.latency_offset is None:
-                            raise ValueError(
-                                f"Input port {current_input.index} of operation"
-                                f" {current_input.operation.graph_id} has no"
-                                " latency-offset."
-                            )
-                        op_start_time_from_in = (
-                            source_end_time - current_input.latency_offset
-                        )
-                        op_start_time = max(op_start_time, op_start_time_from_in)
-
-                    self._start_times[operation.graph_id] = op_start_time
-        for output in self._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:
-                self._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."
-                    )
-                self._start_times[output.graph_id] = self._start_times[
-                    source_port.operation.graph_id
-                ] + cast(int, source_port.latency_offset)
-        self._remove_delays()
+    # def _schedule_alap(self) -> None:
+    #     """Schedule the operations using as-late-as-possible scheduling."""
+    #     precedence_list = self._sfg.get_precedence_list()
+    #     self._schedule_asap()
+    #     max_end_time = self.get_max_end_time()
+
+    #     if self.schedule_time is None:
+    #         self._schedule_time = max_end_time
+    #     elif self.schedule_time < max_end_time:
+    #         raise ValueError(f"Too short schedule time. Minimum is {max_end_time}.")
+
+    #     for output in self._sfg.find_by_type_name(Output.type_name()):
+    #         output = cast(Output, output)
+    #         self.move_operation_alap(output.graph_id)
+    #     for step in reversed(precedence_list):
+    #         graph_ids = {
+    #             outport.operation.graph_id
+    #             for outport in step
+    #             if not isinstance(outport.operation, Delay)
+    #         }
+    #         for graph_id in graph_ids:
+    #             self.move_operation_alap(graph_id)
+
+    # def _schedule_asap(self) -> None:
+    #     """Schedule the operations using as-soon-as-possible scheduling."""
+    #     precedence_list = self._sfg.get_precedence_list()
+
+    #     if len(precedence_list) < 2:
+    #         raise ValueError("Empty signal flow graph cannot be scheduled.")
+
+    #     non_schedulable_ops = set()
+    #     for outport in precedence_list[0]:
+    #         operation = outport.operation
+    #         if operation.type_name() not in [Delay.type_name()]:
+    #             if operation.graph_id not in self._start_times:
+    #                 # Set start time of all operations in the first iter to 0
+    #                 self._start_times[operation.graph_id] = 0
+    #         else:
+    #             non_schedulable_ops.add(operation.graph_id)
+
+    #     for outport in precedence_list[1]:
+    #         operation = outport.operation
+    #         if operation.graph_id not in self._start_times:
+    #             # Set start time of all operations in the first iter to 0
+    #             self._start_times[operation.graph_id] = 0
+
+    #     for outports in precedence_list[2:]:
+    #         for outport in outports:
+    #             operation = outport.operation
+    #             if operation.graph_id not in self._start_times:
+    #                 # Schedule the operation if it does not have a start time yet.
+    #                 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 = self._start_times[
+    #                             source_port.operation.graph_id
+    #                         ]
+
+    #                         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."
+    #                             )
+
+    #                         source_end_time = (
+    #                             source_op_time + source_port.latency_offset
+    #                         )
+
+    #                     if current_input.latency_offset is None:
+    #                         raise ValueError(
+    #                             f"Input port {current_input.index} of operation"
+    #                             f" {current_input.operation.graph_id} has no"
+    #                             " latency-offset."
+    #                         )
+    #                     op_start_time_from_in = (
+    #                         source_end_time - current_input.latency_offset
+    #                     )
+    #                     op_start_time = max(op_start_time, op_start_time_from_in)
+
+    #                 self._start_times[operation.graph_id] = op_start_time
+    #     for output in self._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:
+    #             self._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."
+    #                 )
+    #             self._start_times[output.graph_id] = self._start_times[
+    #                 source_port.operation.graph_id
+    #             ] + cast(int, source_port.latency_offset)
+    #     self._remove_delays()
 
     def _get_memory_variables_list(self) -> List[MemoryVariable]:
         ret: List[MemoryVariable] = []
@@ -1227,3 +1227,149 @@ class Schedule:
 
     # SVG is valid HTML. This is useful for e.g. sphinx-gallery
     _repr_html_ = _repr_svg_
+
+
+class Scheduler():
+    def __init__(self, schedule: Schedule) -> None:
+        self.schedule = 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()
+        if len(prec_list) < 2:
+            raise ValueError("Empty signal flow graph cannot be scheduled.")
+
+        # 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
+
+        # 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
+
+        # 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:
+                    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_port.operation.graph_id
+                            ]
+
+                            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."
+                                )
+
+                            source_end_time = (
+                                source_op_time + source_port.latency_offset
+                            )
+
+                        if current_input.latency_offset is None:
+                            raise ValueError(
+                                f"Input port {current_input.index} of operation"
+                                f" {current_input.operation.graph_id} has no"
+                                " latency-offset."
+                            )
+                        op_start_time_from_in = (
+                            source_end_time - current_input.latency_offset
+                        )
+                        op_start_time = max(op_start_time, op_start_time_from_in)
+
+                    sched._start_times[operation.graph_id] = op_start_time
+        
+        # handle output and remove delays
+        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."
+                    )
+                sched._start_times[output.graph_id] = sched._start_times[
+                    source_port.operation.graph_id
+                ] + cast(int, source_port.latency_offset)
+        sched._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:
+            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()):
+            output = cast(Output, output)
+            sched.move_operation_alap(output.graph_id)
+        
+        # move all operations ALAP
+        for step in reversed(sched.sfg.get_precedence_list()):
+            for outport in step:
+                if not isinstance(outport.operation, Delay):
+                    sched.move_operation_alap(outport.operation.graph_id)
+
+    def schedule_earliest_deadline(self) -> None:
+        """Schedule the operations using earliest deadline scheduling."""
+        pass
+        # sched = self.schedule
+        # prec_list = sched.sfg.get_precedence_list()
+        # if len(prec_list) < 2:
+        #     raise ValueError("Empty signal flow graph cannot be scheduled.")
+
+        # # 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
+
+        # # 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
+        
+        # # 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:
+        #             pass
\ No newline at end of file
diff --git a/b_asic/scheduling_algorithm.py b/b_asic/scheduling_algorithm.py
new file mode 100644
index 00000000..8a1dbcd3
--- /dev/null
+++ b/b_asic/scheduling_algorithm.py
@@ -0,0 +1,8 @@
+from enum import Enum
+
+class SchedAlg(Enum):
+    ASAP = "ASAP"
+    ALAP = "ALAP"
+    EARLIEST_DEADLINE = "earliest_deadline"
+    LEAST_SLACK = "least_slack"
+    PROVIDED = "provided"
-- 
GitLab


From eb4a5de84b262910184d0c9dcda83f119612667d Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Thu, 23 Jan 2025 16:26:43 +0100
Subject: [PATCH 05/52] linting fixes

---
 b_asic/schedule.py             | 16 ++++++++--------
 b_asic/scheduling_algorithm.py |  1 +
 2 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 3f8a0592..86eaba91 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -7,7 +7,7 @@ Contains the schedule class for scheduling operations in an SFG.
 import io
 import sys
 from collections import defaultdict
-from typing import Dict, List, Literal, Optional, Sequence, Tuple, cast
+from typing import Dict, List, Optional, Sequence, Tuple, cast
 
 import matplotlib.pyplot as plt
 import numpy as np
@@ -33,10 +33,10 @@ 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.scheduling_algorithm import SchedAlg
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 from b_asic.types import TypeName
-from b_asic.scheduling_algorithm import SchedAlg
 
 # Need RGB from 0 to 1
 _EXECUTION_TIME_COLOR: Tuple[float, ...] = tuple(
@@ -1229,10 +1229,10 @@ class Schedule:
     _repr_html_ = _repr_svg_
 
 
-class Scheduler():
+class Scheduler:
     def __init__(self, schedule: Schedule) -> None:
         self.schedule = schedule
-    
+
     def schedule_asap(self) -> None:
         """Schedule the operations using as-soon-as-possible scheduling."""
         sched = self.schedule
@@ -1303,7 +1303,7 @@ class Scheduler():
                         op_start_time = max(op_start_time, op_start_time_from_in)
 
                     sched._start_times[operation.graph_id] = op_start_time
-        
+
         # handle output and remove delays
         for output in sched._sfg.find_by_type_name(Output.type_name()):
             output = cast(Output, output)
@@ -1337,7 +1337,7 @@ class Scheduler():
         for output in sched.sfg.find_by_type_name(Output.type_name()):
             output = cast(Output, output)
             sched.move_operation_alap(output.graph_id)
-        
+
         # move all operations ALAP
         for step in reversed(sched.sfg.get_precedence_list()):
             for outport in step:
@@ -1366,10 +1366,10 @@ class Scheduler():
         #     operation = outport.operation
         #     if operation.graph_id not in sched._start_times:
         #         sched._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:
-        #             pass
\ No newline at end of file
+        #             pass
diff --git a/b_asic/scheduling_algorithm.py b/b_asic/scheduling_algorithm.py
index 8a1dbcd3..c6787a73 100644
--- a/b_asic/scheduling_algorithm.py
+++ b/b_asic/scheduling_algorithm.py
@@ -1,5 +1,6 @@
 from enum import Enum
 
+
 class SchedAlg(Enum):
     ASAP = "ASAP"
     ALAP = "ALAP"
-- 
GitLab


From c6c95908ada00ba951ceab486ce2d17cabfa69c8 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Fri, 24 Jan 2025 16:50:52 +0100
Subject: [PATCH 06/52] post commit

---
 b_asic/schedule.py | 126 +++++++++++++++++++++++++++------------------
 1 file changed, 77 insertions(+), 49 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 86eaba91..c0d998cc 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -118,7 +118,7 @@ class Schedule:
         elif algorithm == "ALAP":
             self.scheduler.schedule_alap()
         elif algorithm == "earliest_deadline":
-            self.scheduler.schedule_earliest_deadline()
+            self.scheduler.schedule_earliest_deadline([])
         elif algorithm == "provided":
             if start_times is None:
                 raise ValueError("Must provide start_times when using 'provided'")
@@ -1246,14 +1246,15 @@ 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:
+            # elif operation.graph_id not in sched._start_times:
+            else:
                 sched._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
+            # if operation.graph_id not in sched._start_times:
+            sched._start_times[operation.graph_id] = 0
 
         # handle the remaining sets
         for outports in prec_list[2:]:
@@ -1304,23 +1305,7 @@ class Scheduler:
 
                     sched._start_times[operation.graph_id] = op_start_time
 
-        # handle output and remove delays
-        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."
-                    )
-                sched._start_times[output.graph_id] = sched._start_times[
-                    source_port.operation.graph_id
-                ] + cast(int, source_port.latency_offset)
-        sched._remove_delays()
+        self._handle_outputs_and_delays(non_schedulable_ops)
 
     def schedule_alap(self) -> None:
         """Schedule the operations using as-late-as-possible scheduling."""
@@ -1344,32 +1329,75 @@ class Scheduler:
                 if not isinstance(outport.operation, Delay):
                     sched.move_operation_alap(outport.operation.graph_id)
 
-    def schedule_earliest_deadline(self) -> None:
+    def schedule_earliest_deadline(self, process_elements: list[Operation]) -> None:
         """Schedule the operations using earliest deadline scheduling."""
-        pass
-        # sched = self.schedule
-        # prec_list = sched.sfg.get_precedence_list()
-        # if len(prec_list) < 2:
-        #     raise ValueError("Empty signal flow graph cannot be scheduled.")
-
-        # # 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
-
-        # # 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
-
-        # # 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:
-        #             pass
+
+        # ACT BASED ON THE NUMBER OF PEs!
+
+        sched = self.schedule
+        prec_list = sched.sfg.get_precedence_list()
+        if len(prec_list) < 2:
+            raise ValueError("Empty signal flow graph cannot be scheduled.")
+
+        # 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
+
+        # latencies = [outport.operation.latency for outport in prec_list[1]]
+        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
+
+        for outports in prec_list[2:]:
+            # try all remaining operations for one time step
+            candidates = []
+            while len(candidates) == 0:
+                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
+                current_time += 1
+            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)
+
+    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."
+                    )
+                sched._start_times[output.graph_id] = sched._start_times[
+                    source_port.operation.graph_id
+                ] + cast(int, source_port.latency_offset)
+        sched._remove_delays()
-- 
GitLab


From 980170f1f54558cfd7c27e71f51879d83ad7ea78 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Fri, 24 Jan 2025 17:03:53 +0100
Subject: [PATCH 07/52] earliest deadline, first version working, needs testing

---
 b_asic/schedule.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index c0d998cc..58fa572a 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -1361,7 +1361,9 @@ class Scheduler:
         for outports in prec_list[2:]:
             # try all remaining operations for one time step
             candidates = []
+            current_time -= 1
             while len(candidates) == 0:
+                current_time += 1
                 for outport in outports:
                     remaining_op = outport.operation
                     op_is_ready_to_be_scheduled = True
@@ -1374,7 +1376,6 @@ class Scheduler:
                     if op_is_ready_to_be_scheduled:
                         candidates.append(remaining_op)
                         # sched._start_times[remaining_op.graph_id] = current_time
-                current_time += 1
             sorted_candidates = sorted(
                 candidates, key=lambda candidate: candidate.latency
             )
-- 
GitLab


From 831687a8776d95d32501a73fe255b9d881b58ce7 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:38:14 +0100
Subject: [PATCH 08/52] removed end-of-life py version 3.8

---
 .gitlab-ci.yml | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 2e59a32d..96ca283e 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -35,18 +35,6 @@ before_script:
         path: cov.xml
   coverage: /(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/
 
-run-test-3.8-pyside2:
-  variables:
-    QT_API: pyside2
-  image: python:3.8
-  extends: ".run-test"
-
-run-test-3.8-pyqt5:
-  variables:
-    QT_API: pyqt5
-  image: python:3.8
-  extends: ".run-test"
-
 run-test-3.9-pyside2:
   variables:
     QT_API: pyside2
-- 
GitLab


From 8c2e1053f977c33ef116fe346525690b7499cc18 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:40:42 +0100
Subject: [PATCH 09/52] removed py version 3.9 since it will reach end-of-life
 this year

---
 .gitlab-ci.yml | 12 ------------
 1 file changed, 12 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 96ca283e..873cea6c 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -35,18 +35,6 @@ before_script:
         path: cov.xml
   coverage: /(?i)total.*? (100(?:\.0+)?\%|[1-9]?\d(?:\.\d+)?\%)$/
 
-run-test-3.9-pyside2:
-  variables:
-    QT_API: pyside2
-  image: python:3.9
-  extends: ".run-test"
-
-run-test-3.9-pyqt5:
-  variables:
-    QT_API: pyqt5
-  image: python:3.9
-  extends: ".run-test"
-
 run-test-3.10-pyside2:
   variables:
     QT_API: pyside2
-- 
GitLab


From 9f512f7cd59d72f9443f5926f9e5d182faab3569 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:41:11 +0100
Subject: [PATCH 10/52] updated toml file with new py version

---
 pyproject.toml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index c0d7de2f..ff1ea7b2 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -4,7 +4,7 @@ description = "Better ASIC Toolbox"
 readme = "README.md"
 maintainers = [{ name = "Oscar Gustafsson", email = "oscar.gustafsson@liu.se" }]
 license = { file = "LICENSE" }
-requires-python = ">=3.8"
+requires-python = ">=3.10"
 dependencies = [
   "numpy",
   "qtpy",
-- 
GitLab


From 40eb2980980dad07d536e74c5b1e44682add922c Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:45:29 +0100
Subject: [PATCH 11/52] updated ci file with pyqt6 tests for python 3.10 and
 3.11

---
 .gitlab-ci.yml | 12 ++++++++++++
 1 file changed, 12 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 873cea6c..aeb235f0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -47,6 +47,12 @@ run-test-3.10-pyqt5:
   image: python:3.10
   extends: ".run-test"
 
+run-test-3.10-pyqt6:
+  variables:
+    QT_API: pyqt6
+  image: python:3.10
+  extends: ".run-test"
+
 # PySide2 does not seem to have support for 3.11, "almost works" though
 #run-test-3.11-pyside2:
 #  variables:
@@ -61,6 +67,12 @@ run-test-3.11-pyqt5:
   image: python:3.11
   extends: ".run-test"
 
+run-test-3.11-pyqt6:
+  variables:
+    QT_API: pyqt6
+  image: python:3.11
+  extends: ".run-test"
+
 run-test-3.12-pyqt5:
   variables:
     QT_API: pyqt5
-- 
GitLab


From 7bc3424f7a7322d2b8f05cc9ed6556cfa2db0cd6 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:46:47 +0100
Subject: [PATCH 12/52] updated ci file with python 3.13

---
 .gitlab-ci.yml | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index aeb235f0..1bb8d867 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -85,6 +85,12 @@ run-test-3.12-pyqt6:
   image: python:3.12
   extends: ".run-test"
 
+run-test-3.13-pyqt6:
+  variables:
+    QT_API: pyqt6
+  image: python:3.13
+  extends: ".run-test"
+
 # Seemingly works with Qt6, but tests stall on closing scheduler GUI due to modal dialog(?)
 #run-test-3.10-pyside6:
 #  variables:
-- 
GitLab


From c32e17ee379c60165c8f790d8c8270bb989f5d24 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:01:17 +0000
Subject: [PATCH 13/52] Update .gitlab-ci.yml file

---
 .gitlab-ci.yml | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1bb8d867..275368b0 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -49,7 +49,7 @@ run-test-3.10-pyqt5:
 
 run-test-3.10-pyqt6:
   variables:
-    QT_API: pyqt6
+    QT_API: PyQt6
   image: python:3.10
   extends: ".run-test"
 
@@ -69,7 +69,7 @@ run-test-3.11-pyqt5:
 
 run-test-3.11-pyqt6:
   variables:
-    QT_API: pyqt6
+    QT_API: PyQt6
   image: python:3.11
   extends: ".run-test"
 
@@ -81,13 +81,13 @@ run-test-3.12-pyqt5:
 
 run-test-3.12-pyqt6:
   variables:
-    QT_API: pyqt6
+    QT_API: PyQt6
   image: python:3.12
   extends: ".run-test"
 
 run-test-3.13-pyqt6:
   variables:
-    QT_API: pyqt6
+    QT_API: PyQt6
   image: python:3.13
   extends: ".run-test"
 
-- 
GitLab


From 2e41421589db056bce9473775c87b4d102c1781c Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:13:02 +0000
Subject: [PATCH 14/52] Update .gitlab-ci.yml file

---
 .gitlab-ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 275368b0..88a2d4be 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,6 +19,7 @@ before_script:
   # Install test dependencies
   - pip install -r requirements_test.txt
   - export PYTEST_QT_API=$QT_API
+  - export MPLBACKEND=qtagg
 
 .run-test:
   stage: test
-- 
GitLab


From 450b1e86d267471a8ec78c17d91b38af4aec156d Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 08:26:48 +0000
Subject: [PATCH 15/52] Update .gitlab-ci.yml file

---
 .gitlab-ci.yml | 9 ++++-----
 1 file changed, 4 insertions(+), 5 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 88a2d4be..1bb8d867 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -19,7 +19,6 @@ before_script:
   # Install test dependencies
   - pip install -r requirements_test.txt
   - export PYTEST_QT_API=$QT_API
-  - export MPLBACKEND=qtagg
 
 .run-test:
   stage: test
@@ -50,7 +49,7 @@ run-test-3.10-pyqt5:
 
 run-test-3.10-pyqt6:
   variables:
-    QT_API: PyQt6
+    QT_API: pyqt6
   image: python:3.10
   extends: ".run-test"
 
@@ -70,7 +69,7 @@ run-test-3.11-pyqt5:
 
 run-test-3.11-pyqt6:
   variables:
-    QT_API: PyQt6
+    QT_API: pyqt6
   image: python:3.11
   extends: ".run-test"
 
@@ -82,13 +81,13 @@ run-test-3.12-pyqt5:
 
 run-test-3.12-pyqt6:
   variables:
-    QT_API: PyQt6
+    QT_API: pyqt6
   image: python:3.12
   extends: ".run-test"
 
 run-test-3.13-pyqt6:
   variables:
-    QT_API: PyQt6
+    QT_API: pyqt6
   image: python:3.13
   extends: ".run-test"
 
-- 
GitLab


From 54d614eb4df4cd6a11c05c3d129ab7cf68a7c81f Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 09:15:30 +0000
Subject: [PATCH 16/52] debugging ci file

---
 .gitlab-ci.yml | 1 +
 1 file changed, 1 insertion(+)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1bb8d867..1fa2fb74 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -84,6 +84,7 @@ run-test-3.12-pyqt6:
     QT_API: pyqt6
   image: python:3.12
   extends: ".run-test"
+  allow_failure: true
 
 run-test-3.13-pyqt6:
   variables:
-- 
GitLab


From 7ef91215bd9f3aece99901e627dfab2980fe89d8 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 09:35:51 +0000
Subject: [PATCH 17/52] debugging (adding -s to pytest command)

---
 .gitlab-ci.yml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 1fa2fb74..7001d19d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -23,7 +23,7 @@ before_script:
 .run-test:
   stage: test
   script:
-    - pytest --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=20 --durations=10
+    - pytest -s --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=20 --durations=10
     # - lcov --capture --directory . --output-file coverage.info
     # - lcov --output-file coverage.info --extract coverage.info $PWD/src/'*' $PWD/b_asic/'*'
     # - lcov --list coverage.info
-- 
GitLab


From 7167a5b2f6501039a8c5f8cb23b6b026282ae783 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 09:41:50 +0000
Subject: [PATCH 18/52] attempting fix by adding libxcb-cursor-dev

---
 .gitlab-ci.yml | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 7001d19d..f95d4f67 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,6 +6,7 @@ before_script:
   - apt-get update --yes
   # - apt-get install --yes build-essential cmake graphviz python3-pyqt5 xvfb xdg-utils lcov
   - apt-get install --yes graphviz python3-pyqt5 xvfb xdg-utils
+  - apt-get install -y libxcb-cursor-dev
   - python -m pip install --upgrade pip
   - python --version
   - pip install -r requirements.txt
@@ -23,7 +24,7 @@ before_script:
 .run-test:
   stage: test
   script:
-    - pytest -s --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=20 --durations=10
+    - pytest --cov=b_asic --cov-report=xml:cov.xml --cov-report=term --cov-branch --color=yes test --timeout=20 --durations=10
     # - lcov --capture --directory . --output-file coverage.info
     # - lcov --output-file coverage.info --extract coverage.info $PWD/src/'*' $PWD/b_asic/'*'
     # - lcov --list coverage.info
-- 
GitLab


From c23e613e89b4fb8639b3702c427ed0b6786446b3 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 11:01:17 +0100
Subject: [PATCH 19/52] CI fixes, hopefully working now

---
 .gitlab-ci.yml | 30 ------------------------------
 1 file changed, 30 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f95d4f67..5f4c6f59 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -54,14 +54,6 @@ run-test-3.10-pyqt6:
   image: python:3.10
   extends: ".run-test"
 
-# PySide2 does not seem to have support for 3.11, "almost works" though
-#run-test-3.11-pyside2:
-#  variables:
-#    QT_API: pyside2
-#  image: python:3.11
-#  extends: ".run-test"
-#  allow_failure: true
-
 run-test-3.11-pyqt5:
   variables:
     QT_API: pyqt5
@@ -85,28 +77,6 @@ run-test-3.12-pyqt6:
     QT_API: pyqt6
   image: python:3.12
   extends: ".run-test"
-  allow_failure: true
-
-run-test-3.13-pyqt6:
-  variables:
-    QT_API: pyqt6
-  image: python:3.13
-  extends: ".run-test"
-
-# Seemingly works with Qt6, but tests stall on closing scheduler GUI due to modal dialog(?)
-#run-test-3.10-pyside6:
-#  variables:
-#    QT_API: pyside6
-#  image: python:3.10
-#  extends: ".run-test"
-#  allow_failure: true
-#
-#run-test-3.10-pyqt6:
-#  variables:
-#    QT_API: pyqt6
-#  image: python:3.10
-#  extends: ".run-test"
-#  allow_failure: true
 
 run-vhdl-tests:
   variables:
-- 
GitLab


From e3dc0015eac210792ba4ab365f9382474742106b Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 15:06:12 +0100
Subject: [PATCH 20/52] work in progress

---
 b_asic/sfg_generators.py | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index cdfb93b4..df1bf401 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -255,3 +255,39 @@ def transposed_direct_form_fir(
     output <<= tmp_add
 
     return SFG([input_op], [output], name=Name(name))
+
+
+def direct_form_I_iir(
+    b: Sequence[complex],
+    a: Sequence[complex],
+    name: Optional[str] = None,
+    mult_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
+    add_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
+) -> SFG:
+    # np_b = np.atleast_1d(np.squeeze(np.asarray(b)))
+    # np_a = np.atleast_1d(np.squeeze(np.asarray(a)))
+
+    if mult_properties is None:
+        mult_properties = {}
+    if add_properties is None:
+        add_properties = {}
+
+    input_op = Input()
+    output = Output()
+
+    muls = [ConstantMultiplication(b[0], input_op, **mult_properties)]
+    delays = []
+    prev_delay = input_op
+    for i, coeff in enumerate(b[1:]):
+        prev_delay = Delay(prev_delay)
+        delays.append(prev_delay)
+        if i < len(b) - 1:
+            muls.append(ConstantMultiplication(coeff, prev_delay, **mult_properties))
+
+    # mul_pairs = [(muls[i], muls[i+1]) for i in range(len(muls)-1)]
+    for i in range(len(muls)):
+        Addition(muls[i], delays[i], **add_properties)
+
+    output <<= muls[0]
+
+    return SFG([input_op], [output], name=Name(name))
-- 
GitLab


From 2a7a7305c79f9eb587cf1e4e68e2b013f9bdc945 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 16:00:09 +0100
Subject: [PATCH 21/52] fixes from mr comments

---
 b_asic/schedule.py             | 179 +-----------------------------
 b_asic/scheduler.py            | 193 +++++++++++++++++++++++++++++++++
 b_asic/scheduling_algorithm.py |   9 --
 pyproject.toml                 |   7 +-
 4 files changed, 199 insertions(+), 189 deletions(-)
 create mode 100644 b_asic/scheduler.py
 delete mode 100644 b_asic/scheduling_algorithm.py

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 58fa572a..a708fe1a 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.scheduling_algorithm import SchedAlg
+from b_asic.scheduler import Scheduler, SchedulingAlgorithm
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 from b_asic.types import TypeName
@@ -96,7 +96,7 @@ class Schedule:
         sfg: SFG,
         schedule_time: Optional[int] = None,
         cyclic: bool = False,
-        algorithm: SchedAlg = "ASAP",
+        algorithm: SchedulingAlgorithm = "ASAP",
         start_times: Optional[Dict[GraphID, int]] = None,
         laps: Optional[Dict[GraphID, int]] = None,
         max_resources: Optional[Dict[TypeName, int]] = None,
@@ -1227,178 +1227,3 @@ class Schedule:
 
     # SVG is valid HTML. This is useful for e.g. sphinx-gallery
     _repr_html_ = _repr_svg_
-
-
-class Scheduler:
-    def __init__(self, schedule: Schedule) -> None:
-        self.schedule = 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()
-        if len(prec_list) < 2:
-            raise ValueError("Empty signal flow graph cannot be scheduled.")
-
-        # 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:
-            else:
-                sched._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
-
-        # 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:
-                    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_port.operation.graph_id
-                            ]
-
-                            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."
-                                )
-
-                            source_end_time = (
-                                source_op_time + source_port.latency_offset
-                            )
-
-                        if current_input.latency_offset is None:
-                            raise ValueError(
-                                f"Input port {current_input.index} of operation"
-                                f" {current_input.operation.graph_id} has no"
-                                " latency-offset."
-                            )
-                        op_start_time_from_in = (
-                            source_end_time - current_input.latency_offset
-                        )
-                        op_start_time = max(op_start_time, op_start_time_from_in)
-
-                    sched._start_times[operation.graph_id] = op_start_time
-
-        self._handle_outputs_and_delays(non_schedulable_ops)
-
-    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:
-            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()):
-            output = cast(Output, output)
-            sched.move_operation_alap(output.graph_id)
-
-        # move all operations ALAP
-        for step in reversed(sched.sfg.get_precedence_list()):
-            for outport in step:
-                if not isinstance(outport.operation, Delay):
-                    sched.move_operation_alap(outport.operation.graph_id)
-
-    def schedule_earliest_deadline(self, process_elements: list[Operation]) -> None:
-        """Schedule the operations using earliest deadline scheduling."""
-
-        # ACT BASED ON THE NUMBER OF PEs!
-
-        sched = self.schedule
-        prec_list = sched.sfg.get_precedence_list()
-        if len(prec_list) < 2:
-            raise ValueError("Empty signal flow graph cannot be scheduled.")
-
-        # 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
-
-        # latencies = [outport.operation.latency for outport in prec_list[1]]
-        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
-
-        for outports in prec_list[2:]:
-            # try all remaining operations for one time step
-            candidates = []
-            current_time -= 1
-            while len(candidates) == 0:
-                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)
-
-    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."
-                    )
-                sched._start_times[output.graph_id] = sched._start_times[
-                    source_port.operation.graph_id
-                ] + cast(int, source_port.latency_offset)
-        sched._remove_delays()
diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
new file mode 100644
index 00000000..845859c9
--- /dev/null
+++ b/b_asic/scheduler.py
@@ -0,0 +1,193 @@
+from enum import Enum
+from typing import TYPE_CHECKING, cast
+
+from b_asic.operation import Operation
+from b_asic.port import OutputPort
+from b_asic.special_operations import Delay, Output
+
+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:
+    def __init__(self, schedule: "Schedule") -> None:
+        self.schedule = 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()
+        if len(prec_list) < 2:
+            raise ValueError("Empty signal flow graph cannot be scheduled.")
+
+        # 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:
+            else:
+                sched._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
+
+        # 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:
+                    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_port.operation.graph_id
+                            ]
+
+                            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."
+                                )
+
+                            source_end_time = (
+                                source_op_time + source_port.latency_offset
+                            )
+
+                        if current_input.latency_offset is None:
+                            raise ValueError(
+                                f"Input port {current_input.index} of operation"
+                                f" {current_input.operation.graph_id} has no"
+                                " latency-offset."
+                            )
+                        op_start_time_from_in = (
+                            source_end_time - current_input.latency_offset
+                        )
+                        op_start_time = max(op_start_time, op_start_time_from_in)
+
+                    sched._start_times[operation.graph_id] = op_start_time
+
+        self._handle_outputs_and_delays(non_schedulable_ops)
+
+    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:
+            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()):
+            output = cast(Output, output)
+            sched.move_operation_alap(output.graph_id)
+
+        # move all operations ALAP
+        for step in reversed(sched.sfg.get_precedence_list()):
+            for outport in step:
+                if not isinstance(outport.operation, Delay):
+                    sched.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!
+
+        sched = self.schedule
+        prec_list = sched.sfg.get_precedence_list()
+        if len(prec_list) < 2:
+            raise ValueError("Empty signal flow graph cannot be scheduled.")
+
+        # 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
+
+        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
+
+        for outports in prec_list[2:]:
+            # try all remaining operations for one time step
+            candidates = []
+            current_time -= 1
+            while len(candidates) == 0:
+                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)
+
+    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."
+                    )
+                sched._start_times[output.graph_id] = sched._start_times[
+                    source_port.operation.graph_id
+                ] + cast(int, source_port.latency_offset)
+        sched._remove_delays()
diff --git a/b_asic/scheduling_algorithm.py b/b_asic/scheduling_algorithm.py
deleted file mode 100644
index c6787a73..00000000
--- a/b_asic/scheduling_algorithm.py
+++ /dev/null
@@ -1,9 +0,0 @@
-from enum import Enum
-
-
-class SchedAlg(Enum):
-    ASAP = "ASAP"
-    ALAP = "ALAP"
-    EARLIEST_DEADLINE = "earliest_deadline"
-    LEAST_SLACK = "least_slack"
-    PROVIDED = "provided"
diff --git a/pyproject.toml b/pyproject.toml
index ff1ea7b2..21ece32f 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -12,16 +12,17 @@ dependencies = [
   "matplotlib>=3.7",
   "setuptools_scm[toml]>=6.2",
   "networkx>=3",
-  "qtawesome"
+  "qtawesome",
+  "pyqt6",
 ]
 classifiers = [
   "Intended Audience :: Education",
   "Intended Audience :: Science/Research",
   "Programming Language :: Python :: 3",
-  "Programming Language :: Python :: 3.8",
-  "Programming Language :: Python :: 3.9",
   "Programming Language :: Python :: 3.10",
   "Programming Language :: Python :: 3.11",
+  "Programming Language :: Python :: 3.12",
+  "Programming Language :: Python :: 3.13",
   "Programming Language :: C++",
   "License :: OSI Approved :: MIT License",
   "Operating System :: OS Independent",
-- 
GitLab


From 720336ddda37470237b57937d0ee95912b7001f7 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 16:01:21 +0100
Subject: [PATCH 22/52] fixes from mr comments

---
 pyproject.toml | 1 -
 1 file changed, 1 deletion(-)

diff --git a/pyproject.toml b/pyproject.toml
index 21ece32f..a9074708 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -13,7 +13,6 @@ dependencies = [
   "setuptools_scm[toml]>=6.2",
   "networkx>=3",
   "qtawesome",
-  "pyqt6",
 ]
 classifiers = [
   "Intended Audience :: Education",
-- 
GitLab


From e8faeb947d0e1fd4ab471a3c7aec9e9f34a463ef Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 27 Jan 2025 16:57:23 +0100
Subject: [PATCH 23/52] work in progress, seems like feed-forward part of
 direct-form I is working

---
 b_asic/sfg_generators.py | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index df1bf401..96fcdfdc 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -275,6 +275,7 @@ def direct_form_I_iir(
     input_op = Input()
     output = Output()
 
+    # construct the feed-forward part
     muls = [ConstantMultiplication(b[0], input_op, **mult_properties)]
     delays = []
     prev_delay = input_op
@@ -284,10 +285,27 @@ def direct_form_I_iir(
         if i < len(b) - 1:
             muls.append(ConstantMultiplication(coeff, prev_delay, **mult_properties))
 
-    # mul_pairs = [(muls[i], muls[i+1]) for i in range(len(muls)-1)]
-    for i in range(len(muls)):
-        Addition(muls[i], delays[i], **add_properties)
+    op_a = muls[-1]
+    for i in range(len(muls) - 1):
+        op_a = Addition(op_a, muls[-i - 2], **add_properties)
 
+    # construct the feedback part
+    tmp_add = Addition(op_a, None, **add_properties)
+    muls = [ConstantMultiplication(a[0], tmp_add, **mult_properties)]
     output <<= muls[0]
 
+    delays = []
+    prev_delay = muls[0]
+    for i, coeff in enumerate(a[1:]):
+        prev_delay = Delay(prev_delay)
+        delays.append(prev_delay)
+        if i < len(a) - 1:
+            muls.append(ConstantMultiplication(coeff, prev_delay, **mult_properties))
+
+    op_a = muls[-1]
+    for i in range(len(muls) - 1):
+        op_a = Addition(op_a, muls[-i - 2], **add_properties)
+
+    tmp_add.input(1).connect(op_a)
+
     return SFG([input_op], [output], name=Name(name))
-- 
GitLab


From 22e5526cb4f07a897048e0149ff4d3d1f9fc7ed1 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 28 Jan 2025 11:48:59 +0100
Subject: [PATCH 24/52] generators for direct form iir 1 and 2 working, needs
 automatic tests

---
 b_asic/gui_utils/plot_window.py |  6 +-
 b_asic/sfg_generators.py        | 97 ++++++++++++++++++++++++++++++---
 2 files changed, 92 insertions(+), 11 deletions(-)

diff --git a/b_asic/gui_utils/plot_window.py b/b_asic/gui_utils/plot_window.py
index 5322debf..96c3bec5 100644
--- a/b_asic/gui_utils/plot_window.py
+++ b/b_asic/gui_utils/plot_window.py
@@ -6,8 +6,8 @@ from typing import Dict, List, Mapping, Optional, Sequence  # , Union
 
 # from numpy import (array, real, imag, real_if_close, absolute, angle)
 import numpy as np
-from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
-from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
+from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar
 from matplotlib.figure import Figure
 from matplotlib.ticker import MaxNLocator
 from qtpy.QtCore import Qt
@@ -251,7 +251,7 @@ def start_simulation_dialog(
         The name of the SFG.
     """
     if not QApplication.instance():
-        QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
+        # QApplication.setAttribute(Qt.AA_EnableHighDpiScaling)
         app = QApplication(sys.argv)
     else:
         app = QApplication.instance()
diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index 96fcdfdc..97cf1ced 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -257,16 +257,15 @@ def transposed_direct_form_fir(
     return SFG([input_op], [output], name=Name(name))
 
 
-def direct_form_I_iir(
+def direct_form_1_iir(
     b: Sequence[complex],
     a: Sequence[complex],
     name: Optional[str] = None,
     mult_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
     add_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
 ) -> SFG:
-    # np_b = np.atleast_1d(np.squeeze(np.asarray(b)))
-    # np_a = np.atleast_1d(np.squeeze(np.asarray(a)))
-
+    if name is None:
+        name = "Direct-form I IIR filter"
     if mult_properties is None:
         mult_properties = {}
     if add_properties is None:
@@ -291,16 +290,17 @@ def direct_form_I_iir(
 
     # construct the feedback part
     tmp_add = Addition(op_a, None, **add_properties)
-    muls = [ConstantMultiplication(a[0], tmp_add, **mult_properties)]
-    output <<= muls[0]
+    # muls = [ConstantMultiplication(a[0], tmp_add, **mult_properties)]
+    muls = []
+    output <<= tmp_add
 
     delays = []
-    prev_delay = muls[0]
+    prev_delay = tmp_add
     for i, coeff in enumerate(a[1:]):
         prev_delay = Delay(prev_delay)
         delays.append(prev_delay)
         if i < len(a) - 1:
-            muls.append(ConstantMultiplication(coeff, prev_delay, **mult_properties))
+            muls.append(ConstantMultiplication(-coeff, prev_delay, **mult_properties))
 
     op_a = muls[-1]
     for i in range(len(muls) - 1):
@@ -309,3 +309,84 @@ def direct_form_I_iir(
     tmp_add.input(1).connect(op_a)
 
     return SFG([input_op], [output], name=Name(name))
+
+
+def direct_form_2_iir(
+    b: Sequence[complex],
+    a: Sequence[complex],
+    name: Optional[str] = None,
+    mult_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
+    add_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
+) -> SFG:
+    if name is None:
+        name = "Direct-form I IIR filter"
+    if mult_properties is None:
+        mult_properties = {}
+    if add_properties is None:
+        add_properties = {}
+
+    input_op = Input()
+
+    left_adds = []
+    right_adds = []
+    left_muls = []
+    right_muls = []
+    delays = [Delay()]
+
+    if len(a) != len(b):
+        raise ValueError("size of coefficient lists a and b are not the same")
+
+    # all except the final
+    op_a_left = None
+    op_a_right = None
+    for i in range(len(a) - 1):
+        a_coeff = a[-i - 1]
+        b_coeff = b[-i - 1]
+        if len(left_muls) != 0:  # not first iteration
+            new_delay = Delay()
+            delays[-1] <<= new_delay
+            delays.append(new_delay)
+        left_muls.append(
+            ConstantMultiplication(-a_coeff, delays[-1], **mult_properties)
+        )
+        right_muls.append(
+            ConstantMultiplication(b_coeff, delays[-1], **mult_properties)
+        )
+        if len(left_muls) > 1:  # not first iteration
+            left_adds.append(Addition(op_a_left, left_muls[-1], **add_properties))
+            right_adds.append(Addition(op_a_right, right_muls[-1], **add_properties))
+            op_a_left = left_adds[-1]
+            op_a_right = right_adds[-1]
+        else:
+            op_a_left = left_muls[-1]
+            op_a_right = right_muls[-1]
+
+    # finalize
+    if left_adds:
+        left_adds.append(Addition(input_op, left_adds[-1], **add_properties))
+    else:
+        left_adds.append(Addition(input_op, left_muls[-1], **add_properties))
+    delays[-1] <<= left_adds[-1]
+    mul = ConstantMultiplication(b[0], left_adds[-1], **mult_properties)
+    add = Addition(mul, right_adds[-1], **add_properties)
+
+    # for i, coeff in enumerate(list(reversed(a[1:]))):
+    #     if len(left_muls) != 0: # not first iteration
+    #         new_delay = Delay()
+    #         delays[-1] <<= new_delay
+    #         delays.append(new_delay)
+    #     left_muls.append(ConstantMultiplication(-coeff, delays[-1], **mult_properties))
+    #     if len(left_muls) > 1: # not first iteration
+    #         left_adds.append(Addition(op_a, left_muls[-1], **add_properties))
+    #         op_a = left_adds[-1]
+    #     else:
+    #         op_a = left_muls[-1]
+    # if left_adds:
+    #     left_adds.append(Addition(input_op, left_adds[-1], **add_properties))
+    # else:
+    #     left_adds.append(Addition(input_op, left_muls[-1], **add_properties))
+    # delays[-1] <<= left_adds[-1]
+
+    output = Output()
+    output <<= add
+    return SFG([input_op], [output], name=Name(name))
-- 
GitLab


From 96c159b8bb279750a29f3ba44c4e9431bf0143a7 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 28 Jan 2025 11:49:43 +0100
Subject: [PATCH 25/52] removed commented code

---
 b_asic/sfg_generators.py | 17 -----------------
 1 file changed, 17 deletions(-)

diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index 97cf1ced..910b3fd6 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -370,23 +370,6 @@ def direct_form_2_iir(
     mul = ConstantMultiplication(b[0], left_adds[-1], **mult_properties)
     add = Addition(mul, right_adds[-1], **add_properties)
 
-    # for i, coeff in enumerate(list(reversed(a[1:]))):
-    #     if len(left_muls) != 0: # not first iteration
-    #         new_delay = Delay()
-    #         delays[-1] <<= new_delay
-    #         delays.append(new_delay)
-    #     left_muls.append(ConstantMultiplication(-coeff, delays[-1], **mult_properties))
-    #     if len(left_muls) > 1: # not first iteration
-    #         left_adds.append(Addition(op_a, left_muls[-1], **add_properties))
-    #         op_a = left_adds[-1]
-    #     else:
-    #         op_a = left_muls[-1]
-    # if left_adds:
-    #     left_adds.append(Addition(input_op, left_adds[-1], **add_properties))
-    # else:
-    #     left_adds.append(Addition(input_op, left_muls[-1], **add_properties))
-    # delays[-1] <<= left_adds[-1]
-
     output = Output()
     output <<= add
     return SFG([input_op], [output], name=Name(name))
-- 
GitLab


From 6698c9a9429b2e722ae4f4b88e249d0a5be523ce Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 28 Jan 2025 11:59:46 +0100
Subject: [PATCH 26/52] improved some of the styling

---
 b_asic/sfg_generators.py | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index 910b3fd6..789cd161 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -264,6 +264,8 @@ def direct_form_1_iir(
     mult_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
     add_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
 ) -> SFG:
+    if len(a) != len(b):
+        raise ValueError("size of coefficient lists a and b are not the same")
     if name is None:
         name = "Direct-form I IIR filter"
     if mult_properties is None:
@@ -271,10 +273,8 @@ def direct_form_1_iir(
     if add_properties is None:
         add_properties = {}
 
-    input_op = Input()
-    output = Output()
-
     # construct the feed-forward part
+    input_op = Input()
     muls = [ConstantMultiplication(b[0], input_op, **mult_properties)]
     delays = []
     prev_delay = input_op
@@ -290,8 +290,8 @@ def direct_form_1_iir(
 
     # construct the feedback part
     tmp_add = Addition(op_a, None, **add_properties)
-    # muls = [ConstantMultiplication(a[0], tmp_add, **mult_properties)]
     muls = []
+    output = Output()
     output <<= tmp_add
 
     delays = []
@@ -318,6 +318,8 @@ def direct_form_2_iir(
     mult_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
     add_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
 ) -> SFG:
+    if len(a) != len(b):
+        raise ValueError("size of coefficient lists a and b are not the same")
     if name is None:
         name = "Direct-form I IIR filter"
     if mult_properties is None:
@@ -325,18 +327,12 @@ def direct_form_2_iir(
     if add_properties is None:
         add_properties = {}
 
-    input_op = Input()
-
+    # construct the repeated part of the SFG
     left_adds = []
     right_adds = []
     left_muls = []
     right_muls = []
     delays = [Delay()]
-
-    if len(a) != len(b):
-        raise ValueError("size of coefficient lists a and b are not the same")
-
-    # all except the final
     op_a_left = None
     op_a_right = None
     for i in range(len(a) - 1):
@@ -361,7 +357,8 @@ def direct_form_2_iir(
             op_a_left = left_muls[-1]
             op_a_right = right_muls[-1]
 
-    # finalize
+    # finalize the SFG
+    input_op = Input()
     if left_adds:
         left_adds.append(Addition(input_op, left_adds[-1], **add_properties))
     else:
@@ -369,7 +366,6 @@ def direct_form_2_iir(
     delays[-1] <<= left_adds[-1]
     mul = ConstantMultiplication(b[0], left_adds[-1], **mult_properties)
     add = Addition(mul, right_adds[-1], **add_properties)
-
     output = Output()
     output <<= add
     return SFG([input_op], [output], name=Name(name))
-- 
GitLab


From 11fc50e9f68b8cee32c3c27cf079172c7b6a3d10 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 28 Jan 2025 12:40:25 +0100
Subject: [PATCH 27/52] added docstrings

---
 b_asic/sfg_generators.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index 789cd161..998c20a8 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -264,6 +264,7 @@ def direct_form_1_iir(
     mult_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
     add_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
 ) -> SFG:
+    """Generates a direct-form IIR filter of type I with coefficients a and b."""
     if len(a) != len(b):
         raise ValueError("size of coefficient lists a and b are not the same")
     if name is None:
@@ -318,6 +319,7 @@ def direct_form_2_iir(
     mult_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
     add_properties: Optional[Union[Dict[str, int], Dict[str, Dict[str, int]]]] = None,
 ) -> SFG:
+    """Generates a direct-form IIR filter of type II with coefficients a and b."""
     if len(a) != len(b):
         raise ValueError("size of coefficient lists a and b are not the same")
     if name is None:
-- 
GitLab


From f505a53c32c906f95d8b53581615db9aea0d969d Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 28 Jan 2025 16:24:56 +0100
Subject: [PATCH 28/52] redid schedule/scheduler interface to new standard

---
 b_asic/schedule.py  |  28 ++++----
 b_asic/scheduler.py | 159 ++++++++++++++++++++++----------------------
 2 files changed, 91 insertions(+), 96 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index a708fe1a..d1c63041 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
@@ -94,12 +94,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 +111,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
+            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 +122,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 +391,12 @@ 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[GraphID, 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 +768,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..30089db2 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,30 +1,48 @@
-from enum import Enum
+from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, cast
 
-from b_asic.operation import Operation
 from b_asic.port import OutputPort
 from b_asic.special_operations import Delay, 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 SchedulingAlgorithm(Enum):
+#     ASAP = "ASAP"
+#     ALAP = "ALAP"
+#     EARLIEST_DEADLINE = "earliest_deadline"
+#     # LEAST_SLACK = "least_slack" # to be implemented
+#     PROVIDED = "provided"
 
 
-class Scheduler:
-    def __init__(self, schedule: "Schedule") -> None:
-        self.schedule = schedule
+class Scheduler(ABC):
+    @abstractmethod
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        pass
 
-    def schedule_asap(self) -> None:
-        """Schedule the operations using as-soon-as-possible scheduling."""
-        sched = self.schedule
-        prec_list = sched.sfg.get_precedence_list()
+    def _handle_outputs(self, schedule, non_schedulable_ops) -> 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):
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        prec_list = schedule.sfg.get_precedence_list()
         if len(prec_list) < 2:
             raise ValueError("Empty signal flow graph cannot be scheduled.")
 
@@ -34,21 +52,19 @@ 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:
@@ -64,7 +80,7 @@ class Scheduler:
                         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,41 +107,42 @@ 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(schedule, non_schedulable_ops)
+        schedule.remove_delays()
 
-        self._handle_outputs_and_delays(non_schedulable_ops)
 
-    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()
+class ALAPScheduler(Scheduler):
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        # self.schedule_asap()
+        ASAPScheduler().apply_scheduling(schedule)
+        max_end_time = schedule.get_max_end_time()
 
-        if sched.schedule_time is None:
-            sched.set_schedule_time(max_end_time)
-        elif sched.schedule_time < 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):
+    def __init__(self, max_resources: dict[TypeName, int]) -> None:
+        self._max_resources = max_resources
 
-        sched = self.schedule
-        prec_list = sched.sfg.get_precedence_list()
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        # ACT BASED ON THE NUMBER OF PEs!
+        prec_list = schedule.sfg.get_precedence_list()
         if len(prec_list) < 2:
             raise ValueError("Empty signal flow graph cannot be scheduled.")
 
@@ -135,8 +152,8 @@ 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:
-                sched._start_times[operation.graph_id] = 0
+            elif operation.graph_id not in schedule.start_times:
+                schedule.start_times[operation.graph_id] = 0
 
         current_time = 0
         sorted_outports = sorted(
@@ -144,50 +161,30 @@ class Scheduler:
         )
         for outport in sorted_outports:
             op = outport.operation
-            sched._start_times[op.graph_id] = current_time
+            schedule.start_times[op.graph_id] = current_time
             current_time += 1
 
         for outports in prec_list[2:]:
-            # try all remaining operations for one time step
-            candidates = []
             current_time -= 1
-            while len(candidates) == 0:
-                current_time += 1
-                for outport in outports:
-                    remaining_op = outport.operation
+            for outport in outports:
+                op = outport.operation
+                candidates = []
+                while not candidates:
+                    current_time += 1
                     op_is_ready_to_be_scheduled = True
-                    for op_input in remaining_op.inputs:
+                    for op_input in op.inputs:
                         source_op = op_input.signals[0].source.operation
-                        source_op_time = sched.start_times[source_op.graph_id]
+                        source_op_time = schedule.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)
-
-    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."
-                    )
-                sched._start_times[output.graph_id] = sched._start_times[
-                    source_port.operation.graph_id
-                ] + cast(int, source_port.latency_offset)
-        sched._remove_delays()
+                        candidates.append(op)
+                sorted_candidates = sorted(
+                    candidates, key=lambda candidate: candidate.latency
+                )
+                # schedule the best candidate to current time
+                schedule.start_times[sorted_candidates[0].graph_id] = current_time
+
+        self._handle_outputs(schedule, non_schedulable_ops)
+        schedule.remove_delays()
-- 
GitLab


From 3b2d67c2082088c990f43a5adbaf0c657cfd8a92 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 09:18:50 +0100
Subject: [PATCH 29/52] tests now working

---
 b_asic/schedule.py          | 11 ++---
 b_asic/signal_flow_graph.py |  3 +-
 test/fixtures/schedule.py   |  9 ++--
 test/test_architecture.py   |  3 +-
 test/test_schedule.py       | 83 +++++++++++++++++++------------------
 test/test_scheduler_gui.py  |  3 +-
 6 files changed, 58 insertions(+), 54 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index d1c63041..acb6572b 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -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
@@ -113,7 +108,7 @@ class Schedule:
 
         if scheduler:
             self._scheduler = scheduler
-            scheduler.apply_scheduling(self)
+            self._scheduler.apply_scheduling(self)
         else:
             if start_times is None:
                 raise ValueError("Must provide start_times when using 'provided'")
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/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/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_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


From 11845eb03acf61a2213f6f133dfa7e4772c29939 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 09:31:46 +0100
Subject: [PATCH 30/52] updated examples to not fail

---
 examples/fivepointwinograddft.py                  | 3 ++-
 examples/folding_example_with_architecture.py     | 3 ++-
 examples/lwdfallpass.py                           | 3 ++-
 examples/secondorderdirectformiir.py              | 3 ++-
 examples/secondorderdirectformiir_architecture.py | 3 ++-
 examples/thirdorderblwdf.py                       | 3 ++-
 examples/threepointwinograddft.py                 | 3 ++-
 7 files changed, 14 insertions(+), 7 deletions(-)

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()
 
 # %%
-- 
GitLab


From 823bb689a55cde659f2fb082d217f81636eee3d3 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 09:50:23 +0100
Subject: [PATCH 31/52] added docstrings for scheduler

---
 b_asic/scheduler.py | 57 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 46 insertions(+), 11 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 30089db2..554ce97e 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,5 +1,5 @@
 from abc import ABC, abstractmethod
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, Optional, cast
 
 from b_asic.port import OutputPort
 from b_asic.special_operations import Delay, Output
@@ -9,17 +9,16 @@ 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.
+        """
         pass
 
     def _handle_outputs(self, schedule, non_schedulable_ops) -> None:
@@ -41,7 +40,16 @@ class Scheduler(ABC):
 
 
 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.
+
+        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.")
@@ -114,8 +122,16 @@ class ASAPScheduler(Scheduler):
 
 
 class ALAPScheduler(Scheduler):
+    """Scheduler that implements the as-late-as-possible (ALAP) algorithm."""
+
     def apply_scheduling(self, schedule: "Schedule") -> None:
-        # self.schedule_asap()
+        """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()
 
@@ -137,10 +153,29 @@ class ALAPScheduler(Scheduler):
 
 
 class EarliestDeadlineScheduler(Scheduler):
-    def __init__(self, max_resources: dict[TypeName, int]) -> None:
+    """
+    Scheduler that implements the earliest-deadline-first algorithm.
+
+    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.
+    """
+
+    def __init__(self, max_resources: Optional[dict[TypeName, int]]) -> None:
         self._max_resources = 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.
+        """
         # ACT BASED ON THE NUMBER OF PEs!
         prec_list = schedule.sfg.get_precedence_list()
         if len(prec_list) < 2:
-- 
GitLab


From bc0326a1bf38e5b31502c79c028ec734ccbc0923 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 15:38:11 +0100
Subject: [PATCH 32/52] seems like its working for execution time 1

---
 b_asic/scheduler.py | 122 +++++++++++++++++++++++++++-----------------
 1 file changed, 76 insertions(+), 46 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 554ce97e..a7d1b85a 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,8 +1,9 @@
 from abc import ABC, abstractmethod
+from collections import defaultdict
 from typing import TYPE_CHECKING, Optional, cast
 
 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:
@@ -21,7 +22,7 @@ class Scheduler(ABC):
         """
         pass
 
-    def _handle_outputs(self, schedule, non_schedulable_ops) -> None:
+    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)
@@ -165,8 +166,11 @@ class EarliestDeadlineScheduler(Scheduler):
         resource is used.
     """
 
-    def __init__(self, max_resources: Optional[dict[TypeName, int]]) -> None:
-        self._max_resources = max_resources
+    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.
@@ -176,50 +180,76 @@ class EarliestDeadlineScheduler(Scheduler):
         schedule : Schedule
             Schedule to apply the scheduling algorithm on.
         """
-        # ACT BASED ON THE NUMBER OF PEs!
-        prec_list = schedule.sfg.get_precedence_list()
-        if len(prec_list) < 2:
-            raise ValueError("Empty signal flow graph cannot be scheduled.")
 
-        # 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 schedule.start_times:
-                schedule.start_times[operation.graph_id] = 0
+        # TODO: Take the execution time into consideration!
 
+        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)
+
+        remaining_ops = set(schedule.start_times.keys())
+        remaining_ops = {elem for elem in remaining_ops if not elem.startswith("in")}
+
+        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
-            schedule.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
+            )
+
+            if not best_candidate:
+                current_time += 1
+                remaining_resources = self._max_resources.copy()
+                continue
+
+            # update remaining resources
+            if best_candidate.type_name() in remaining_resources:
+                remaining_resources[best_candidate.type_name()] -= 1
+
+            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
+    ):
+        # precompute source end times for faster checks
+        sfg = schedule.sfg
+        source_end_times = defaultdict(float)
+        for op_id in remaining_ops:
+            operation = sfg.find_by_id(op_id)
+            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,
+                    )
 
-        for outports in prec_list[2:]:
-            current_time -= 1
-            for outport in outports:
-                op = outport.operation
-                candidates = []
-                while not candidates:
-                    current_time += 1
-                    op_is_ready_to_be_scheduled = True
-                    for op_input in op.inputs:
-                        source_op = op_input.signals[0].source.operation
-                        source_op_time = schedule.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(op)
-                sorted_candidates = sorted(
-                    candidates, key=lambda candidate: candidate.latency
-                )
-                # schedule the best candidate to current time
-                schedule.start_times[sorted_candidates[0].graph_id] = current_time
+        best_candidate = None
+        best_deadline = float('inf')
+        for op_id in remaining_ops:
+            operation = sfg.find_by_id(op_id)
 
-        self._handle_outputs(schedule, non_schedulable_ops)
-        schedule.remove_delays()
+            # 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
-- 
GitLab


From 081823ce1dd6272e474d9968fc5197b01c81b0f7 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Thu, 30 Jan 2025 11:00:26 +0100
Subject: [PATCH 33/52] there is a bug that can occur when an op is scheduled
 before predecessors

---
 b_asic/scheduler.py | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index a7d1b85a..b17b4842 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -181,7 +181,7 @@ class EarliestDeadlineScheduler(Scheduler):
             Schedule to apply the scheduling algorithm on.
         """
 
-        # TODO: Take the execution time into consideration!
+        # TODO: Solve bug where operation (ADD) is scheduled before proceeding (MUL) -> pipelining
 
         ALAPScheduler().apply_scheduling(schedule)
 
@@ -190,9 +190,15 @@ class EarliestDeadlineScheduler(Scheduler):
             input_op = cast(Input, input_op)
             schedule.move_operation_asap(input_op.graph_id)
 
+        # construct the set of remaining operations, excluding inputs
         remaining_ops = set(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
         while remaining_ops:
@@ -202,13 +208,23 @@ class EarliestDeadlineScheduler(Scheduler):
 
             if not best_candidate:
                 current_time += 1
-                remaining_resources = self._max_resources.copy()
+
+                # update available operators
+                for operation, ready_time in used_resources_ready_times.items():
+                    print(ready_time, current_time)
+                    if ready_time == current_time:
+                        remaining_resources[operation.type_name()] += 1
+                # remaining_resources = self._max_resources.copy()
                 continue
 
-            # update remaining resources
+            # if the resource is constrained, update remaining resources
             if best_candidate.type_name() in remaining_resources:
                 remaining_resources[best_candidate.type_name()] -= 1
+                used_resources_ready_times[best_candidate] = (
+                    current_time + best_candidate.execution_time
+                )
 
+            # schedule the best candidate to the current time
             remaining_ops.remove(best_candidate.graph_id)
             schedule.start_times[best_candidate.graph_id] = current_time
 
@@ -222,11 +238,16 @@ class EarliestDeadlineScheduler(Scheduler):
     def _find_best_candidate(
         schedule, remaining_ops, remaining_resources, current_time
     ):
-        # precompute source end times for faster checks
         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):
@@ -235,11 +256,6 @@ class EarliestDeadlineScheduler(Scheduler):
                         schedule.start_times[source_op.graph_id] + source_op.latency,
                     )
 
-        best_candidate = None
-        best_deadline = float('inf')
-        for op_id in remaining_ops:
-            operation = sfg.find_by_id(op_id)
-
             # check resource constraints
             if operation.type_name() in remaining_resources:
                 if remaining_resources[operation.type_name()] == 0:
-- 
GitLab


From d1cde303a460672b1228cfc26cbe93daaeab1348 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Thu, 30 Jan 2025 14:14:56 +0100
Subject: [PATCH 34/52] solved previous bug, now possible to schedule for
 different execution times, next step will be to write automatic tests

---
 b_asic/scheduler.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index b17b4842..cc4086d9 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,3 +1,4 @@
+import sys
 from abc import ABC, abstractmethod
 from collections import defaultdict
 from typing import TYPE_CHECKING, Optional, cast
@@ -181,8 +182,6 @@ class EarliestDeadlineScheduler(Scheduler):
             Schedule to apply the scheduling algorithm on.
         """
 
-        # TODO: Solve bug where operation (ADD) is scheduled before proceeding (MUL) -> pipelining
-
         ALAPScheduler().apply_scheduling(schedule)
 
         # move all inputs ASAP to ensure correct operation
@@ -211,7 +210,6 @@ class EarliestDeadlineScheduler(Scheduler):
 
                 # update available operators
                 for operation, ready_time in used_resources_ready_times.items():
-                    print(ready_time, current_time)
                     if ready_time == current_time:
                         remaining_resources[operation.type_name()] += 1
                 # remaining_resources = self._max_resources.copy()
@@ -255,6 +253,9 @@ class EarliestDeadlineScheduler(Scheduler):
                         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:
-- 
GitLab


From b8f2960ebf184e70294f4afbda32817006e91b66 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Fri, 31 Jan 2025 14:16:36 +0000
Subject: [PATCH 35/52] Apply 1 suggestion(s) to 1 file(s)

Co-authored-by: Oscar Gustafsson <oscar.gustafsson@liu.se>
---
 b_asic/sfg_generators.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index 998c20a8..2b693af3 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -323,7 +323,7 @@ def direct_form_2_iir(
     if len(a) != len(b):
         raise ValueError("size of coefficient lists a and b are not the same")
     if name is None:
-        name = "Direct-form I IIR filter"
+        name = "Direct-form II IIR filter"
     if mult_properties is None:
         mult_properties = {}
     if add_properties is None:
-- 
GitLab


From 012aaf3137919bdfbce8344ddde771fb6eedacfb Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Fri, 31 Jan 2025 16:43:15 +0100
Subject: [PATCH 36/52] fixed start_times setter

---
 b_asic/schedule.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index acb6572b..4c7f01b6 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -388,8 +388,11 @@ class Schedule:
 
     @start_times.setter
     def start_times(self, start_times: dict[GraphID, int]) -> None:
-        if not isinstance(start_times, dict[GraphID, int]):
-            raise TypeError("start_times must be a dict[GraphID, int]")
+        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
-- 
GitLab


From 23f06d335c5fcf79a17182405a9cfb07bd971966 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Fri, 31 Jan 2025 20:04:00 +0100
Subject: [PATCH 37/52] added tests, did some bug fixes and added .coveragerc
 file for setting up excludes for code coverage

---
 .coveragerc                        |   4 +
 .gitignore                         |   1 +
 README.md                          |   6 +-
 b_asic/__init__.py                 |   1 +
 b_asic/scheduler.py                |  25 ++-
 pyproject.toml                     |   4 +
 test/fixtures/signal_flow_graph.py |   8 +
 test/test_scheduler.py             | 259 +++++++++++++++++++++++++++++
 8 files changed, 290 insertions(+), 18 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/README.md b/README.md
index 074f2828..8f6a2984 100644
--- a/README.md
+++ b/README.md
@@ -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/__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/scheduler.py b/b_asic/scheduler.py
index cc4086d9..e109d01d 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -21,7 +21,7 @@ class Scheduler(ABC):
         schedule : Schedule
             Schedule to apply the scheduling algorithm on.
         """
-        pass
+        raise NotImplementedError
 
     def _handle_outputs(self, schedule, non_schedulable_ops=set()) -> None:
         for output in schedule.sfg.find_by_type_name(Output.type_name()):
@@ -77,14 +77,6 @@ class ASAPScheduler(Scheduler):
                 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:
@@ -190,8 +182,8 @@ class EarliestDeadlineScheduler(Scheduler):
             schedule.move_operation_asap(input_op.graph_id)
 
         # construct the set of remaining operations, excluding inputs
-        remaining_ops = set(schedule.start_times.keys())
-        remaining_ops = {elem for elem in remaining_ops if not elem.startswith("in")}
+        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 = {}
@@ -218,9 +210,14 @@ class EarliestDeadlineScheduler(Scheduler):
             # if the resource is constrained, update remaining resources
             if best_candidate.type_name() in remaining_resources:
                 remaining_resources[best_candidate.type_name()] -= 1
-                used_resources_ready_times[best_candidate] = (
-                    current_time + best_candidate.execution_time
-                )
+                if best_candidate.execution_time:
+                    used_resources_ready_times[best_candidate] = (
+                        current_time + best_candidate.execution_time
+                    )
+                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)
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/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_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
-- 
GitLab


From 87bf24d65e50b918754ccdfd4898adb46978ccd9 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 28 Jan 2025 16:24:56 +0100
Subject: [PATCH 38/52] redid schedule/scheduler interface to new standard

---
 b_asic/schedule.py  |  28 ++++----
 b_asic/scheduler.py | 159 ++++++++++++++++++++++----------------------
 2 files changed, 91 insertions(+), 96 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index a708fe1a..d1c63041 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
@@ -94,12 +94,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 +111,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
+            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 +122,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 +391,12 @@ 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[GraphID, 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 +768,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..30089db2 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,30 +1,48 @@
-from enum import Enum
+from abc import ABC, abstractmethod
 from typing import TYPE_CHECKING, cast
 
-from b_asic.operation import Operation
 from b_asic.port import OutputPort
 from b_asic.special_operations import Delay, 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 SchedulingAlgorithm(Enum):
+#     ASAP = "ASAP"
+#     ALAP = "ALAP"
+#     EARLIEST_DEADLINE = "earliest_deadline"
+#     # LEAST_SLACK = "least_slack" # to be implemented
+#     PROVIDED = "provided"
 
 
-class Scheduler:
-    def __init__(self, schedule: "Schedule") -> None:
-        self.schedule = schedule
+class Scheduler(ABC):
+    @abstractmethod
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        pass
 
-    def schedule_asap(self) -> None:
-        """Schedule the operations using as-soon-as-possible scheduling."""
-        sched = self.schedule
-        prec_list = sched.sfg.get_precedence_list()
+    def _handle_outputs(self, schedule, non_schedulable_ops) -> 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):
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        prec_list = schedule.sfg.get_precedence_list()
         if len(prec_list) < 2:
             raise ValueError("Empty signal flow graph cannot be scheduled.")
 
@@ -34,21 +52,19 @@ 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:
@@ -64,7 +80,7 @@ class Scheduler:
                         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,41 +107,42 @@ 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(schedule, non_schedulable_ops)
+        schedule.remove_delays()
 
-        self._handle_outputs_and_delays(non_schedulable_ops)
 
-    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()
+class ALAPScheduler(Scheduler):
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        # self.schedule_asap()
+        ASAPScheduler().apply_scheduling(schedule)
+        max_end_time = schedule.get_max_end_time()
 
-        if sched.schedule_time is None:
-            sched.set_schedule_time(max_end_time)
-        elif sched.schedule_time < 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):
+    def __init__(self, max_resources: dict[TypeName, int]) -> None:
+        self._max_resources = max_resources
 
-        sched = self.schedule
-        prec_list = sched.sfg.get_precedence_list()
+    def apply_scheduling(self, schedule: "Schedule") -> None:
+        # ACT BASED ON THE NUMBER OF PEs!
+        prec_list = schedule.sfg.get_precedence_list()
         if len(prec_list) < 2:
             raise ValueError("Empty signal flow graph cannot be scheduled.")
 
@@ -135,8 +152,8 @@ 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:
-                sched._start_times[operation.graph_id] = 0
+            elif operation.graph_id not in schedule.start_times:
+                schedule.start_times[operation.graph_id] = 0
 
         current_time = 0
         sorted_outports = sorted(
@@ -144,50 +161,30 @@ class Scheduler:
         )
         for outport in sorted_outports:
             op = outport.operation
-            sched._start_times[op.graph_id] = current_time
+            schedule.start_times[op.graph_id] = current_time
             current_time += 1
 
         for outports in prec_list[2:]:
-            # try all remaining operations for one time step
-            candidates = []
             current_time -= 1
-            while len(candidates) == 0:
-                current_time += 1
-                for outport in outports:
-                    remaining_op = outport.operation
+            for outport in outports:
+                op = outport.operation
+                candidates = []
+                while not candidates:
+                    current_time += 1
                     op_is_ready_to_be_scheduled = True
-                    for op_input in remaining_op.inputs:
+                    for op_input in op.inputs:
                         source_op = op_input.signals[0].source.operation
-                        source_op_time = sched.start_times[source_op.graph_id]
+                        source_op_time = schedule.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)
-
-    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."
-                    )
-                sched._start_times[output.graph_id] = sched._start_times[
-                    source_port.operation.graph_id
-                ] + cast(int, source_port.latency_offset)
-        sched._remove_delays()
+                        candidates.append(op)
+                sorted_candidates = sorted(
+                    candidates, key=lambda candidate: candidate.latency
+                )
+                # schedule the best candidate to current time
+                schedule.start_times[sorted_candidates[0].graph_id] = current_time
+
+        self._handle_outputs(schedule, non_schedulable_ops)
+        schedule.remove_delays()
-- 
GitLab


From c9c9e1bc6d05903c1aafd6da773d13def2b6474d Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 09:18:50 +0100
Subject: [PATCH 39/52] tests now working

---
 b_asic/schedule.py          | 11 ++---
 b_asic/signal_flow_graph.py |  3 +-
 test/fixtures/schedule.py   |  9 ++--
 test/test_architecture.py   |  3 +-
 test/test_schedule.py       | 83 +++++++++++++++++++------------------
 test/test_scheduler_gui.py  |  3 +-
 6 files changed, 58 insertions(+), 54 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index d1c63041..acb6572b 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -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
@@ -113,7 +108,7 @@ class Schedule:
 
         if scheduler:
             self._scheduler = scheduler
-            scheduler.apply_scheduling(self)
+            self._scheduler.apply_scheduling(self)
         else:
             if start_times is None:
                 raise ValueError("Must provide start_times when using 'provided'")
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/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/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_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


From 4303787cfb23f6a008f91eb51898cfcfd61f5406 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 09:31:46 +0100
Subject: [PATCH 40/52] updated examples to not fail

---
 examples/fivepointwinograddft.py                  | 3 ++-
 examples/folding_example_with_architecture.py     | 3 ++-
 examples/lwdfallpass.py                           | 3 ++-
 examples/secondorderdirectformiir.py              | 3 ++-
 examples/secondorderdirectformiir_architecture.py | 3 ++-
 examples/thirdorderblwdf.py                       | 3 ++-
 examples/threepointwinograddft.py                 | 3 ++-
 7 files changed, 14 insertions(+), 7 deletions(-)

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()
 
 # %%
-- 
GitLab


From d5935e9d614be17586c743feaf3b57833539cc11 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 09:50:23 +0100
Subject: [PATCH 41/52] added docstrings for scheduler

---
 b_asic/scheduler.py | 57 ++++++++++++++++++++++++++++++++++++---------
 1 file changed, 46 insertions(+), 11 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 30089db2..554ce97e 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,5 +1,5 @@
 from abc import ABC, abstractmethod
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, Optional, cast
 
 from b_asic.port import OutputPort
 from b_asic.special_operations import Delay, Output
@@ -9,17 +9,16 @@ 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.
+        """
         pass
 
     def _handle_outputs(self, schedule, non_schedulable_ops) -> None:
@@ -41,7 +40,16 @@ class Scheduler(ABC):
 
 
 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.
+
+        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.")
@@ -114,8 +122,16 @@ class ASAPScheduler(Scheduler):
 
 
 class ALAPScheduler(Scheduler):
+    """Scheduler that implements the as-late-as-possible (ALAP) algorithm."""
+
     def apply_scheduling(self, schedule: "Schedule") -> None:
-        # self.schedule_asap()
+        """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()
 
@@ -137,10 +153,29 @@ class ALAPScheduler(Scheduler):
 
 
 class EarliestDeadlineScheduler(Scheduler):
-    def __init__(self, max_resources: dict[TypeName, int]) -> None:
+    """
+    Scheduler that implements the earliest-deadline-first algorithm.
+
+    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.
+    """
+
+    def __init__(self, max_resources: Optional[dict[TypeName, int]]) -> None:
         self._max_resources = 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.
+        """
         # ACT BASED ON THE NUMBER OF PEs!
         prec_list = schedule.sfg.get_precedence_list()
         if len(prec_list) < 2:
-- 
GitLab


From 87268b645b76b1a7aa7af3d67444987853eac04e Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 29 Jan 2025 15:38:11 +0100
Subject: [PATCH 42/52] seems like its working for execution time 1

---
 b_asic/scheduler.py | 122 +++++++++++++++++++++++++++-----------------
 1 file changed, 76 insertions(+), 46 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 554ce97e..a7d1b85a 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,8 +1,9 @@
 from abc import ABC, abstractmethod
+from collections import defaultdict
 from typing import TYPE_CHECKING, Optional, cast
 
 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:
@@ -21,7 +22,7 @@ class Scheduler(ABC):
         """
         pass
 
-    def _handle_outputs(self, schedule, non_schedulable_ops) -> None:
+    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)
@@ -165,8 +166,11 @@ class EarliestDeadlineScheduler(Scheduler):
         resource is used.
     """
 
-    def __init__(self, max_resources: Optional[dict[TypeName, int]]) -> None:
-        self._max_resources = max_resources
+    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.
@@ -176,50 +180,76 @@ class EarliestDeadlineScheduler(Scheduler):
         schedule : Schedule
             Schedule to apply the scheduling algorithm on.
         """
-        # ACT BASED ON THE NUMBER OF PEs!
-        prec_list = schedule.sfg.get_precedence_list()
-        if len(prec_list) < 2:
-            raise ValueError("Empty signal flow graph cannot be scheduled.")
 
-        # 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 schedule.start_times:
-                schedule.start_times[operation.graph_id] = 0
+        # TODO: Take the execution time into consideration!
 
+        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)
+
+        remaining_ops = set(schedule.start_times.keys())
+        remaining_ops = {elem for elem in remaining_ops if not elem.startswith("in")}
+
+        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
-            schedule.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
+            )
+
+            if not best_candidate:
+                current_time += 1
+                remaining_resources = self._max_resources.copy()
+                continue
+
+            # update remaining resources
+            if best_candidate.type_name() in remaining_resources:
+                remaining_resources[best_candidate.type_name()] -= 1
+
+            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
+    ):
+        # precompute source end times for faster checks
+        sfg = schedule.sfg
+        source_end_times = defaultdict(float)
+        for op_id in remaining_ops:
+            operation = sfg.find_by_id(op_id)
+            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,
+                    )
 
-        for outports in prec_list[2:]:
-            current_time -= 1
-            for outport in outports:
-                op = outport.operation
-                candidates = []
-                while not candidates:
-                    current_time += 1
-                    op_is_ready_to_be_scheduled = True
-                    for op_input in op.inputs:
-                        source_op = op_input.signals[0].source.operation
-                        source_op_time = schedule.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(op)
-                sorted_candidates = sorted(
-                    candidates, key=lambda candidate: candidate.latency
-                )
-                # schedule the best candidate to current time
-                schedule.start_times[sorted_candidates[0].graph_id] = current_time
+        best_candidate = None
+        best_deadline = float('inf')
+        for op_id in remaining_ops:
+            operation = sfg.find_by_id(op_id)
 
-        self._handle_outputs(schedule, non_schedulable_ops)
-        schedule.remove_delays()
+            # 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
-- 
GitLab


From f36057d7edbe4e43f1768aed8aae719264667bc8 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Thu, 30 Jan 2025 11:00:26 +0100
Subject: [PATCH 43/52] there is a bug that can occur when an op is scheduled
 before predecessors

---
 b_asic/scheduler.py | 34 +++++++++++++++++++++++++---------
 1 file changed, 25 insertions(+), 9 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index a7d1b85a..b17b4842 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -181,7 +181,7 @@ class EarliestDeadlineScheduler(Scheduler):
             Schedule to apply the scheduling algorithm on.
         """
 
-        # TODO: Take the execution time into consideration!
+        # TODO: Solve bug where operation (ADD) is scheduled before proceeding (MUL) -> pipelining
 
         ALAPScheduler().apply_scheduling(schedule)
 
@@ -190,9 +190,15 @@ class EarliestDeadlineScheduler(Scheduler):
             input_op = cast(Input, input_op)
             schedule.move_operation_asap(input_op.graph_id)
 
+        # construct the set of remaining operations, excluding inputs
         remaining_ops = set(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
         while remaining_ops:
@@ -202,13 +208,23 @@ class EarliestDeadlineScheduler(Scheduler):
 
             if not best_candidate:
                 current_time += 1
-                remaining_resources = self._max_resources.copy()
+
+                # update available operators
+                for operation, ready_time in used_resources_ready_times.items():
+                    print(ready_time, current_time)
+                    if ready_time == current_time:
+                        remaining_resources[operation.type_name()] += 1
+                # remaining_resources = self._max_resources.copy()
                 continue
 
-            # update remaining resources
+            # if the resource is constrained, update remaining resources
             if best_candidate.type_name() in remaining_resources:
                 remaining_resources[best_candidate.type_name()] -= 1
+                used_resources_ready_times[best_candidate] = (
+                    current_time + best_candidate.execution_time
+                )
 
+            # schedule the best candidate to the current time
             remaining_ops.remove(best_candidate.graph_id)
             schedule.start_times[best_candidate.graph_id] = current_time
 
@@ -222,11 +238,16 @@ class EarliestDeadlineScheduler(Scheduler):
     def _find_best_candidate(
         schedule, remaining_ops, remaining_resources, current_time
     ):
-        # precompute source end times for faster checks
         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):
@@ -235,11 +256,6 @@ class EarliestDeadlineScheduler(Scheduler):
                         schedule.start_times[source_op.graph_id] + source_op.latency,
                     )
 
-        best_candidate = None
-        best_deadline = float('inf')
-        for op_id in remaining_ops:
-            operation = sfg.find_by_id(op_id)
-
             # check resource constraints
             if operation.type_name() in remaining_resources:
                 if remaining_resources[operation.type_name()] == 0:
-- 
GitLab


From 5ec071d9323de130956d0d124154bd4db3a285fd Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Thu, 30 Jan 2025 14:14:56 +0100
Subject: [PATCH 44/52] solved previous bug, now possible to schedule for
 different execution times, next step will be to write automatic tests

---
 b_asic/scheduler.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index b17b4842..cc4086d9 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -1,3 +1,4 @@
+import sys
 from abc import ABC, abstractmethod
 from collections import defaultdict
 from typing import TYPE_CHECKING, Optional, cast
@@ -181,8 +182,6 @@ class EarliestDeadlineScheduler(Scheduler):
             Schedule to apply the scheduling algorithm on.
         """
 
-        # TODO: Solve bug where operation (ADD) is scheduled before proceeding (MUL) -> pipelining
-
         ALAPScheduler().apply_scheduling(schedule)
 
         # move all inputs ASAP to ensure correct operation
@@ -211,7 +210,6 @@ class EarliestDeadlineScheduler(Scheduler):
 
                 # update available operators
                 for operation, ready_time in used_resources_ready_times.items():
-                    print(ready_time, current_time)
                     if ready_time == current_time:
                         remaining_resources[operation.type_name()] += 1
                 # remaining_resources = self._max_resources.copy()
@@ -255,6 +253,9 @@ class EarliestDeadlineScheduler(Scheduler):
                         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:
-- 
GitLab


From 12402efb9df9f6047cd27ea47d7e4e0f07709384 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Fri, 31 Jan 2025 16:43:15 +0100
Subject: [PATCH 45/52] fixed start_times setter

---
 b_asic/schedule.py | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index acb6572b..4c7f01b6 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -388,8 +388,11 @@ class Schedule:
 
     @start_times.setter
     def start_times(self, start_times: dict[GraphID, int]) -> None:
-        if not isinstance(start_times, dict[GraphID, int]):
-            raise TypeError("start_times must be a dict[GraphID, int]")
+        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
-- 
GitLab


From fd3ffdf63b95eba117dcb3129664351104488e11 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Fri, 31 Jan 2025 20:04:00 +0100
Subject: [PATCH 46/52] added tests, did some bug fixes and added .coveragerc
 file for setting up excludes for code coverage

---
 .coveragerc                        |   4 +
 .gitignore                         |   1 +
 README.md                          |   6 +-
 b_asic/__init__.py                 |   1 +
 b_asic/scheduler.py                |  25 ++-
 pyproject.toml                     |   4 +
 test/fixtures/signal_flow_graph.py |   8 +
 test/test_scheduler.py             | 259 +++++++++++++++++++++++++++++
 8 files changed, 290 insertions(+), 18 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/README.md b/README.md
index 074f2828..8f6a2984 100644
--- a/README.md
+++ b/README.md
@@ -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/__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/scheduler.py b/b_asic/scheduler.py
index cc4086d9..e109d01d 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -21,7 +21,7 @@ class Scheduler(ABC):
         schedule : Schedule
             Schedule to apply the scheduling algorithm on.
         """
-        pass
+        raise NotImplementedError
 
     def _handle_outputs(self, schedule, non_schedulable_ops=set()) -> None:
         for output in schedule.sfg.find_by_type_name(Output.type_name()):
@@ -77,14 +77,6 @@ class ASAPScheduler(Scheduler):
                 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:
@@ -190,8 +182,8 @@ class EarliestDeadlineScheduler(Scheduler):
             schedule.move_operation_asap(input_op.graph_id)
 
         # construct the set of remaining operations, excluding inputs
-        remaining_ops = set(schedule.start_times.keys())
-        remaining_ops = {elem for elem in remaining_ops if not elem.startswith("in")}
+        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 = {}
@@ -218,9 +210,14 @@ class EarliestDeadlineScheduler(Scheduler):
             # if the resource is constrained, update remaining resources
             if best_candidate.type_name() in remaining_resources:
                 remaining_resources[best_candidate.type_name()] -= 1
-                used_resources_ready_times[best_candidate] = (
-                    current_time + best_candidate.execution_time
-                )
+                if best_candidate.execution_time:
+                    used_resources_ready_times[best_candidate] = (
+                        current_time + best_candidate.execution_time
+                    )
+                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)
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/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_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
-- 
GitLab


From ab69992ba104052a7405fe7a98848b3495ab7b59 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 3 Feb 2025 14:54:15 +0000
Subject: [PATCH 47/52] Edit README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 8f6a2984..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/)
-- 
GitLab


From 57685b4603cb24fdb5ae629386fb1b9c8b7fe8b5 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 3 Feb 2025 16:50:03 +0100
Subject: [PATCH 48/52] removed qt5 tests from CI-file

---
 .gitlab-ci.yml | 34 ++++++++++++++--------------------
 1 file changed, 14 insertions(+), 20 deletions(-)

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:
-- 
GitLab


From 7d6742ebaf1c42b82430cd8bb0c5d5b5c4c84777 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 3 Feb 2025 19:02:26 +0100
Subject: [PATCH 49/52] fixed bug where pyside2 was imported even though
 pyside6 used

---
 b_asic/scheduler_gui/main_window.py | 24 +++++++++++++-----------
 1 file changed, 13 insertions(+), 11 deletions(-)

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)
-- 
GitLab


From 8a38ddc02910969470fe3548664b30f45e2084e4 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 3 Feb 2025 19:26:41 +0100
Subject: [PATCH 50/52] updated compile.py to hopefully work with pyside6 and
 pyqt6, also recompiled

---
 b_asic/scheduler_gui/compile.py        | 47 +++-----------------------
 b_asic/scheduler_gui/ui_main_window.py |  2 +-
 2 files changed, 5 insertions(+), 44 deletions(-)

diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py
index 2d9be528..046544d3 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,7 @@ 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:
-            from qtpy.uic import compileUi
-
-            with open(outfile, "w") as ofile:
-                compileUi(filename, ofile)
-        elif uic.PYQT6:
+        if uic.PYSIDE6 or uic.PYQT6:
             uic_ = shutil.which("pyside6-uic")
             arguments = f"-g python -o {outfile} {filename}"
 
@@ -218,9 +180,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/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py
index ad3fa898..dfe6a056 100644
--- a/b_asic/scheduler_gui/ui_main_window.py
+++ b/b_asic/scheduler_gui/ui_main_window.py
@@ -1,4 +1,4 @@
-# Form implementation generated from reading ui file '.\B-ASIC\b_asic\scheduler_gui\main_window.ui'
+# Form implementation generated from reading ui file '.\b_asic\scheduler_gui\main_window.ui'
 #
 # Created by: PyQt6 UI code generator 6.8.0
 #
-- 
GitLab


From 127a5687fc91a2f7b5d2f0ad49927548f7160fce Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Mon, 3 Feb 2025 20:03:50 +0100
Subject: [PATCH 51/52] recompiled with pyside6, will hopefully work with pyqt6
 also

---
 b_asic/scheduler_gui/compile.py        |   8 +-
 b_asic/scheduler_gui/ui_main_window.py | 848 ++++++++++++++++---------
 2 files changed, 545 insertions(+), 311 deletions(-)

diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py
index 046544d3..2dc7b719 100644
--- a/b_asic/scheduler_gui/compile.py
+++ b/b_asic/scheduler_gui/compile.py
@@ -160,7 +160,13 @@ def compile_ui(*filenames: str) -> None:
         directory = directory if directory else "."
         outfile = f"{directory}/ui_{file}.py"
 
-        if uic.PYSIDE6 or uic.PYQT6:
+        if uic.PYQT6:
+            from qtpy.uic import compileUi
+
+            with open(outfile, "w") as ofile:
+                compileUi(filename, ofile)
+
+        elif uic.PYSIDE6:
             uic_ = shutil.which("pyside6-uic")
             arguments = f"-g python -o {outfile} {filename}"
 
diff --git a/b_asic/scheduler_gui/ui_main_window.py b/b_asic/scheduler_gui/ui_main_window.py
index dfe6a056..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\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
-- 
GitLab


From 8b68ff9e0781420a9d24aeb43d76d8f2b4f44176 Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Tue, 4 Feb 2025 10:29:52 +0100
Subject: [PATCH 52/52] fixed error from mr

---
 b_asic/GUI/main_window.py | 1 -
 1 file changed, 1 deletion(-)

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()
-- 
GitLab