From dde6f31d0279be18a7218b63ab2ef60f50dd2c55 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Thu, 26 Jan 2023 09:58:30 +0100
Subject: [PATCH] Improve typing

---
 b_asic/core_operations.py    |  90 +++++++++++++--------------
 b_asic/graph_component.py    |   6 +-
 b_asic/operation.py          | 116 ++++++++++++++++++++---------------
 b_asic/port.py               |  10 +--
 b_asic/schedule.py           |   2 +-
 b_asic/signal.py             |   6 +-
 b_asic/signal_flow_graph.py  |  14 ++---
 b_asic/special_operations.py |  34 ++++++----
 8 files changed, 151 insertions(+), 127 deletions(-)

diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py
index a6984423..6041228a 100644
--- a/b_asic/core_operations.py
+++ b/b_asic/core_operations.py
@@ -26,19 +26,19 @@ class Constant(AbstractOperation):
 
     _execution_time = 0
 
-    def __init__(self, value: Number = 0, name: Name = ""):
+    def __init__(self, value: Number = 0, name: Name = Name("")):
         """Construct a Constant operation with the given value."""
         super().__init__(
             input_count=0,
             output_count=1,
-            name=name,
+            name=Name(name),
             latency_offsets={"out0": 0},
         )
         self.set_param("value", value)
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "c"
+        return TypeName("c")
 
     def evaluate(self):
         return self.param("value")
