# -*- coding: utf-8 -*-
"""
==============
Noise Modeling
==============
"""
from __future__ import annotations
from abc import abstractmethod
from typing import Generic, Optional, TypeVar
from hermespy.core import RandomNode, RandomRealization, Serializable, Signal
__author__ = "Jan Adler"
__copyright__ = "Copyright 2023, Barkhausen Institut gGmbH"
__credits__ = ["Jan Adler"]
__license__ = "AGPLv3"
__version__ = "1.1.0"
__maintainer__ = "Jan Adler"
__email__ = "jan.adler@barkhauseninstitut.org"
__status__ = "Prototype"
[docs]
class NoiseRealization(RandomRealization):
"""Realization of a noise model"""
__power: float
def __init__(self, noise: Noise, power: float) -> None:
"""
Args:
noise (Noise): Noise model to be realized.
power (power): Power indicator of the noise model.
"""
self.__power = power
RandomRealization.__init__(self, noise)
@property
def power(self) -> float:
"""Power of the noise realization.
Returns: Power in Watt.
"""
return self.__power
[docs]
@abstractmethod
def add_to(self, signal: Signal) -> Signal:
"""
Args:
signal (Signal):
The signal to which the noise should be added.
realization (NoiseRealizationType):
Realization of the noise model to be added to `signal`.
power (float, optional)
Power of the added noise.
"""
... # pragma no cover
NoiseRealizationType = TypeVar("NoiseRealizationType", bound=NoiseRealization)
"""Type of noise realization"""
[docs]
class Noise(RandomNode, Generic[NoiseRealizationType]):
"""Noise modeling base class."""
__power: float # Power of the added noise
def __init__(self, power: float = 0.0, seed: Optional[int] = None) -> None:
"""
Args:
power (float, optional):
Power of the added noise.
"""
self.power = power
RandomNode.__init__(self, seed=seed)
[docs]
@abstractmethod
def realize(self, power: Optional[float] = None) -> NoiseRealizationType:
"""Realize the noise model.
Args:
power (float, optional):
Power of the added noise.
If not specified, the class :meth:`Noise.power` configuration will be applied.
Returns: Noise model realization.
"""
... # pragma no cover
[docs]
def add(self, signal: Signal, realization: Optional[NoiseRealizationType] = None) -> Signal:
"""Add noise to a signal model.
Args:
signal (Signal):
The signal to which the noise should be added.
realization (NoiseRealizationType):
Realization of the noise model to be added to `signal`.
Returns: Signal model with added noise.
"""
realization = self.realize() if realization is None else realization
return realization.add_to(signal)
@property
def power(self) -> float:
"""Power of the added noise.
Note that for white Gaussian noise the power is equivalent to the
variance of the added random variable.
Returns:
power (float): Power of the added noise.
Raises:
ValueError: If the `power` is smaller than zero.
"""
return self.__power
@power.setter
def power(self, value: float) -> None:
"""Set power of the added noise."""
if value < 0.0:
raise ValueError("Additive white Gaussian noise power must be greater or equal to zero")
self.__power = value
[docs]
class AWGNRealization(NoiseRealization):
"""Realization of additive white Gaussian noise"""
[docs]
def add_to(self, signal: Signal) -> Signal:
# Create random number generator
rng = self.generator()
noise_samples = (
rng.normal(0, self.power**0.5, signal.samples.shape)
+ 1j * rng.normal(0, self.power**0.5, signal.samples.shape)
) / 2**0.5
noisy_signal = signal.copy()
noisy_signal.samples += noise_samples
noisy_signal.noise_power = self.power
return noisy_signal
[docs]
class AWGN(Serializable, Noise[AWGNRealization]):
"""Additive White Gaussian Noise."""
yaml_tag = "AWGN"
property_blacklist = {"random_mother"}
def __init__(self, power: float = 0.0, seed: Optional[int] = None) -> None:
"""
Args:
power (float, optional):
Power of the added noise.
"""
Noise.__init__(self, power=power, seed=seed)
[docs]
def realize(self, power: Optional[float] = None) -> AWGNRealization:
power = self.power if power is None else power
return AWGNRealization(self, power)