From c2a9e42b295dab2e8959a14abebe6b5298099c21 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Fri, 3 Feb 2023 22:49:50 +0100
Subject: [PATCH] Add initial support for signal generators

---
 b_asic/signal_generator.py           | 139 +++++++++++++++++++++++++++
 docs_sphinx/api/index.rst            |   1 +
 docs_sphinx/api/signal_generator.rst |   6 ++
 test/test_signal_generator.py        |  84 ++++++++++++++++
 4 files changed, 230 insertions(+)
 create mode 100644 b_asic/signal_generator.py
 create mode 100644 docs_sphinx/api/signal_generator.rst
 create mode 100644 test/test_signal_generator.py

diff --git a/b_asic/signal_generator.py b/b_asic/signal_generator.py
new file mode 100644
index 00000000..549c0efd
--- /dev/null
+++ b/b_asic/signal_generator.py
@@ -0,0 +1,139 @@
+"""
+B-ASIC signal generators
+
+These can be used as input to Simulation to algorithmically provide signal values.
+"""
+
+from numbers import Number
+from typing import Callable
+
+
+class SignalGenerator:
+    def __call__(self, time: int) -> complex:
+        raise NotImplementedError
+
+    def __add__(self, other) -> "AddGenerator":
+        if isinstance(other, Number):
+            return AddGenerator(self, Constant(other))
+        return AddGenerator(self, other)
+
+    def __radd__(self, other) -> "AddGenerator":
+        if isinstance(other, Number):
+            return AddGenerator(self, Constant(other))
+        return AddGenerator(self, other)
+
+    def __sub__(self, other) -> "SubGenerator":
+        if isinstance(other, Number):
+            return SubGenerator(self, Constant(other))
+        return SubGenerator(self, other)
+
+    def __rsub__(self, other) -> "SubGenerator":
+        if isinstance(other, Number):
+            return SubGenerator(Constant(other), self)
+        return SubGenerator(other, self)
+
+    def __mul__(self, other) -> "MulGenerator":
+        if isinstance(other, Number):
+            return MultGenerator(self, Constant(other))
+        return MultGenerator(self, other)
+
+    def __rmul__(self, other) -> "MulGenerator":
+        if isinstance(other, Number):
+            return MultGenerator(self, Constant(other))
+        return MultGenerator(self, other)
+
+
+class Impulse(SignalGenerator):
+    """
+    Signal generator that creates an impulse at a given delay.
+
+    Parameters
+    ----------
+    delay : int, default: 0
+        The delay before the signal goes to 1 for one sample.
+    """
+
+    def __init__(self, delay: int = 0) -> Callable[[int], complex]:
+        self._delay = delay
+
+    def __call__(self, time: int) -> complex:
+        return 1 if time == self._delay else 0
+
+
+class Step(SignalGenerator):
+    """
+    Signal generator that creates a step at a given delay.
+
+    Parameters
+    ----------
+    delay : int, default: 0
+        The delay before the signal goes to 1.
+    """
+
+    def __init__(self, delay: int = 0) -> Callable[[int], complex]:
+        self._delay = delay
+
+    def __call__(self, time: int) -> complex:
+        return 1 if time >= self._delay else 0
+
+
+class Constant(SignalGenerator):
+    """
+    Signal generator that outputs a constant value.
+
+    Parameters
+    ----------
+    constant : complex, default: 1.0
+        The constant.
+    """
+
+    def __init__(self, constant: complex = 1.0) -> Callable[[int], complex]:
+        self._constant = constant
+
+    def __call__(self, time: int) -> complex:
+        return self._constant
+
+
+class AddGenerator:
+    """
+    Signal generator that adds two signals.
+    """
+
+    def __init__(
+        self, a: SignalGenerator, b: SignalGenerator
+    ) -> Callable[[int], complex]:
+        self._a = a
+        self._b = b
+
+    def __call__(self, time: int) -> complex:
+        return self._a(time) + self._b(time)
+
+
+class SubGenerator:
+    """
+    Signal generator that subtracts two signals.
+    """
+
+    def __init__(
+        self, a: SignalGenerator, b: SignalGenerator
+    ) -> Callable[[int], complex]:
+        self._a = a
+        self._b = b
+
+    def __call__(self, time: int) -> complex:
+        return self._a(time) - self._b(time)
+
+
+class MultGenerator:
+    """
+    Signal generator that multiplies two signals.
+    """
+
+    def __init__(
+        self, a: SignalGenerator, b: SignalGenerator
+    ) -> Callable[[int], complex]:
+        self._a = a
+        self._b = b
+
+    def __call__(self, time: int) -> complex:
+        return self._a(time) * self._b(time)
diff --git a/docs_sphinx/api/index.rst b/docs_sphinx/api/index.rst
index f6492835..a36b3efc 100644
--- a/docs_sphinx/api/index.rst
+++ b/docs_sphinx/api/index.rst
@@ -14,5 +14,6 @@ API
     sfg_generator.rst
     signal.rst
     signal_flow_graph.rst