@@ -67,7 +67,7 @@ class Addition(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -75,7 +75,7 @@ class Addition(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -83,7 +83,7 @@ class Addition(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "add"
+        return TypeName("add")
 
     def evaluate(self, a, b):
         return a + b
@@ -102,7 +102,7 @@ class Subtraction(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -110,7 +110,7 @@ class Subtraction(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -118,7 +118,7 @@ class Subtraction(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "sub"
+        return TypeName("sub")
 
     def evaluate(self, a, b):
         return a - b
@@ -139,7 +139,7 @@ class AddSub(AbstractOperation):
         is_add: bool = True,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -147,7 +147,7 @@ class AddSub(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -156,7 +156,7 @@ class AddSub(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "addsub"
+        return TypeName("addsub")
 
     def evaluate(self, a, b):
         return a + b if self.is_add else a - b
@@ -185,7 +185,7 @@ class Multiplication(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -193,7 +193,7 @@ class Multiplication(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -201,7 +201,7 @@ class Multiplication(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "mul"
+        return TypeName("mul")
 
     def evaluate(self, a, b):
         return a * b
@@ -220,7 +220,7 @@ class Division(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -228,7 +228,7 @@ class Division(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -236,7 +236,7 @@ class Division(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "div"
+        return TypeName("div")
 
     def evaluate(self, a, b):
         return a / b
@@ -256,7 +256,7 @@ class Min(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -264,7 +264,7 @@ class Min(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -272,7 +272,7 @@ class Min(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "min"
+        return TypeName("min")
 
     def evaluate(self, a, b):
         if isinstance(a, complex) or isinstance(b, complex):
@@ -296,7 +296,7 @@ class Max(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -304,7 +304,7 @@ class Max(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -312,7 +312,7 @@ class Max(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "max"
+        return TypeName("max")
 
     def evaluate(self, a, b):
         if isinstance(a, complex) or isinstance(b, complex):
@@ -334,7 +334,7 @@ class SquareRoot(AbstractOperation):
     def __init__(
         self,
         src0: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -342,7 +342,7 @@ class SquareRoot(AbstractOperation):
         super().__init__(
             input_count=1,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -350,7 +350,7 @@ class SquareRoot(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "sqrt"
+        return TypeName("sqrt")
 
     def evaluate(self, a):
         return sqrt(complex(a))
@@ -368,7 +368,7 @@ class ComplexConjugate(AbstractOperation):
     def __init__(
         self,
         src0: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -376,7 +376,7 @@ class ComplexConjugate(AbstractOperation):
         super().__init__(
             input_count=1,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -384,7 +384,7 @@ class ComplexConjugate(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "conj"
+        return TypeName("conj")
 
     def evaluate(self, a):
         return conjugate(a)
@@ -402,7 +402,7 @@ class Absolute(AbstractOperation):
     def __init__(
         self,
         src0: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -410,7 +410,7 @@ class Absolute(AbstractOperation):
         super().__init__(
             input_count=1,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -418,7 +418,7 @@ class Absolute(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "abs"
+        return TypeName("abs")
 
     def evaluate(self, a):
         return np_abs(a)
@@ -437,7 +437,7 @@ class ConstantMultiplication(AbstractOperation):
         self,
         value: Number = 0,
         src0: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -446,7 +446,7 @@ class ConstantMultiplication(AbstractOperation):
         super().__init__(
             input_count=1,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -455,7 +455,7 @@ class ConstantMultiplication(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "cmul"
+        return TypeName("cmul")
 
     def evaluate(self, a):
         return a * self.param("value")
@@ -486,7 +486,7 @@ class Butterfly(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -494,7 +494,7 @@ class Butterfly(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=2,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -502,7 +502,7 @@ class Butterfly(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "bfly"
+        return TypeName("bfly")
 
     def evaluate(self, a, b):
         return a + b, a - b
@@ -523,7 +523,7 @@ class MAD(AbstractOperation):
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
         src2: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -531,7 +531,7 @@ class MAD(AbstractOperation):
         super().__init__(
             input_count=3,
             output_count=1,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1, src2],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -539,7 +539,7 @@ class MAD(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "mad"
+        return TypeName("mad")
 
     def evaluate(self, a, b, c):
         return a * b + c
@@ -558,7 +558,7 @@ class SymmetricTwoportAdaptor(AbstractOperation):
         value: Number = 0,
         src0: Optional[SignalSourceProvider] = None,
         src1: Optional[SignalSourceProvider] = None,
-        name: Name = "",
+        name: Name = Name(""),
         latency: Optional[int] = None,
         latency_offsets: Optional[Dict[str, int]] = None,
     ):
@@ -566,7 +566,7 @@ class SymmetricTwoportAdaptor(AbstractOperation):
         super().__init__(
             input_count=2,
             output_count=2,
-            name=name,
+            name=Name(name),
             input_sources=[src0, src1],
             latency=latency,
             latency_offsets=latency_offsets,
@@ -575,7 +575,7 @@ class SymmetricTwoportAdaptor(AbstractOperation):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "sym2p"
+        return TypeName("sym2p")
 
     def evaluate(self, a, b):
         tmp = self.value * (b - a)
diff --git a/b_asic/graph_component.py b/b_asic/graph_component.py
index 89f7785c..28deab5a 100644
--- a/b_asic/graph_component.py
+++ b/b_asic/graph_component.py
@@ -113,10 +113,10 @@ class AbstractGraphComponent(GraphComponent):
     _graph_id: GraphID
     _parameters: Dict[str, Any]
 
-    def __init__(self, name: Name = ""):
+    def __init__(self, name: Name = Name("")):
         """Construct a graph component."""
-        self._name = name
-        self._graph_id = ""
+        self._name = Name(name)
+        self._graph_id = GraphID("")
         self._parameters = {}
 
     def __str__(self) -> str:
diff --git a/b_asic/operation.py b/b_asic/operation.py
index 36392e13..ff03277e 100644
--- a/b_asic/operation.py
+++ b/b_asic/operation.py
@@ -18,15 +18,33 @@ from typing import (
     NewType,
     Optional,
     Sequence,
-    Set,
     Tuple,
     Union,
+    TYPE_CHECKING,
 )
 
-from b_asic.graph_component import AbstractGraphComponent, GraphComponent, Name
+from b_asic.graph_component import (
+    AbstractGraphComponent,
+    GraphComponent,
+    GraphID,
+    Name,
+)
 from b_asic.port import InputPort, OutputPort, SignalSourceProvider
 from b_asic.signal import Signal
 
+
+if TYPE_CHECKING:
+    # Conditionally imported to avoid circular imports
+    from b_asic.core_operations import (
+        Addition,
+        Subtraction,
+        Multiplication,
+        ConstantMultiplication,
+        Division,
+    )
+    from b_asic.signal_flow_graph import SFG
+
+
 ResultKey = NewType("ResultKey", str)
 ResultMap = Mapping[ResultKey, Optional[Number]]
 MutableResultMap = MutableMapping[ResultKey, Optional[Number]]
@@ -63,7 +81,9 @@ class Operation(GraphComponent, SignalSourceProvider):
         raise NotImplementedError
 
     @abstractmethod
-    def __sub__(self, src: Union[SignalSourceProvider, Number]) -> "Subtraction":
+    def __sub__(
+        self, src: Union[SignalSourceProvider, Number]
+    ) -> "Subtraction":
         """
         Overloads the subtraction operator to make it return a new Subtraction operation
         object that is connected to the self and other objects.
@@ -305,10 +325,9 @@ class Operation(GraphComponent, SignalSourceProvider):
 
     @property
     @abstractmethod
-    def latency_offsets(self) -> Sequence[Sequence[int]]:
-        """Get a nested list with all the operations ports latency-offsets, the first list contains the
-        latency-offsets of the operations input ports, the second list contains the latency-offsets of
-        the operations output ports.
+    def latency_offsets(self) -> Dict[str, int]:
+        """
+        Get a dictionary with all the operations ports latency-offsets.
         """
         raise NotImplementedError
 
@@ -332,7 +351,7 @@ class Operation(GraphComponent, SignalSourceProvider):
 
     @property
     @abstractmethod
-    def execution_time(self) -> int:
+    def execution_time(self) -> Optional[int]:
         """
         Get the execution time of the operation, which is the time it takes before the
         processing element implementing the operation can be reused for starting another operation.
@@ -351,7 +370,7 @@ class Operation(GraphComponent, SignalSourceProvider):
     @abstractmethod
     def get_plot_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
         """
         Get a tuple constaining coordinates for the two polygons outlining
         the latency and execution time of the operation.
@@ -362,7 +381,7 @@ class Operation(GraphComponent, SignalSourceProvider):
     @abstractmethod
     def get_io_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
         """
         Get a tuple constaining coordinates for inputs and outputs, respectively.
         These maps to the polygons and are corresponding to a start time of 0
@@ -387,7 +406,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
         self,
         input_count: int,
         output_count: int,
-        name: Name = "",
+        name: Name = Name(""),
         input_sources: Optional[
             Sequence[Optional[SignalSourceProvider]]
         ] = None,
@@ -404,7 +423,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
         The latency offsets may also be specified to be initialized.
         """
-        super().__init__(name)
+        super().__init__(Name(name))
 
         self._input_ports = [InputPort(self, i) for i in range(input_count)]
         self._output_ports = [OutputPort(self, i) for i in range(output_count)]
@@ -421,26 +440,18 @@ class AbstractOperation(Operation, AbstractGraphComponent):
                 if src is not None:
                     self._input_ports[i].connect(src.source)
 
-        ports_without_latency_offset = set(
-            (
-                [f"in{i}" for i in range(self.input_count)]
-                + [f"out{i}" for i in range(self.output_count)]
-            )
-        )
-
-        if latency_offsets is not None:
-            self.set_latency_offsets(latency_offsets)
-
         if latency is not None:
-            # Set the latency of the rest of ports with no latency_offset.
+            # Set the latency for all ports initially.
             if latency < 0:
                 raise ValueError("Latency cannot be negative")
             for inp in self.inputs:
-                if inp.latency_offset is None:
-                    inp.latency_offset = 0
+                inp.latency_offset = 0
             for outp in self.outputs:
-                if outp.latency_offset is None:
-                    outp.latency_offset = latency
+                outp.latency_offset = latency
+
+        # Set specific latency_offsets
+        if latency_offsets is not None:
+            self.set_latency_offsets(latency_offsets)
 
         self._execution_time = execution_time
 
@@ -550,41 +561,41 @@ class AbstractOperation(Operation, AbstractGraphComponent):
     def __str__(self) -> str:
         """Get a string representation of this operation."""
         inputs_dict = {}
-        for i, port in enumerate(self.inputs):
-            if port.signal_count == 0:
+        for i, inport in enumerate(self.inputs):
+            if inport.signal_count == 0:
                 inputs_dict[i] = "-"
                 break
             dict_ele = []
-            for signal in port.signals:
+            for signal in inport.signals:
                 if signal.source:
                     if signal.source.operation.graph_id:
                         dict_ele.append(signal.source.operation.graph_id)
                     else:
-                        dict_ele.append("no_id")
+                        dict_ele.append(GraphID("no_id"))
                 else:
                     if signal.graph_id:
                         dict_ele.append(signal.graph_id)
                     else:
-                        dict_ele.append("no_id")
+                        dict_ele.append(GraphID("no_id"))
             inputs_dict[i] = dict_ele
 
         outputs_dict = {}
-        for i, port in enumerate(self.outputs):
-            if port.signal_count == 0:
+        for i, outport in enumerate(self.outputs):
+            if outport.signal_count == 0:
                 outputs_dict[i] = "-"
                 break
             dict_ele = []
-            for signal in port.signals:
+            for signal in outport.signals:
                 if signal.destination:
                     if signal.destination.operation.graph_id:
                         dict_ele.append(signal.destination.operation.graph_id)
                     else:
-                        dict_ele.append("no_id")
+                        dict_ele.append(GraphID("no_id"))
                 else:
                     if signal.graph_id:
                         dict_ele.append(signal.graph_id)
                     else:
-                        dict_ele.append("no_id")
+                        dict_ele.append(GraphID("no_id"))
             outputs_dict[i] = dict_ele
 
         return (
@@ -638,7 +649,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
             key += str(index)
         elif not key:
             key = str(index)
-        return key
+        return ResultKey(key)
 
     def current_output(
         self, index: int, delays: Optional[DelayMap] = None, prefix: str = ""
@@ -871,7 +882,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
         )
 
     @property
-    def latency_offsets(self) -> Sequence[Sequence[int]]:
+    def latency_offsets(self) -> Dict[str, int]:
         latency_offsets = {}
 
         for i, inp in enumerate(self.inputs):
@@ -914,7 +925,7 @@ class AbstractOperation(Operation, AbstractGraphComponent):
                 )
 
     @property
-    def execution_time(self) -> Union[int, None]:
+    def execution_time(self) -> Optional[int]:
         """Execution time of operation."""
         return self._execution_time
 
@@ -924,13 +935,13 @@ class AbstractOperation(Operation, AbstractGraphComponent):
             raise ValueError("Execution time cannot be negative")
         self._execution_time = execution_time
 
-    def _increase_time_resolution(self, factor: int):
+    def _increase_time_resolution(self, factor: int) -> None:
         if self._execution_time is not None:
             self._execution_time *= factor
         for port in [*self.inputs, *self.outputs]:
             port.latency_offset *= factor
 
-    def _decrease_time_resolution(self, factor: int):
+    def _decrease_time_resolution(self, factor: int) -> None:
         if self._execution_time is not None:
             self._execution_time = self._execution_time // factor
         for port in [*self.inputs, *self.outputs]:
@@ -938,37 +949,39 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
     def get_plot_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
+        # Doc-string inherited
         return (
             self._get_plot_coordinates_for_latency(),
             self._get_plot_coordinates_for_execution_time(),
         )
 
-    def _get_plot_coordinates_for_execution_time(self) -> List[List[Number]]:
+    def _get_plot_coordinates_for_execution_time(self) -> List[List[float]]:
         # Always a rectangle, but easier if coordinates are returned
-        if self._execution_time is None:
+        execution_time = self._execution_time  # Copy for type checking
+        if execution_time is None:
             return []
         return [
             [0, 0],
             [0, 1],
-            [self.execution_time, 1],
-            [self.execution_time, 0],
+            [execution_time, 1],
+            [execution_time, 0],
             [0, 0],
         ]
 
-    def _get_plot_coordinates_for_latency(self) -> List[List[Number]]:
+    def _get_plot_coordinates_for_latency(self) -> List[List[float]]:
         # Points for latency polygon
         latency = []
         # Remember starting point
         start_point = [self.inputs[0].latency_offset, 0]
-        num_in = len(self.inputs)
+        num_in = self.input_count
         latency.append(start_point)
         for k in range(1, num_in):
             latency.append([self.inputs[k - 1].latency_offset, k / num_in])
             latency.append([self.inputs[k].latency_offset, k / num_in])
         latency.append([self.inputs[num_in - 1].latency_offset, 1])
 
-        num_out = len(self.outputs)
+        num_out = self.output_count
         latency.append([self.outputs[num_out - 1].latency_offset, 1])
         for k in reversed(range(1, num_out)):
             latency.append([self.outputs[k].latency_offset, k / num_out])
@@ -981,7 +994,8 @@ class AbstractOperation(Operation, AbstractGraphComponent):
 
     def get_io_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
+        # Doc-string inherited
         input_coords = [
             [
                 self.inputs[k].latency_offset,
diff --git a/b_asic/port.py b/b_asic/port.py
index 0ea0b28f..39dd4d98 100644
--- a/b_asic/port.py
+++ b/b_asic/port.py
@@ -42,7 +42,7 @@ class Port(ABC):
 
     @property
     @abstractmethod
-    def latency_offset(self) -> int:
+    def latency_offset(self) -> Optional[int]:
         """Get the latency_offset of the port."""
         raise NotImplementedError
 
@@ -126,7 +126,7 @@ class AbstractPort(Port):
         return self._index
 
     @property
-    def latency_offset(self) -> int:
+    def latency_offset(self) -> Optional[int]:
         return self._latency_offset
 
     @latency_offset.setter
@@ -199,7 +199,9 @@ class InputPort(AbstractPort):
             None if self._source_signal is None else self._source_signal.source
         )
 
-    def connect(self, src: SignalSourceProvider, name: Name = "") -> Signal:
+    def connect(
+        self, src: SignalSourceProvider, name: Name = Name("")
+    ) -> Signal:
         """
         Connect the provided signal source to this input port by creating a new signal.
         Returns the new signal.
@@ -207,7 +209,7 @@ class InputPort(AbstractPort):
         if self._source_signal is not None:
             raise ValueError("Cannot connect already connected input port.")
         # self._source_signal is set by the signal constructor.
-        return Signal(source=src.source, destination=self, name=name)
+        return Signal(source=src.source, destination=self, name=Name(name))
 
     def __lshift__(self, src: SignalSourceProvider) -> Signal:
         """
diff --git a/b_asic/schedule.py b/b_asic/schedule.py
index 4db660c5..acaa2db7 100644
--- a/b_asic/schedule.py
+++ b/b_asic/schedule.py
@@ -136,7 +136,7 @@ class Schedule:
 
     def _backward_slacks(
         self, op_id: GraphID
-    ) -> Dict["OutputPort", Dict["Signal", int]]:
+    ) -> Dict[OutputPort, Dict[Signal, int]]:
         ret = {}
         start_time = self._start_times[op_id]
         op = self._sfg.find_by_id(op_id)
diff --git a/b_asic/signal.py b/b_asic/signal.py
index 0ec3cfa6..b9b80383 100644
--- a/b_asic/signal.py
+++ b/b_asic/signal.py
@@ -27,10 +27,10 @@ class Signal(AbstractGraphComponent):
         source: Optional["OutputPort"] = None,
         destination: Optional["InputPort"] = None,
         bits: Optional[int] = None,
-        name: Name = "",
+        name: Name = Name(""),
     ):
         """Construct a Signal."""
-        super().__init__(name)
+        super().__init__(Name(name))
         self._source = None
         self._destination = None
         if source is not None:
@@ -41,7 +41,7 @@ class Signal(AbstractGraphComponent):
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "s"
+        return TypeName("s")
 
     @property
     def neighbors(self) -> Iterable[GraphComponent]:
diff --git a/b_asic/signal_flow_graph.py b/b_asic/signal_flow_graph.py
index 55aed0ed..3adf217e 100644
--- a/b_asic/signal_flow_graph.py
+++ b/b_asic/signal_flow_graph.py
@@ -63,12 +63,12 @@ class GraphIDGenerator:
         while new_id in used_ids:
             self._next_id_number[type_name] += 1
             new_id = type_name + str(self._next_id_number[type_name])
-        return new_id
+        return GraphID(new_id)
 
     @property
     def id_number_offset(self) -> GraphIDNumber:
         """Get the graph id number offset of this generator."""
-        return (
+        return GraphIDNumber(
             self._next_id_number.default_factory()
         )  # pylint: disable=not-callable
 
@@ -300,7 +300,7 @@ class SFG(AbstractOperation):
         return string_io.getvalue()
 
     def __call__(
-        self, *src: Optional[SignalSourceProvider], name: Name = ""
+        self, *src: Optional[SignalSourceProvider], name: Name = Name("")
     ) -> "SFG":
         """
         Get a new independent SFG instance that is identical to this SFG
@@ -310,13 +310,13 @@ class SFG(AbstractOperation):
             inputs=self._input_operations,
             outputs=self._output_operations,
             id_number_offset=self.id_number_offset,
-            name=name,
+            name=Name(name),
             input_sources=src if src else None,
         )
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "sfg"
+        return TypeName("sfg")
 
     def evaluate(self, *args):
         result = self.evaluate_outputs(args)
@@ -424,14 +424,14 @@ class SFG(AbstractOperation):
         return True
 
     @property
-    def input_operations(self) -> Sequence[Operation]:
+    def input_operations(self) -> Iterable[Operation]:
         """
         Get the internal input operations in the same order as their respective input ports.
         """
         return self._input_operations
 
     @property
-    def output_operations(self) -> Sequence[Operation]:
+    def output_operations(self) -> Iterable[Operation]:
         """
         Get the internal output operations in the same order as their respective output ports.
         """
diff --git a/b_asic/special_operations.py b/b_asic/special_operations.py
index 02a80dac..b63e112d 100644
--- a/b_asic/special_operations.py
+++ b/b_asic/special_operations.py
@@ -14,7 +14,6 @@ from b_asic.operation import (
     DelayMap,
     MutableDelayMap,
     MutableResultMap,
-    ResultKey,
 )
 from b_asic.port import SignalSourceProvider
 
@@ -29,19 +28,19 @@ class Input(AbstractOperation):
 
     _execution_time = 0
 
-    def __init__(self, name: Name = ""):
+    def __init__(self, name: Name = Name("")):
         """Construct an Input operation."""
         super().__init__(
             input_count=0,
             output_count=1,
-            name=name,
+            name=Name(name),
             latency_offsets={"out0": 0},
         )
         self.set_param("value", 0)
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "in"
+        return TypeName("in")
 
     def evaluate(self):
         return self.param("value")
@@ -58,7 +57,8 @@ class Input(AbstractOperation):
 
     def get_plot_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
+        # Doc-string inherited
         return (
             [
                 [-0.5, 0],
@@ -80,7 +80,8 @@ class Input(AbstractOperation):
 
     def get_io_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
+        # Doc-string inherited
         return ([], [[0, 0.5]])
 
 
@@ -96,27 +97,30 @@ class Output(AbstractOperation):
     _execution_time = 0
 
     def __init__(
-        self, src0: Optional[SignalSourceProvider] = None, name: Name = ""
+        self,
+        src0: Optional[SignalSourceProvider] = None,
+        name: Name = Name(""),
     ):
         """Construct an Output operation."""
         super().__init__(
             input_count=1,
             output_count=0,
-            name=name,
+            name=Name(name),
             input_sources=[src0],
             latency_offsets={"in0": 0},
         )
 
     @classmethod
     def type_name(cls) -> TypeName:
-        return "out"
+        return TypeName("out")
 
     def evaluate(self, _):
         return None
 
     def get_plot_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
+        # Doc-string inherited
         return (
             [[0, 0], [0, 1], [0.25, 1], [0.5, 0.5], [0.25, 0], [0, 0]],
             [[0, 0], [0, 1], [0.25, 1], [0.5, 0.5], [0.25, 0], [0, 0]],
@@ -124,7 +128,8 @@ class Output(AbstractOperation):
 
     def get_io_coordinates(
         self,
-    ) -> Tuple[List[List[Number]], List[List[Number]]]:
+    ) -> Tuple[List[List[float]], List[List[float]]]:
+        # Doc-string inherited
         return ([[0, 0.5]], [])
 
 
@@ -140,11 +145,14 @@ class Delay(AbstractOperation):
         self,
         src0: Optional[SignalSourceProvider] = None,
         initial_value: Number = 0,
-        name: Name = "",
+        name: Name = Name(""),
     ):
         """Construct a Delay operation."""
         super().__init__(
-            input_count=1, output_count=1, name=name, input_sources=[src0]
+            input_count=1,
+            output_count=1,
+            name=Name(name),
+            input_sources=[src0],
         )
         self.set_param("initial_value", initial_value)
 
-- 
GitLab