sdr.awgn(x: ArrayLike, snr: float | None = None, noise: float | None = None, seed: int | None = None) NDArray

Adds additive white Gaussian noise (AWGN) to the time-domain signal \(x[n]\).

Parameters:
x: ArrayLike

The time-domain signal \(x[n]\) to which AWGN is added.

snr: float | None = None

The desired signal-to-noise ratio (SNR) in dB. If specified, the average signal power is measured explicitly. It is assumed that \(x[n]\) contains signal only. If the signal power is known, the desired noise variance can be computed and passed in noise. If snr is None, noise must be specified.

noise: float | None = None

The noise power (variance) in linear units. If noise is None, snr must be specified.

seed: int | None = None

The seed for the random number generator. This is passed to numpy.random.default_rng().

Returns:

The noisy signal \(x[n] + w[n]\).

Notes

The signal-to-noise ratio (SNR) is defined as

\[ \text{SNR} = \frac{P_{\text{signal,avg}}}{P_{\text{noise}}} = \frac{\frac{1}{N} \sum_{n=0}^{N-1} \left| x[n] \right|^2}{\sigma^2} , \]

where \(\sigma^2\) is the noise variance. The output signal, with the specified SNR, is \(y[n] = x[n] + w[n]\).

For real signals:

\[w \sim \mathcal{N}(0, \sigma^2)\]

For complex signals:

\[w \sim \mathcal{CN}(0, \sigma^2) = \mathcal{N}(0, \sigma^2 / 2) + j\mathcal{N}(0, \sigma^2 / 2)\]

Examples

Create a real sinusoid and set its \(S/N\) to 10 dB.

In [1]: x = np.sin(2 * np.pi * 5 * np.arange(100) / 100); \
   ...: y = sdr.awgn(x, snr=10)
   ...: 

In [2]: plt.figure(); \
   ...: sdr.plot.time_domain(x, label="$x[n]$"); \
   ...: sdr.plot.time_domain(y, label="$y[n]$"); \
   ...: plt.title("Input signal $x[n]$ and noisy output signal $y[n]$ with 10 dB SNR");
   ...: 
../../_images/sdr_awgn_1.png

Create a QPSK reference signal and set its \(E_s/N_0\) to 10 dB. When the signal has 1 sample per symbol, \(E_s/N_0\) is equivalent to the discrete-time \(S/N\).

In [3]: psk = sdr.PSK(4, phase_offset=45); \
   ...: s = np.random.randint(0, psk.order, 1_000); \
   ...: x = psk.map_symbols(s); \
   ...: y = sdr.awgn(x, snr=10)
   ...: 

In [4]: plt.figure(); \
   ...: sdr.plot.constellation(x, label="$x[n]$", zorder=2); \
   ...: sdr.plot.constellation(y, label="$y[n]$", zorder=1); \
   ...: plt.title(f"QPSK constellations for $x[n]$ with $\infty$ dB $E_s/N_0$\nand $y[n]$ with 10 dB $E_s/N_0$");
   ...: 
../../_images/sdr_awgn_2.png