From 2724fa3d8a0a487948c78b762eb515c5c3c4827b Mon Sep 17 00:00:00 2001
From: Samuel Fagerlund <samfa392@student.liu.se>
Date: Mon, 1 Jul 2024 11:32:14 +0200
Subject: [PATCH] Add tests

---
 b_asic/GUI/__init__.py                  |  1 +
 b_asic/GUI/port_button.py               |  1 +
 b_asic/GUI/precedence_graph_window.py   |  1 +
 b_asic/GUI/properties_window.py         | 10 +++---
 b_asic/GUI/select_sfg_window.py         |  1 +
 b_asic/GUI/simulate_sfg_window.py       |  1 +
 b_asic/__init__.py                      |  1 +
 b_asic/codegen/vhdl/entity.py           |  1 +
 b_asic/core_operations.py               | 10 ++++++
 b_asic/gui_utils/color_button.py        |  1 +
 b_asic/save_load_structure.py           | 16 +++++----
 b_asic/schedule.py                      | 15 ++++----
 b_asic/scheduler_gui/__init__.py        |  1 +
 b_asic/scheduler_gui/scheduler_event.py | 12 +++----
 b_asic/scheduler_gui/signal_item.py     |  1 -
 b_asic/sfg_generators.py                |  1 +
 b_asic/signal.py                        |  1 +
 b_asic/signal_flow_graph.py             | 19 +++++-----
 b_asic/special_operations.py            |  1 +
 examples/introduction.py                |  1 +
 examples/schedulingexample.py           |  1 +
 examples/thirdorderblwdf.py             |  1 +
 test/fixtures/operation_tree.py         |  4 +--
 test/test_gui/twotapfir.py              | 15 ++------
 test/test_operation.py                  |  1 +
 test/test_outputport.py                 |  1 +
 test/test_sfg.py                        | 46 +++++++++++++++++++++++++
 27 files changed, 112 insertions(+), 53 deletions(-)

