From 9996e607956bbd5142dcbc4f41225a8268b4ba26 Mon Sep 17 00:00:00 2001
From: Oscar Gustafsson <oscar.gustafsson@gmail.com>
Date: Mon, 13 Feb 2023 11:44:30 +0100
Subject: [PATCH] Add random signal generators

---
 b_asic/signal_generator.py    | 83 ++++++++++++++++++++++++++++++++++-
 test/test_signal_generator.py | 36 +++++++++++++++
 2 files changed, 118 insertions(+), 1 deletion(-)

diff --git a/b_asic/signal_generator.py b/b_asic/signal_generator.py
index 11c7097b..fce18c42 100644
--- a/b_asic/signal_generator.py
+++ b/b_asic/signal_generator.py
@@ -13,7 +13,9 @@ if you want more information.
 
 from math import pi, sin
 from numbers import Number
-from typing import Sequence
+from typing import Optional, Sequence
+
+import numpy as np
 
 
 class SignalGenerator:
@@ -187,6 +189,85 @@ class Sinusoid(SignalGenerator):
         )
 
 
+class Gaussian(SignalGenerator):
+    """
+    Signal generator with Gaussian noise.
+
+    See :class:`numpy.random.Generator.normal` for further details.
+
+    Parameters
+    ----------
+    seed : int, optional
+        The seed of the random number generator.
+    scale : float, default: 1
+        The standard deviation of the noise.
+    loc : float, default: 0
+        The average value of the noise.
+    """
+
+    def __init__(
+        self, seed: Optional[int] = None, loc: float = 0.0, scale: float = 1.0
+    ) -> None:
+        self._rng = np.random.default_rng(seed)
+        self._seed = seed
+        self._loc = loc
+        self._scale = scale
+
+    def __call__(self, time: int) -> complex:
+        return self._rng.normal(self._loc, self._scale)
+
+    def __repr__(self):
+        ret_list = []
+        if self._seed is not None:
+            ret_list.append(f"seed={self._seed}")
+        if self._loc:
+            ret_list.append(f"loc={self._loc}")
+        if self._scale != 1.0:
+            ret_list.append(f"scale={self._scale}")
+        args = ", ".join(ret_list)
+        return f"Gaussian({args})"
+
+
+class Uniform(SignalGenerator):
+    """
+    Signal generator with uniform noise.
+
+    See :class:`numpy.random.Generator.normal` for further details.
+
+
+    Parameters
+    ----------
+    seed : int, optional
+        The seed of the random number generator.
+    low : float, default: -1
+        The lower value of the uniform range.
+    high : float, default: 1
+        The upper value of the uniform range.
+    """
+
+    def __init__(
+        self, seed: Optional[int] = None, low: float = -1.0, high: float = 1.0
+    ) -> None:
+        self._rng = np.random.default_rng(seed)
+        self._seed = seed
+        self._low = low
+        self._high = high
+
+    def __call__(self, time: int) -> complex:
+        return self._rng.uniform(self._low, self._high)
+
+    def __repr__(self):
+        ret_list = []
+        if self._seed is not None:
+            ret_list.append(f"seed={self._seed}")
+        if self._low != -1.0:
+            ret_list.append(f"low={self._low}")
+        if self._high != 1.0:
+            ret_list.append(f"high={self._high}")
+        args = ", ".join(ret_list)
+        return f"Uniform({args})"
+
+
 class _AddGenerator(SignalGenerator):
     """
     Signal generator that adds two signals.
diff --git a/test/test_signal_generator.py b/test/test_signal_generator.py
index e11423bd..97093a52 100644
--- a/test/test_signal_generator.py
+++ b/test/test_signal_generator.py
@@ -4,9 +4,11 @@ import pytest
 
 from b_asic.signal_generator import (
     Constant,
+    Gaussian,
     Impulse,
     Sinusoid,
     Step,
+    Uniform,
     ZeroPad,
     _AddGenerator,
     _DivGenerator,
@@ -97,6 +99,40 @@ def test_sinusoid():
     assert str(g) == "Sinusoid(0.5, 0.25)"
 
 
+def test_gaussian():
+    g = Gaussian(1234)
+    assert g(0) == pytest.approx(-1.6038368053963015)
+    assert g(1) == pytest.approx(0.06409991400376411)
+
+    assert str(g) == "Gaussian(seed=1234)"
+
+    # Check same seed gives same sequence
+    g1 = Gaussian(12345)
+    g2 = Gaussian(12345)
+
+    for n in range(100):
+        assert g1(n) == g2(n)
+
+    assert str(Gaussian(1234, 1, 2)) == "Gaussian(seed=1234, loc=1, scale=2)"
+
+
+def test_uniform():
+    g = Uniform(1234)
+    assert g(0) == pytest.approx(0.9533995333962844)
+    assert g(1) == pytest.approx(-0.23960852996076443)
+
+    assert str(g) == "Uniform(seed=1234)"
+
+    # Check same seed gives same sequence
+    g1 = Uniform(12345)
+    g2 = Uniform(12345)
+
+    for n in range(100):
+        assert g1(n) == g2(n)
+
+    assert str(Uniform(1234, 1, 2)) == "Uniform(seed=1234, low=1, high=2)"
+
+
 def test_addition():
     g = Impulse() + Impulse(2)
     assert g(-1) == 0
-- 
GitLab