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] 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