From c8bb844e8e244e4de47f5274c80fabebfd886a5e Mon Sep 17 00:00:00 2001
From: Simon Bjurek <simbj106@student.liu.se>
Date: Wed, 26 Feb 2025 12:48:35 +0100
Subject: [PATCH] removed some old qt5 references and other small fixes

---
 .gitlab-ci.yml                          |   2 +-
 b_asic/gui_utils/mpl_window.py          |   4 +-
 b_asic/logger.py                        |   4 +-
 b_asic/schedule.py                      |  31 +++++-
 b_asic/scheduler.py                     |   1 +
 b_asic/scheduler_gui/compile.py         |  12 +-
 b_asic/scheduler_gui/main_window.py     |   2 +-
 b_asic/scheduler_gui/scheduler_event.py |   8 +-
 b_asic/scheduler_gui/scheduler_item.py  |   4 +-
 docs_sphinx/conf.py                     |   1 -
 docs_sphinx/index.rst                   |   2 +-
 test/unit/test_schedule.py              | 141 ++++++++++++++++--------
 12 files changed, 145 insertions(+), 67 deletions(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4a822db9..7accae47 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -4,7 +4,7 @@ stages:
 
 before_script:
   - apt-get update --yes
-  # - apt-get install --yes build-essential cmake graphviz python3-pyqt5 xvfb xdg-utils lcov
+  # - apt-get install --yes build-essential cmake graphviz 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
diff --git a/b_asic/gui_utils/mpl_window.py b/b_asic/gui_utils/mpl_window.py
index 266d4ae7..1a6db678 100644
--- a/b_asic/gui_utils/mpl_window.py
+++ b/b_asic/gui_utils/mpl_window.py
@@ -1,7 +1,7 @@
 """MPLWindow is a dialog that provides an Axes for plotting in."""
 
-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 qtpy.QtCore import Qt
 from qtpy.QtWidgets import QDialog, QVBoxLayout
diff --git a/b_asic/logger.py b/b_asic/logger.py
index e6615ed0..606ac6f9 100644
--- a/b_asic/logger.py
+++ b/b_asic/logger.py
@@ -1,6 +1,6 @@
 #!/usr/bin/env python3
 """
-B-ASIC Scheduler-gui Logger Module.
+B-ASIC Logger Module.
 
 Contains a logger that logs to the console and a file using levels. It is based
 on the :mod:`logging` module and has predefined levels of logging.
@@ -8,7 +8,7 @@ on the :mod:`logging` module and has predefined levels of logging.
 Usage:
 ------
 
-    >>> import b_asic.scheduler_gui.logger as logger
+    >>> import b_asic.logger as logger
     >>> log = logger.getLogger()
     >>> log.info('This is a log post with level INFO')
 
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 181c426e..543e10af 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -117,10 +117,13 @@ class Schedule:
             self._start_times = start_times
             self._laps.update(laps)
             self._remove_delays_no_laps()
+
         max_end_time = self.get_max_end_time()
         if not self._schedule_time:
             self._schedule_time = max_end_time
 
+        self._validate_schedule()
+
     def __str__(self) -> str:
         """Return a string representation of this Schedule."""
         res: List[Tuple[GraphID, int, int, int]] = [
@@ -155,6 +158,32 @@ class Schedule:
 
         return string_io.getvalue()
 
+    def _validate_schedule(self) -> None:
+        if self._schedule_time is None:
+            raise ValueError("Schedule without set scheduling time detected.")
+        if not isinstance(self._schedule_time, int):
+            raise ValueError("Schedule with non-integer scheduling time detected.")
+
+        ops = {op.graph_id for op in self._sfg.operations}
+        missing_elems = ops - set(self._start_times)
+        extra_elems = set(self._start_times) - ops
+        if missing_elems:
+            raise ValueError(
+                f"Missing operations detected in start_times: {missing_elems}"
+            )
+        if extra_elems:
+            raise ValueError(f"Extra operations detected in start_times: {extra_elems}")
+
+        for graph_id, time in self._start_times.items():
+            if self.forward_slack(graph_id) < 0 or self.backward_slack(graph_id) < 0:
+                raise ValueError(
+                    f"Negative slack detected in Schedule for operation: {graph_id}."
+                )
+            if time > self._schedule_time:
+                raise ValueError(
+                    f"Start time larger than scheduling time detected in Schedule for operation {graph_id}"
+                )
+
     def start_time_of_operation(self, graph_id: GraphID) -> int:
         """
         Return the start time of the operation with the specified by *graph_id*.
@@ -1122,7 +1151,7 @@ class Schedule:
             color="black",
         )
 
