Source code for doatools.model.signals

from abc import ABC, abstractmethod
import numpy as np
from scipy.linalg import sqrtm
from ..utils.math import randcn

[docs]class SignalGenerator(ABC): """Abstrace base class for all signal generators. Extend this class to create your own signal generators. """ @property @abstractmethod def dim(self): """Retrieves the dimension of the signal generator.""" pass
[docs] @abstractmethod def emit(self, n): """Emits the signal matrix. Generates a k x n matrix where k is the dimension of the signal and each column represents a sample. """ pass
[docs]class ComplexStochasticSignal(SignalGenerator): """Creates a signal generator that generates zero-mean complex circularly-symmetric Gaussian signals. Args: dim (int): Dimension of the complex Gaussian distribution. Must match the size of ``C`` if ``C`` is not a scalar. C: Covariance matrix of the complex Gaussian distribution. Can be specified by 1. A full covariance matrix. 2. An real vector denoting the diagonals of the covariance matrix if the covariance matrix is diagonal. 3. A scalar if the covariance matrix is diagonal and all diagonal elements share the same value. In this case, parameter n must be specified. Default value is `1.0`. """ def __init__(self, dim, C=1.0): self._dim = dim if np.isscalar(C): # Scalar self._C2 = np.sqrt(C) self._generator = lambda n: self._C2 * randcn((self._dim, n)) elif C.ndim == 1: # Vector if C.size != dim: raise ValueError('The size of C must be {0}.'.format(dim)) self._C2 = np.sqrt(C).reshape((-1, 1)) self._generator = lambda n: self._C2 * randcn((self._dim, n)) elif C.ndim == 2: # Matrix if C.shape[0] != dim or C.shape[1] != dim: raise ValueError('The shape of C must be ({0}, {0}).'.format(dim)) self._C2 = sqrtm(C) self._generator = lambda n: self._C2 @ randcn((self._dim, n)) else: raise ValueError( 'The covariance must be specified by a scalar, a vector of' 'size {0}, or a matrix of {0}x{0}.'.format(dim) ) self._C = C @property def dim(self): return self._dim
[docs] def emit(self, n): return self._generator(n)
[docs]class RandomPhaseSignal(SignalGenerator): r"""Creates a random phase signal generator. The phases are uniformly and independently sampled from :math:`[-\pi, \pi]`. Args: dim (int): Dimension of the signal (usually equal to the number of sources). amplitudes: Amplitudes of the signal. Can be specified by 1. A scalar if all sources have the same amplitude. 2. A vector if the sources have different amplitudes. """ def __init__(self, dim, amplitudes=1.0): self._dim = dim if np.isscalar(amplitudes): self._amplitudes = np.full(amplitudes, (dim, 1)) else: if amplitudes.size != dim: raise ValueError("The size of 'amplitudes' does not match the value of 'dim'.") self._amplitudes = amplitudes.reshape((-1, 1)) @property def dim(self): return self._dim
[docs] def emit(self, n): phases = np.random.uniform(-np.pi, np.pi, (self._dim, n)) c = np.sin(phases) * 1j c += np.cos(phases) return self._amplitudes * c