Source code for denspp.offline.analog.dev_noise

import numpy as np
from logging import getLogger
from dataclasses import dataclass
from scipy.constants import Boltzmann, elementary_charge


[docs] @dataclass class SettingsNoise: """Settings for configuring the emulation of noise properties Attributes: temp: Temperature [K] wgn_dB: Effective spectral input noise power [dBW/sqrt(Hz)] Fc: Corner frequency of the flicker (1/f) noise [Hz] slope: Alpha coefficient of the flicker noise [] """ temp: float wgn_dB: float Fc: float slope: float @property def temp_celsius(self) -> float: return self.temp - 273.15 @property def noise_pwr(self) -> float: return 4 * Boltzmann * self.temp_celsius @property def temperature_voltage(self) -> float: return Boltzmann * self.temp / elementary_charge
RecommendedSettingsNoise = SettingsNoise( temp=300, wgn_dB=-120, Fc=10, slope=0.6 )
[docs] class ProcessNoise: __print_device: str = "" def __init__(self, settings: SettingsNoise, fs_ana: float) -> None: """Processing analog noise for transient signals of electrical devices :param settings: Dataclass for using noise simulation :param fs_ana: Sampling frequency [Hz] """ self.__settings_noise = settings self.__noise_sampling_rate = fs_ana self._logger = getLogger(__name__) ######################## FUNCTIONS FOR GENERATE NOISE ######################## def __do_fft(self, y: np.ndarray) -> dict: window = np.hanning(y.size) fft_in = window * y N = y.size // 2 fft_out = 2 / N * np.abs(np.fft.fft(fft_in)) fft_out[0] = fft_out[0] / 2 freq = self.__noise_sampling_rate * np.fft.fftfreq(fft_out.size) # Taking positive range xsel = np.where(freq >= 0) fft_out = fft_out[xsel] freq = freq[xsel] return {'freq': freq, 'Y': fft_out} @staticmethod def __noise_awgn(size: int, fs: float, e_n: float) -> np.ndarray: """Generation of transient noise signal with spectral noise power [dBW/sqrt(Hz)] Args: e_n: Spectral noise power density [dBW/sqrt(Hz)] fs: Sampling rate size: Number of points Returns: Numpy array with white gaussian noise signal """ rho = 10 ** (e_n / 10) sigma = rho * np.sqrt(fs / 2) return np.random.randn(size) * sigma @staticmethod def __noise_flicker(size: int, alpha: float) -> np.ndarray: """Generating flicker noise Args: size: no of points alpha: Slope of the pink noise Returns: Numpy array with pink/flicker noise signal """ window_size = 2 * size + (size % 2) # --- Thermal noise y_noise = np.random.randn(window_size) window = np.hanning(window_size) y_pink = np.fft.fft(window * y_noise) y_pink[0] = y_pink[0] / 2 # --- Flicker spectral component n = np.arange(1, size + 1, 1) n = np.power(n, alpha) # --- Generate full spectrum y = y_pink[0:size] / n y_pos = y[np.newaxis, :] y_neg = np.fliplr(y_pos) y0 = np.concatenate((y_pos, y_neg), axis=None) # --- Generate pink noise u_pink = np.real(np.fft.ifft(y0)[0:size]) u_pink -= np.mean(u_pink) return u_pink def __noise_real(self, tsize: int, fs: float, e_n: float, fc: float, alpha: float) -> np.ndarray: """Generation of real noise signal with specific noise power [dBW], corner frequency Fc, and slope of 1/f noise Args: tsize: Size of time vector fs: Sampling frequency [in Hz] e_n: Spectral power density for the white gaussian noise generation [in dBW/sqrt(Hz)] fc: Corner frequency of the white and pink noise alpha: Slope of the pink noise Returns: Numpy array with real noise signal """ # --- Generate noise components and match e_white = self.__noise_awgn(tsize, fs, e_n) e_pink = self.__noise_flicker(tsize, alpha) # --- Adapting the amplitude fft_white = self.__do_fft(e_white) fft_pink = self.__do_fft(e_pink) # --- Find corner frequency x_corner = np.argwhere(fft_white['freq'] >= fc)[0] n_mean = 100 y_wgm = np.convolve(fft_white['Y'], np.ones(n_mean) / n_mean, mode='same') y_pnk = np.convolve(fft_pink['Y'], np.ones(n_mean) / n_mean, mode='same') scalef = y_wgm[x_corner] / y_pnk[x_corner] # --- Generate output noise return scalef * e_pink + e_white ######################## FUNCTIONS FOR HANDLING NOISE ######################## @staticmethod def __calc_spectral_noise_device(dev_val: float) -> float: """Calculating the noise spectral density value""" assert dev_val > 0, "Apply only positive values" return 10 * np.log10(dev_val) def __calc_spectral_noise_pwr(self, fs: float) -> float: """Calculating the noise spectral density value""" val = self.__settings_noise.noise_pwr / fs return 3.01 + 10 * np.log10(val) def __calc_spectral_noise_volt(self, fs: float, resistance: float) -> float: """Calculating the noise spectral density value""" val = np.sqrt(2 * self.__settings_noise.noise_pwr / fs * resistance) return 10 * np.log10(val) def __calc_spectral_noise_curr(self, fs: float, resistance: float) -> float: """Calculating the noise spectral density value""" val = np.sqrt(2 * self.__settings_noise.noise_pwr / fs / resistance) return 3.01 + 10 * np.log10(val) @staticmethod def _calculate_params(noise_in: np.ndarray) -> [float, float]: """Calculating the parameters of effective input and std :param noise_in: Numpy array of the noise input :return: List with effective noise value and peak-to-peak noise value """ noise_eff = np.std(noise_in) noise_pp = np.max(noise_in) - np.min(noise_in) return noise_eff, noise_pp def _do_print(self, noise_in: np.ndarray, mode_output: int=0) -> None: """Printing output from noise analysis :param noise_in: Numpy array with generated noise signal :param mode_output: Output mode [0: power in mW, 1: voltage in µV, 2: current in nA] """ match mode_output: case 0: unit_text = "mW" unit_scale = 1e3 unit_type = "power" case 1: unit_text = "µV" unit_scale = 1e6 unit_type = "voltage" case 2: unit_text = "nA" unit_scale = 1e9 unit_type = "current" case _: unit_text = "" unit_scale = 1e0 unit_type = "" text_dev = f"" if len(self.__print_device) == 0 else f" ({self.__print_device})" noise_eff, noise_pp = self._calculate_params(noise_in) self._logger.debug(f"... effective input noise {unit_type}{text_dev}: {unit_scale * noise_eff:.4f} {unit_text}") self._logger.debug(f"... effective peak-to-peak noise {unit_type}{text_dev}: {unit_scale * noise_pp:.4f} {unit_text}")
[docs] def gen_noise_real_pwr(self, size: int, e_n: float=0.0) -> np.ndarray: """Generating real transient noise power Args: size: Number of iterations e_n: Spectral power noise density [dBW/sqrt(Hz)] Returns: Numpy array with transient noise """ u_noise = self.__noise_real( tsize=size, fs=self.__noise_sampling_rate, e_n=self.__settings_noise.wgn_dB if e_n == 0.0 else e_n, fc=self.__settings_noise.Fc, alpha=self.__settings_noise.slope ) self._do_print(u_noise, 0) return u_noise
[docs] def gen_noise_real_volt(self, size: int, resistance: float) -> np.ndarray: """Generating real transient noise voltage Args: size: Number of iterations resistance: Resistance value [Ohm] Returns: Numpy array with transient noise """ u_noise = self.__noise_real( tsize=size, fs=self.__noise_sampling_rate, e_n=self.__calc_spectral_noise_volt(self.__noise_sampling_rate, resistance), fc=self.__settings_noise.Fc, alpha=self.__settings_noise.slope ) self._do_print(u_noise, 1) return u_noise
[docs] def gen_noise_real_curr(self, size: int, resistance: float) -> np.ndarray: """Generating real transient noise current Args: size: Number of iterations resistance: Resistance value [Ohm] Returns: Numpy array with transient noise """ u_noise = self.__noise_real( tsize=size, fs=self.__noise_sampling_rate, e_n=self.__calc_spectral_noise_curr(self.__noise_sampling_rate, resistance), fc=self.__settings_noise.Fc, alpha=self.__settings_noise.slope ) self._do_print(u_noise, 2) return u_noise
[docs] def gen_noise_awgn_dev(self, size: int, dev_e: float) -> np.ndarray: """Generating white transient noise power Args: size: Number of iterations dev_e: Spectral noise voltage density from device [V/sqrt(Hz)] Returns: Numpy array with transient noise """ u_noise = self.__noise_awgn( size=size, fs=self.__noise_sampling_rate, e_n=self.__calc_spectral_noise_device(dev_e) ) self._do_print(u_noise, 0) return u_noise
[docs] def gen_noise_awgn_pwr(self, size: int, e_n: float=0.0) -> np.ndarray: """Generating white transient noise power Args: size: Number of iterations e_n: Spectral power noise density [dBW/sqrt(Hz)] Returns: Numpy array with transient noise """ u_noise = self.__noise_awgn( size=size, fs=self.__noise_sampling_rate, e_n=self.__settings_noise.wgn_dB if e_n == 0.0 else e_n, ) self._do_print(u_noise, 0) return u_noise
[docs] def gen_noise_awgn_volt(self, size: int, resistance: float) -> np.ndarray: """Generating white transient noise voltage Args: size: Number of iterations resistance: Resistance value [Ohm] Returns: Numpy array with transient noise """ u_noise = self.__noise_awgn( size=size, fs=self.__noise_sampling_rate, e_n=self.__calc_spectral_noise_volt(self.__noise_sampling_rate, resistance) ) self._do_print(u_noise, 1) return u_noise
[docs] def gen_noise_awgn_curr(self, size: int, resistance: float) -> np.ndarray: """Generating white transient noise current Args: size: Number of iterations resistance: Resistance value [Ohm] Returns: Numpy array with transient noise """ u_noise = self.__noise_awgn( size=size, fs=self.__noise_sampling_rate, e_n=self.__calc_spectral_noise_curr(self.__noise_sampling_rate, resistance) ) self._do_print(u_noise, 2) return u_noise
[docs] def gen_noise_flicker_volt(self, size: int) -> np.ndarray: """Generating white transient flicker noise voltage Args: size: Number of iterations alpha: Slope of flicker component in spectral part Returns: Numpy array with transient flicker noise """ return self.__noise_flicker(size, self.__settings_noise.slope)