-    def _reset_y_locations(self) -> None:
+    def reset_y_locations(self) -> None:
         """Reset all the y-locations in the schedule to None"""
         self._y_locations = defaultdict(_y_locations_default)
 
diff --git a/b_asic/scheduler.py b/b_asic/scheduler.py
index 74a892c0..87708e75 100644
--- a/b_asic/scheduler.py
+++ b/b_asic/scheduler.py
@@ -172,6 +172,7 @@ class ListScheduler(Scheduler, ABC):
         cyclic: Optional[bool] = False,
     ) -> None:
         super()
+
         if max_resources is not None:
             if not isinstance(max_resources, dict):
                 raise ValueError("max_resources must be a dictionary.")
diff --git a/b_asic/scheduler_gui/compile.py b/b_asic/scheduler_gui/compile.py
index 2dc7b719..fe97bf54 100644
--- a/b_asic/scheduler_gui/compile.py
+++ b/b_asic/scheduler_gui/compile.py
@@ -18,7 +18,7 @@ from qtpy import uic
 from setuptools_scm import get_version
 
 try:
-    import b_asic.scheduler_gui.logger as logger
+    import b_asic.logger as logger
 
     log = logger.getLogger()
     sys.excepthook = logger.handle_exceptions
@@ -37,18 +37,16 @@ def _check_filenames(*filenames: str) -> None:
 
 def _check_qt_version() -> None:
     """
-    Check if PySide2, PyQt5, PySide6, or PyQt6 is installed.
+    Check if PySide6 or PyQt6 is installed.
 
     Otherwise, raise AssertionError exception.
     """
-    assert (
-        uic.PYSIDE2 or uic.PYQT5 or uic.PYSIDE6 or uic.PYQT6
-    ), "Python QT bindings must be installed"
+    assert uic.PYSIDE6 or uic.PYQT6, "Python QT bindings must be installed"
 
 
 def replace_qt_bindings(filename: str) -> None:
     """
-    Replace qt-binding API in *filename* from PySide2/6 or PyQt5/6 to qtpy.
+    Replace qt-binding API in *filename* from PySide6 or PyQt6 to qtpy.
 
     Parameters
     ----------
@@ -57,8 +55,6 @@ def replace_qt_bindings(filename: str) -> None:
     """
     with open(f"{filename}") as file:
         filedata = file.read()
-        filedata = filedata.replace("from PyQt5", "from qtpy")
-        filedata = filedata.replace("from PySide2", "from qtpy")
         filedata = filedata.replace("from PyQt6", "from qtpy")
         filedata = filedata.replace("from PySide6", "from qtpy")
     with open(f"{filename}", "w") as file:
diff --git a/b_asic/scheduler_gui/main_window.py b/b_asic/scheduler_gui/main_window.py
index c15be267..217afa8a 100644
--- a/b_asic/scheduler_gui/main_window.py
+++ b/b_asic/scheduler_gui/main_window.py
@@ -56,7 +56,7 @@ from qtpy.QtWidgets import (
 )
 
 # B-ASIC
-import b_asic.scheduler_gui.logger as logger
+import b_asic.logger as logger
 from b_asic._version import __version__
 from b_asic.graph_component import GraphComponent, GraphID
 from b_asic.gui_utils.about_window import AboutWindow
diff --git a/b_asic/scheduler_gui/scheduler_event.py b/b_asic/scheduler_gui/scheduler_event.py
index 89d4a384..617e14cb 100644
--- a/b_asic/scheduler_gui/scheduler_event.py
+++ b/b_asic/scheduler_gui/scheduler_event.py
@@ -19,7 +19,7 @@ from b_asic.scheduler_gui.operation_item import OperationItem
 from b_asic.scheduler_gui.timeline_item import TimelineItem
 
 
-class SchedulerEvent:  # PyQt5
+class SchedulerEvent:
     """
     Event filter and handlers for SchedulerItem.
 
@@ -29,7 +29,7 @@ class SchedulerEvent:  # PyQt5
         The parent QGraphicsItem.
     """
 
-    class Signals(QObject):  # PyQt5
+    class Signals(QObject):
         """A class representing signals."""
 
         component_selected = Signal(str)
@@ -43,11 +43,11 @@ class SchedulerEvent:  # PyQt5
     _axes: Optional[AxesItem]
     _current_pos: QPointF
     _delta_time: int
-    _signals: Signals  # PyQt5
+    _signals: Signals
     _schedule: Schedule
     _old_op_position: int = -1
 