diff --git a/b_asic/GUI/__init__.py b/b_asic/GUI/__init__.py
index 357ada54..89256ec9 100644
--- a/b_asic/GUI/__init__.py
+++ b/b_asic/GUI/__init__.py
@@ -2,6 +2,7 @@
 
 Graphical user interface for B-ASIC.
 """
+
 from b_asic.GUI.main_window import start_editor
 
 __all__ = ['start_editor']
diff --git a/b_asic/GUI/port_button.py b/b_asic/GUI/port_button.py
index 92f2696c..c90166d0 100644
--- a/b_asic/GUI/port_button.py
+++ b/b_asic/GUI/port_button.py
@@ -1,6 +1,7 @@
 """
 B-ASIC port button module.
 """
+
 from typing import TYPE_CHECKING
 
 from qtpy.QtCore import QMimeData, Qt, Signal
diff --git a/b_asic/GUI/precedence_graph_window.py b/b_asic/GUI/precedence_graph_window.py
index 090ca3d1..57365bf9 100644
--- a/b_asic/GUI/precedence_graph_window.py
+++ b/b_asic/GUI/precedence_graph_window.py
@@ -1,6 +1,7 @@
 """
 B-ASIC window to show precedence graph.
 """
+
 from qtpy.QtCore import Qt, Signal
 from qtpy.QtWidgets import (
     QCheckBox,
diff --git a/b_asic/GUI/properties_window.py b/b_asic/GUI/properties_window.py
index cb6cda29..6babd698 100644
--- a/b_asic/GUI/properties_window.py
+++ b/b_asic/GUI/properties_window.py
@@ -162,10 +162,12 @@ class PropertiesWindow(QDialog):
 
         self.operation.operation.set_latency_offsets(
             {
-                port: float(latency_edit.text().replace(",", "."))
-                if latency_edit.text()
-                and float(latency_edit.text().replace(",", ".")) > 0
-                else None
+                port: (
+                    float(latency_edit.text().replace(",", "."))
+                    if latency_edit.text()
+                    and float(latency_edit.text().replace(",", ".")) > 0
+                    else None
+                )
                 for port, latency_edit in self._latency_fields.items()
             }
         )
diff --git a/b_asic/GUI/select_sfg_window.py b/b_asic/GUI/select_sfg_window.py
index 53118a4b..a3ac58bf 100644
--- a/b_asic/GUI/select_sfg_window.py
+++ b/b_asic/GUI/select_sfg_window.py
@@ -1,6 +1,7 @@
 """
 B-ASIC select SFG window.
 """
+
 from typing import TYPE_CHECKING
 
 from qtpy.QtCore import Qt, Signal
diff --git a/b_asic/GUI/simulate_sfg_window.py b/b_asic/GUI/simulate_sfg_window.py
index 2e27170e..29dc5c8e 100644
--- a/b_asic/GUI/simulate_sfg_window.py
+++ b/b_asic/GUI/simulate_sfg_window.py
@@ -1,6 +1,7 @@
 """
 B-ASIC window to simulate an SFG.
 """
+
 from typing import TYPE_CHECKING, Dict
 
 from qtpy.QtCore import Qt, Signal
diff --git a/b_asic/__init__.py b/b_asic/__init__.py
index f1fded40..fae7aec4 100644
--- a/b_asic/__init__.py
+++ b/b_asic/__init__.py
@@ -1,6 +1,7 @@
 """B-ASIC - Better ASIC Toolbox.
 ASIC toolbox that simplifies circuit design and optimization.
 """
+
 # Python modules.
 from b_asic.core_operations import *
 from b_asic.graph_component import *
diff --git a/b_asic/codegen/vhdl/entity.py b/b_asic/codegen/vhdl/entity.py
index 4ccbd28d..c52f8584 100644
--- a/b_asic/codegen/vhdl/entity.py
+++ b/b_asic/codegen/vhdl/entity.py
@@ -1,6 +1,7 @@
 """
 Module for code generation of VHDL entity declarations
 """
+
 from typing import Set, TextIO
 
 from b_asic.codegen.vhdl import VHDL_TAB, write_lines
diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py
index e731a480..4e44de1c 100644
--- a/b_asic/core_operations.py
+++ b/b_asic/core_operations.py
@@ -73,6 +73,7 @@ class Constant(AbstractOperation):
     def __str__(self) -> str:
         return f"{self.value}"
 
+
 class Addition(AbstractOperation):
     """
     Binary addition operation.
@@ -240,6 +241,7 @@ class AddSub(AbstractOperation):
     ========
     Addition, Subtraction
     """
+
     is_linear = True
 
     def __init__(
@@ -317,6 +319,7 @@ class Multiplication(AbstractOperation):
     ========
     ConstantMultiplication
     """