+    signal_generator.rst
     simulation.rst
     special_operations.rst
diff --git a/docs_sphinx/api/signal_generator.rst b/docs_sphinx/api/signal_generator.rst
new file mode 100644
index 00000000..0ca472ce
--- /dev/null
+++ b/docs_sphinx/api/signal_generator.rst
@@ -0,0 +1,6 @@
+***************************
+``b_asic.signal_generator``
+***************************
+
+.. automodule:: b_asic.signal_generator
+   :members:
diff --git a/test/test_signal_generator.py b/test/test_signal_generator.py
new file mode 100644
index 00000000..ad07861e
--- /dev/null
+++ b/test/test_signal_generator.py
@@ -0,0 +1,84 @@
+from b_asic.signal_generator import Constant, Impulse, Step
+
+
+def test_impulse():
+    g = Impulse()
+    assert g(0) == 1
+    assert g(1) == 0
+    assert g(2) == 0
+
+    g = Impulse(1)
+    assert g(0) == 0
+    assert g(1) == 1
+    assert g(2) == 0
+
+
+def test_step():
+    g = Step()
+    assert g(0) == 1
+    assert g(1) == 1
+    assert g(2) == 1
+
+    g = Step(1)
+    assert g(0) == 0
+    assert g(1) == 1
+    assert g(2) == 1
+
+
+def test_constant():
+    g = Constant()
+    assert g(0) == 1
+    assert g(1) == 1
+    assert g(2) == 1
+
+    g = Constant(0.5)
+    assert g(0) == 0.5
+    assert g(1) == 0.5
+    assert g(2) == 0.5
+
+
+def test_addition():
+    g = Impulse() + Impulse(2)
+    assert g(0) == 1
+    assert g(1) == 0
+    assert g(2) == 1
+    assert g(3) == 0
+
+    g = 1 + Impulse(2)
+    assert g(0) == 1
+    assert g(1) == 1
+    assert g(2) == 2
+    assert g(3) == 1
+
+    g = Impulse(1) + 1
+    assert g(0) == 1
+    assert g(1) == 2
+    assert g(2) == 1
+    assert g(3) == 1
+
+
+def test_subtraction():
+    g = Impulse() - Impulse(2)
+    assert g(0) == 1
+    assert g(1) == 0
+    assert g(2) == -1
+    assert g(3) == 0
+
+    g = 1 - Impulse(2)
+    assert g(0) == 1
+    assert g(1) == 1
+    assert g(2) == 0
+    assert g(3) == 1
+
+    g = Impulse(2) - 1
+    assert g(0) == -1
+    assert g(1) == -1
+    assert g(2) == 0
+    assert g(3) == -1
+
+
+def test_multiplication():
+    g = Impulse() * 0.5
+    assert g(0) == 0.5
+    assert g(1) == 0
+    assert g(2) == 0
-- 
GitLab