-    def __init__(self, parent: Optional[QGraphicsItem] = None):  # PyQt5
+    def __init__(self, parent: Optional[QGraphicsItem] = None):
         super().__init__(parent=parent)
         self._signals = self.Signals()
 
diff --git a/b_asic/scheduler_gui/scheduler_item.py b/b_asic/scheduler_gui/scheduler_item.py
index 80e09ac6..13af04a9 100644
--- a/b_asic/scheduler_gui/scheduler_item.py
+++ b/b_asic/scheduler_gui/scheduler_item.py
@@ -31,7 +31,7 @@ from b_asic.scheduler_gui.signal_item import SignalItem
 from b_asic.types import GraphID
 
 
-class SchedulerItem(SchedulerEvent, QGraphicsItemGroup):  # PySide2 / PyQt5
+class SchedulerItem(SchedulerEvent, QGraphicsItemGroup):
     """
     A class to represent a schedule in a QGraphicsScene.
 
@@ -312,7 +312,7 @@ class SchedulerItem(SchedulerEvent, QGraphicsItemGroup):  # PySide2 / PyQt5
         )
 
     def _redraw_from_start(self) -> None:
-        self.schedule._reset_y_locations()
+        self.schedule.reset_y_locations()
         self.schedule.sort_y_locations_on_start_times()
         for graph_id in self.schedule.start_times.keys():
             self._set_position(graph_id)
diff --git a/docs_sphinx/conf.py b/docs_sphinx/conf.py
index ed0e0e06..32ba2028 100644
--- a/docs_sphinx/conf.py
+++ b/docs_sphinx/conf.py
@@ -40,7 +40,6 @@ intersphinx_mapping = {
     'graphviz': ('https://graphviz.readthedocs.io/en/stable/', None),
     'matplotlib': ('https://matplotlib.org/stable/', None),
     'numpy': ('https://numpy.org/doc/stable/', None),
-    'PyQt5': ("https://www.riverbankcomputing.com/static/Docs/PyQt5", None),
     'networkx': ('https://networkx.org/documentation/stable', None),
     'mplsignal': ('https://mplsignal.readthedocs.io/en/stable/', None),
 }
diff --git a/docs_sphinx/index.rst b/docs_sphinx/index.rst
index bcbb9c1e..6cb1b85f 100644
--- a/docs_sphinx/index.rst
+++ b/docs_sphinx/index.rst
@@ -39,7 +39,7 @@ can pull new changes without having to reinstall it. It also makes it easy to co
 any improvements.
 
 In addition to the dependencies that are automatically installed, you will also
-need a Qt-binding, but you are free to choose from the available Qt5 and Qt6 bindings.
+need a Qt-binding, but you are free to choose between PyQt6 and PySide6.
 See `https://gitlab.liu.se/da/B-ASIC <https://gitlab.liu.se/da/B-ASIC>`_ for more info.
 
 If you use B-ASIC in a publication, please acknowledge it. Later on there will be a
diff --git a/test/unit/test_schedule.py b/test/unit/test_schedule.py
index 4d45755d..e7c51569 100644
--- a/test/unit/test_schedule.py
+++ b/test/unit/test_schedule.py
@@ -11,7 +11,7 @@ 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.sfg_generators import direct_form_1_iir, direct_form_fir
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 
@@ -247,6 +247,50 @@ class TestInit:
         }
         assert schedule.schedule_time == 10
 
+    def test_provided_schedule(self):
+        sfg = direct_form_1_iir([1, 2, 3], [1, 2, 3])
+
+        sfg.set_latency_of_type(Addition.type_name(), 1)
+        sfg.set_latency_of_type(ConstantMultiplication.type_name(), 3)
+        sfg.set_execution_time_of_type(Addition.type_name(), 1)
+        sfg.set_execution_time_of_type(ConstantMultiplication.type_name(), 1)
+
+        start_times = {
+            "in0": 1,
+            "cmul0": 1,
+            "cmul1": 0,
+            "cmul2": 0,
+            "cmul3": 0,
+            "cmul4": 0,
+            "add3": 3,
+            "add1": 3,
+            "add0": 4,
+            "add2": 5,
+            "out0": 6,
+        }
+        laps = {
+            's8': 1,
+            's10': 2,
+            's15': 1,
+            's17': 2,
+            's0': 0,
+            's3': 0,
+            's12': 0,
+            's11': 0,
+            's14': 0,
+            's13': 0,
+            's6': 0,
+            's4': 0,
+            's5': 0,
+            's2': 0,
+        }
+
+        schedule = Schedule(sfg, start_times=start_times, laps=laps)
+
+        assert schedule.start_times == start_times
+        assert schedule.laps == laps
+        assert schedule.schedule_time == 6
+
 
 class TestSlacks:
     def test_forward_backward_slack_normal_latency(self, precedence_sfg_delays):
@@ -297,24 +341,22 @@ class TestSlacks:
         schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         schedule.print_slacks()
         captured = capsys.readouterr()
-        assert (
-            captured.out
-            == """Graph ID | Backward |  Forward
----------|----------|---------
-add0     |        0 |        0
-add1     |        0 |        0
-add2     |        0 |        0
-add3     |        0 |        7
-cmul0    |        0 |        1
-cmul1    |        0 |        0
-cmul2    |        0 |        0
-cmul3    |        4 |        0
-cmul4    |       16 |        0
-cmul5    |       16 |        0
-cmul6    |        4 |        0
-in0      |       oo |        0
-out0     |        0 |       oo
-"""
+        assert captured.out == (
+            "Graph ID | Backward |  Forward\n"
+            "---------|----------|---------\n"
+            "add0     |        0 |        0\n"
+            "add1     |        0 |        0\n"
+            "add2     |        0 |        0\n"
+            "add3     |        0 |        7\n"
+            "cmul0    |        0 |        1\n"
+            "cmul1    |        0 |        0\n"
+            "cmul2    |        0 |        0\n"
+            "cmul3    |        4 |        0\n"
+            "cmul4    |       16 |        0\n"
+            "cmul5    |       16 |        0\n"
+            "cmul6    |        4 |        0\n"
+            "in0      |       oo |        0\n"
+            "out0     |        0 |       oo\n"
         )
         assert captured.err == ""
 
@@ -325,24 +367,22 @@ out0     |        0 |       oo
         schedule = Schedule(precedence_sfg_delays, scheduler=ASAPScheduler())
         schedule.print_slacks(1)
         captured = capsys.readouterr()
-        assert (
-            captured.out
-            == """Graph ID | Backward |  Forward
----------|----------|---------
-cmul0    |        0 |        1
-add0     |        0 |        0
-add1     |        0 |        0
-cmul1    |        0 |        0
-cmul2    |        0 |        0
-add3     |        0 |        7
-add2     |        0 |        0
-out0     |        0 |       oo
-cmul3    |        4 |        0
-cmul6    |        4 |        0
-cmul4    |       16 |        0
-cmul5    |       16 |        0
-in0      |       oo |        0
-"""
+        assert captured.out == (
+            "Graph ID | Backward |  Forward\n"
+            "---------|----------|---------\n"
+            "cmul0    |        0 |        1\n"
+            "add0     |        0 |        0\n"
+            "add1     |        0 |        0\n"
+            "cmul1    |        0 |        0\n"
+            "cmul2    |        0 |        0\n"
+            "add3     |        0 |        7\n"
+            "add2     |        0 |        0\n"
+            "out0     |        0 |       oo\n"
+            "cmul3    |        4 |        0\n"
+            "cmul6    |        4 |        0\n"
+            "cmul4    |       16 |        0\n"
+            "cmul5    |       16 |        0\n"
+            "in0      |       oo |        0\n"
         )
         assert captured.err == ""
 
@@ -802,10 +842,23 @@ class TestYLocations:
         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, ASAPScheduler())
-        # Assign locations
-        schedule.show()
-        assert schedule._y_locations == {'in0': 0, 'cmul0': 1, 'add0': 3, 'out0': 2}
-        schedule.move_y_location('add0', 1, insert=True)
-        assert schedule._y_locations == {'in0': 0, 'cmul0': 2, 'add0': 1, 'out0': 3}
-        schedule.move_y_location('out0', 1)
-        assert schedule._y_locations == {'in0': 0, 'cmul0': 2, 'add0': 1, 'out0': 1}
+
+        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 3, "out0": 2}
+        schedule.move_y_location("add0", 1, insert=True)
+        assert schedule._y_locations == {"in0": 0, "cmul0": 2, "add0": 1, "out0": 3}
+        schedule.move_y_location("out0", 1)
+        assert schedule._y_locations == {"in0": 0, "cmul0": 2, "add0": 1, "out0": 1}
+
+    def test_reset(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, ASAPScheduler())
+
+        assert schedule._y_locations == {"in0": 0, "cmul0": 1, "add0": 3, "out0": 2}
+        schedule.reset_y_locations()
+        assert schedule._y_locations["in0"] is None
+        assert schedule._y_locations["cmul0"] is None
+        assert schedule._y_locations["add0"] is None
+        assert schedule._y_locations["add0"] is None
+        assert schedule._y_locations["out0"] is None
+        assert schedule._y_locations["foo"] is None
-- 
GitLab