+
     is_swappable = True
 
     def __init__(
@@ -450,6 +453,7 @@ class Min(AbstractOperation):
     ========
     Max
     """
+
     is_swappable = True
 
     def __init__(
@@ -515,6 +519,7 @@ class Max(AbstractOperation):
     ========
     Min
     """
+
     is_swappable = True
 
     def __init__(
@@ -729,6 +734,7 @@ class ConstantMultiplication(AbstractOperation):
     --------
     Multiplication
     """
+
     is_linear = True
 
     def __init__(
@@ -802,6 +808,7 @@ class Butterfly(AbstractOperation):
     execution_time : int, optional
         Operation execution time (time units before operator can be reused).
     """
+
     is_linear = True
 
     def __init__(
@@ -865,6 +872,7 @@ class MAD(AbstractOperation):
     Multiplication
     Addition
     """
+
     is_swappable = True
 
     def __init__(
@@ -922,6 +930,7 @@ class SymmetricTwoportAdaptor(AbstractOperation):
         y_1 & = & x_0 + \text{value}\times\left(x_1 - x_0\right)
         \end{eqnarray}
     """
+
     is_linear = True
     is_swappable = True
 
@@ -1263,6 +1272,7 @@ class Shift(AbstractOperation):
             raise TypeError("value must be an int")
         self.set_param("value", value)
 
+
 class Sink(AbstractOperation):
     r"""
     Sink operation.
diff --git a/b_asic/gui_utils/color_button.py b/b_asic/gui_utils/color_button.py
index 725ddda1..5f753e92 100644
--- a/b_asic/gui_utils/color_button.py
+++ b/b_asic/gui_utils/color_button.py
@@ -1,6 +1,7 @@
 """
 Qt button for use in preference dialogs, selecting color.
 """
+
 from qtpy.QtCore import Qt, Signal
 from qtpy.QtGui import QColor
 from qtpy.QtWidgets import QColorDialog, QPushButton
diff --git a/b_asic/save_load_structure.py b/b_asic/save_load_structure.py
index 1dccc40b..58497f4d 100644
--- a/b_asic/save_load_structure.py
+++ b/b_asic/save_load_structure.py
@@ -63,9 +63,11 @@ def sfg_to_python(
                 if attr != "latency" and hasattr(comp, attr)
             }
             params = {
-                attr: getattr(comp, attr)
-                if not isinstance(getattr(comp, attr), str)
-                else f'"{getattr(comp, attr)}"'
+                attr: (
+                    getattr(comp, attr)
+                    if not isinstance(getattr(comp, attr), str)
+                    else f'"{getattr(comp, attr)}"'
+                )
                 for attr in params_filtered
             }
             params = {k: v for k, v in params.items() if v}
@@ -153,9 +155,11 @@ def python_to_sfg(path: str) -> Tuple[SFG, Dict[str, Tuple[int, int]]]:
         exec(code, globals(), locals())
 
     return (
-        locals()["prop"]["name"]
-        if "prop" in locals()
-        else [v for k, v in locals().items() if isinstance(v, SFG)][0],
+        (
+            locals()["prop"]["name"]
+            if "prop" in locals()
+            else [v for k, v in locals().items() if isinstance(v, SFG)][0]
+        ),
         locals()["positions"] if "positions" in locals() else {},
     )
 
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 7fe533fa..4b34d580 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -526,9 +526,7 @@ class Schedule:
         """
         self._sfg.set_execution_time_of_type(type_name, execution_time)
 
-    def set_latency_of_type(
-        self, type_name: TypeName, latency: int
-    ) -> None:
+    def set_latency_of_type(self, type_name: TypeName, latency: int) -> None:
         """
         Set the latency of all operations with the given type name.
 
@@ -542,20 +540,19 @@ class Schedule:
         """
         passed = True
         for op in self._sfg.operations:
-            if type_name == op.type_name() or type_name == op.graph_id:            
+            if type_name == op.type_name() or type_name == op.graph_id:
                 change_in_latency = latency - op.latency
                 if change_in_latency > (self.forward_slack(op.graph_id)):
                     passed = False
                     raise ValueError(
                         f"Error: Can't increase latency for all components. Try increassing forward slack time by rescheduling. "
-                        f"Error in: {op.graph_id}" 
+                        f"Error in: {op.graph_id}"
                     )
                     break
         if change_in_latency < 0 or passed:
-                for op in self._sfg.operations:            
-                    if type_name == op.type_name() or type_name == op.graph_id:           
-                        cast(Operation, op).set_latency(latency)
- 
+            for op in self._sfg.operations:
+                if type_name == op.type_name() or type_name == op.graph_id:
+                    cast(Operation, op).set_latency(latency)
 
     def move_y_location(
         self, graph_id: GraphID, new_y: int, insert: bool = False
diff --git a/b_asic/scheduler_gui/__init__.py b/b_asic/scheduler_gui/__init__.py
index fa401de5..65a54cad 100644
--- a/b_asic/scheduler_gui/__init__.py
+++ b/b_asic/scheduler_gui/__init__.py
@@ -3,6 +3,7 @@ B-ASIC Scheduler-gui Module.
 
 Graphical user interface for B-ASIC scheduler.
 """
+
 from b_asic.scheduler_gui.main_window import start_scheduler
 
 __all__ = ['start_scheduler']
diff --git a/b_asic/scheduler_gui/scheduler_event.py b/b_asic/scheduler_gui/scheduler_event.py
index d11df346..6df2b679 100644
--- a/b_asic/scheduler_gui/scheduler_event.py
+++ b/b_asic/scheduler_gui/scheduler_event.py
@@ -69,12 +69,10 @@ class SchedulerEvent:  # PyQt5
     # Filters #
     ###########
     @overload
-    def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None:
-        ...
+    def installSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ...
 
     @overload
-    def installSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None:
-        ...
+    def installSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ...
 
     def installSceneEventFilters(self, filterItems) -> None:
         """
@@ -88,12 +86,10 @@ class SchedulerEvent:  # PyQt5
             item.installSceneEventFilter(self)
 
     @overload
-    def removeSceneEventFilters(self, filterItems: QGraphicsItem) -> None:
-        ...
+    def removeSceneEventFilters(self, filterItems: QGraphicsItem) -> None: ...
 
     @overload
-    def removeSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None:
-        ...
+    def removeSceneEventFilters(self, filterItems: List[QGraphicsItem]) -> None: ...
 
     def removeSceneEventFilters(self, filterItems) -> None:
         """
diff --git a/b_asic/scheduler_gui/signal_item.py b/b_asic/scheduler_gui/signal_item.py
index ccb065df..723bf228 100644
--- a/b_asic/scheduler_gui/signal_item.py
+++ b/b_asic/scheduler_gui/signal_item.py
@@ -5,7 +5,6 @@ Contains the scheduler_gui SignalItem class for drawing and maintaining a signal
 in the schedule.
 """
 
-
 from typing import TYPE_CHECKING, cast
 
 from qtpy.QtCore import QPointF
diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py
index 8682db1b..cdfb93b4 100644
--- a/b_asic/sfg_generators.py
+++ b/b_asic/sfg_generators.py
@@ -3,6 +3,7 @@ B-ASIC signal flow graph generators.
 
 This module contains a number of functions generating SFGs for specific functions.
 """
+
 from typing import Dict, Optional, Sequence, Union
 
 import numpy as np
diff --git a/b_asic/signal.py b/b_asic/signal.py
index df04fadc..d755bfd5 100644
--- a/b_asic/signal.py
+++ b/b_asic/signal.py
@@ -3,6 +3,7 @@ B-ASIC Signal Module.
 
 Contains the class for representing the connections between operations.
 """
+
 from typing import TYPE_CHECKING, Iterable, Optional, Union
 
 from b_asic.graph_component import AbstractGraphComponent, GraphComponent
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index c3947a99..44979196 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -25,6 +25,7 @@ from typing import (
     cast,
 )
 
+import numpy as np
 from graphviz import Digraph
 
 from b_asic.graph_component import GraphComponent
@@ -1691,7 +1692,7 @@ class SFG(AbstractOperation):
 
         return Schedule(self, algorithm="ASAP").schedule_time
 
-    def dfs(self, graph, start, end):
+    def _dfs(self, graph, start, end):
         """
         Find loop(s) in graph
 
@@ -1753,16 +1754,16 @@ class SFG(AbstractOperation):
                             visited.add(new_op)
                     else:
                         raise ValueError("Destination does not exist")
-        if dict_of_sfg == {}:
+        if not dict_of_sfg:
             raise ValueError(
                 "the SFG does not have any loops and therefore no iteration period bound."
             )
         cycles = [
             [node] + path
             for node in dict_of_sfg
-            for path in self.dfs(dict_of_sfg, node, node)
+            for path in self._dfs(dict_of_sfg, node, node)
         ]
-        if cycles == []:
+        if not cycles:
             return -1
         op_and_latency = {}
         for op in self.operations:
@@ -1836,15 +1837,13 @@ class SFG(AbstractOperation):
                             visited.add(new_op)
                     else:
                         raise ValueError("Destination does not exist")
-        if dict_of_sfg == {}:
-            raise ValueError(
-                "the SFG does not have any loops and therefore no iteration period bound."
-            )
+        if not dict_of_sfg:
+            raise ValueError("Empty SFG")
         cycles = [
             [node] + path
             for node in dict_of_sfg
             if node[0] == 't'
-            for path in self.dfs(dict_of_sfg, node, node)
+            for path in self._dfs(dict_of_sfg, node, node)
         ]
         delay_loop_list = []
         for lista in cycles:
@@ -1861,11 +1860,9 @@ class SFG(AbstractOperation):
             for x in delay_loop_list
             if x not in state_space_lista
         ]
-        import numpy as np
 
         mat_row = len(delay_element_used) + len(output_index_used)
         mat_col = len(delay_element_used) + len(input_index_used)
-        print(delay_element_used)
         mat_content = np.zeros((mat_row, mat_col))
         matrix_in = [0] * mat_col
         matrix_answer = [0] * mat_row
diff --git a/b_asic/special_operations.py b/b_asic/special_operations.py
index 0e05a379..a67b55db 100644
--- a/b_asic/special_operations.py
+++ b/b_asic/special_operations.py
@@ -191,6 +191,7 @@ class Delay(AbstractOperation):
             output_count=1,
             name=Name(name),
             input_sources=[src0],
+            latency=0,
         )
         self.set_param("initial_value", initial_value)
 
diff --git a/examples/introduction.py b/examples/introduction.py
index 16778e30..0caa0bd0 100644
--- a/examples/introduction.py
+++ b/examples/introduction.py
@@ -3,6 +3,7 @@
 Introduction example for the TSTE87 course
 ==========================================
 """
+
 from b_asic.core_operations import Addition, ConstantMultiplication
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
diff --git a/examples/schedulingexample.py b/examples/schedulingexample.py
index 31663194..f68c42a6 100644
--- a/examples/schedulingexample.py
+++ b/examples/schedulingexample.py
@@ -14,6 +14,7 @@ Node numbering from the original SFG used with the Matlab toolbox::
     sfg=addoperand(sfg,'delay',1,3,4);
     sfg=addoperand(sfg,'out',1,7);
 """
+
 from b_asic.signal_flow_graph import SFG
 from b_asic.special_operations import Delay, Input, Output
 
diff --git a/examples/thirdorderblwdf.py b/examples/thirdorderblwdf.py
index fc289e24..7496b078 100644
--- a/examples/thirdorderblwdf.py
+++ b/examples/thirdorderblwdf.py
@@ -5,6 +5,7 @@ Third-order Bireciprocal LWDF
 
 Small bireciprocal lattice wave digital filter.
 """
+
 import numpy as np
 from mplsignal.freq_plots import freqz_fir
 
diff --git a/test/fixtures/operation_tree.py b/test/fixtures/operation_tree.py
index 02134668..23c1179e 100644
--- a/test/fixtures/operation_tree.py
+++ b/test/fixtures/operation_tree.py
@@ -87,10 +87,10 @@ def butterfly_operation_tree():
         *(
             Butterfly(
                 *(Butterfly(Constant(2), Constant(4), name="bfly3").outputs),
-                name="bfly2"
+                name="bfly2",
             ).outputs
         ),
-        name="bfly1"
+        name="bfly1",
     )
 
 
diff --git a/test/test_gui/twotapfir.py b/test/test_gui/twotapfir.py
index e0d7ca6f..44300bb8 100644
--- a/test/test_gui/twotapfir.py
+++ b/test/test_gui/twotapfir.py
@@ -3,15 +3,8 @@ B-ASIC automatically generated SFG file.
 Name: twotapfir
 Last saved: 2023-01-24 14:38:17.654639.
 """
-from b_asic import (
-    SFG,
-    Addition,
-    ConstantMultiplication,
-    Delay,
-    Input,
-    Output,
-    Signal,
-)
+
+from b_asic import SFG, Addition, ConstantMultiplication, Delay, Input, Output, Signal
 
 # Inputs:
 in1 = Input(name="in1")
@@ -24,9 +17,7 @@ t1 = Delay(initial_value=0, name="")
 cmul1 = ConstantMultiplication(
     value=-0.5, name="cmul1", latency_offsets={'in0': None, 'out0': None}
 )
-add1 = Addition(
-    name="add1", latency_offsets={'in0': None, 'in1': None, 'out0': None}
-)
+add1 = Addition(name="add1", latency_offsets={'in0': None, 'in1': None, 'out0': None})
 cmul2 = ConstantMultiplication(
     value=0.5, name="cmul2", latency_offsets={'in0': None, 'out0': None}
 )
diff --git a/test/test_operation.py b/test/test_operation.py
index dc7ad0b1..ebcd1789 100644
--- a/test/test_operation.py
+++ b/test/test_operation.py
@@ -1,6 +1,7 @@
 """
 B-ASIC test suite for the AbstractOperation class.
 """
+
 import re
 
 import pytest
diff --git a/test/test_outputport.py b/test/test_outputport.py
index 3480c2eb..5ebc5655 100644
--- a/test/test_outputport.py
+++ b/test/test_outputport.py
@@ -1,6 +1,7 @@
 """
 B-ASIC test suite for OutputPort.
 """
+
 import pytest
 
 from b_asic import Signal
diff --git a/test/test_sfg.py b/test/test_sfg.py
index ab38f328..eae9c403 100644
--- a/test/test_sfg.py
+++ b/test/test_sfg.py
@@ -7,6 +7,7 @@ import sys
 from os import path, remove
 from typing import Counter, Dict, Type
 
+import numpy as np
 import pytest
 
 from b_asic import Input, Output, Signal
@@ -1772,3 +1773,48 @@ class TestInsertDelays:
         assert source2.type_name() == d_type_name
         assert source3.type_name() == d_type_name
         assert source4.type_name() == bfly.type_name()
+
+
+class TestIterationPeriodBound:
+    def test_accumulator(self, sfg_simple_accumulator):
+        sfg_simple_accumulator.set_latency_of_type('add', 2)
+        assert sfg_simple_accumulator.iteration_period_bound() == 2
+
+    def test_no_latency(self, sfg_simple_accumulator):
+        with pytest.raises(
+            ValueError,
+            match="All native offsets have to set to a non-negative value to",
+        ):
+            sfg_simple_accumulator.iteration_period_bound()
+
+    def test_secondorder_iir(self, precedence_sfg_delays):
+        precedence_sfg_delays.set_latency_of_type('add', 2)
+        precedence_sfg_delays.set_latency_of_type('cmul', 3)
+        assert precedence_sfg_delays.iteration_period_bound() == 10
+
+    def test_no_delays(self, sfg_two_inputs_two_outputs):
+        assert sfg_two_inputs_two_outputs.iteration_period_bound() == -1
+
+
+class TestStateSpace:
+    def test_accumulator(self, sfg_simple_accumulator):
+        ss = sfg_simple_accumulator.state_space_representation()
+        assert ss[0] == ['v0', 'y0']
+        assert (ss[1] == np.array([[1.0, 1.0], [0.0, 1.0]])).all()
+        assert ss[2] == ['v0', 'x0']
+
+    def test_secondorder_iir(self, precedence_sfg_delays):
+        ss = precedence_sfg_delays.state_space_representation()
+        assert ss[0] == ['v0', 'v1', 'y0']
+
+        mat = np.array([[5.0, 2.0, 5.0], [1.0, 0.0, 0.0], [4.0, 6.0, 35.0]])
+        assert (ss[1] == mat).all()
+        assert ss[2] == ['v0', 'v1', 'x0']
+
+    @pytest.mark.xfail()
+    def test_no_delays(self, sfg_two_inputs_two_outputs):
+        ss = sfg_two_inputs_two_outputs.state_space_representation()
+
+        assert ss[0] == ['y0', 'y1']
+        assert (ss[1] == np.array([[1.0, 1.0], [1.0, 2.0]])).all()
+        assert ss[2] == ['x0', 'x1']
-- 
GitLab