Skip to content

Noise

Complex Gaussian noise generation.

Provides unity-variance complex noise arrays and thermal noise power calculations used to populate the receiver noise floor in the datacube.

band_limited_complex_noise(f_min, f_max, N_samples, fs, normalize=False)

Generates band-limited complex noise within a specified frequency range.

Parameters:

Name Type Description Default
f_min float

Minimum frequency limit in Hz.

required
f_max float

Maximum frequency limit in Hz.

required
N_samples int

Total number of time-domain samples to generate.

required
fs float

Sampling frequency in Hz.

required
normalize bool

If True, normalizes the output signal to have a pointwise magnitude of one. Defaults to False.

False

Returns:

Type Description
ndarray

A complex time-domain array representing the band-limited noise.

Source code in src/rad_lab/noise.py
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
def band_limited_complex_noise(
    f_min: float, f_max: float, N_samples: int, fs: float, normalize: bool = False
) -> np.ndarray:
    """
    Generates band-limited complex noise within a specified frequency range.

    Args:
        f_min: Minimum frequency limit in Hz.
        f_max: Maximum frequency limit in Hz.
        N_samples: Total number of time-domain samples to generate.
        fs: Sampling frequency in Hz.
        normalize: If True, normalizes the output signal to have a pointwise
            magnitude of one. Defaults to False.

    Returns:
        A complex time-domain array representing the band-limited noise.
    """
    if not isinstance(N_samples, int):
        raise TypeError("N_samples must be an integer.")
    if f_min > f_max:
        raise ValueError("f_min must not be greater than f_max.")
    if fs <= 0:
        raise ValueError("Sampling frequency fs must be positive.")

    freqs = fft.fftfreq(N_samples, d=1 / fs)
    spectrum = np.zeros(N_samples, dtype=np.complex64)

    # Create a boolean mask for frequencies within the specified band
    frequency_band_mask = (freqs >= f_min) & (freqs <= f_max)

    # For frequencies in the band, create phasors (complex numbers with magnitude 1)
    num_freqs_in_band = np.sum(frequency_band_mask)
    random_phases = 2 * np.pi * np.random.rand(num_freqs_in_band)
    spectrum[frequency_band_mask] = np.exp(1j * random_phases)

    # Inverse FFT to get the time-domain signal
    noise = fft.ifft(spectrum)

    if normalize:
        magnitude = np.abs(noise)
        # Avoid division by zero for elements with zero magnitude
        return np.divide(
            noise,
            magnitude,
            out=np.zeros_like(noise, dtype=np.complex64),
            where=(magnitude != 0),
        )

    return noise

gaussian_complex_noise(mu, sigma, p, N_samples, fs, normalize=False)

Generates complex noise with a Power Spectral Density (PSD) shaped by a generalized Gaussian envelope.

Parameters:

Name Type Description Default
mu float

Center frequency of the Gaussian PSD in Hz.

required
sigma float

Standard deviation of the Gaussian distribution.

required
p float

Order of the Gaussian distribution (p=1 for standard Gaussian).

required
N_samples int

Total number of samples to generate.

required
fs float

Sampling frequency in Hz.

required
normalize bool

If True, normalizes the output signal to have a pointwise magnitude of one. Defaults to False.

False

Returns:

Type Description
ndarray

A complex time-domain array with Gaussian-shaped spectral content.

Source code in src/rad_lab/noise.py
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
def gaussian_complex_noise(
    mu: float, sigma: float, p: float, N_samples: int, fs: float, normalize: bool = False
) -> np.ndarray:
    """
    Generates complex noise with a Power Spectral Density (PSD) shaped by a
    generalized Gaussian envelope.

    Args:
        mu: Center frequency of the Gaussian PSD in Hz.
        sigma: Standard deviation of the Gaussian distribution.
        p: Order of the Gaussian distribution (p=1 for standard Gaussian).
        N_samples: Total number of samples to generate.
        fs: Sampling frequency in Hz.
        normalize: If True, normalizes the output signal to have a pointwise
            magnitude of one. Defaults to False.

    Returns:
        A complex time-domain array with Gaussian-shaped spectral content.
    """
    if not isinstance(N_samples, int):
        raise TypeError("N_samples must be an integer.")
    if fs <= 0:
        raise ValueError("Sampling frequency fs must be positive.")

    freqs = fft.fftfreq(N_samples, d=1 / fs)

    # Calculate the magnitude of the spectrum based on a generalized Gaussian shape
    gaussian_term = ((freqs - mu) ** 2) / (2 * sigma**2)
    magnitude_spectrum = np.exp(-(gaussian_term**p))

    # The original included a scaling factor from the normal distribution PDF.
    # While not strictly necessary for shaping, we keep it for consistency.
    pdf_scaling_factor = 1 / (sigma * np.sqrt(2 * c.PI))
    magnitude_spectrum *= pdf_scaling_factor

    # Apply a random phase to each frequency component to create the complex spectrum
    random_phases = 2 * c.PI * np.random.rand(N_samples)
    spectrum = magnitude_spectrum * np.exp(1j * random_phases)
    spectrum = spectrum.astype(np.complex64)

    # Inverse FFT to get the time-domain signal.
    # Scaling by sqrt(N_samples) helps preserve power according to Parseval's theorem.
    noise = fft.ifft(spectrum) * np.sqrt(N_samples)

    if normalize:
        magnitude = np.abs(noise)
        # Avoid division by zero for elements with zero magnitude
        return np.divide(
            noise,
            magnitude,
            out=np.zeros_like(noise, dtype=np.complex64),
            where=(magnitude != 0),
        )

    return noise

unity_variance_complex_noise(in_size)

Generates complex Gaussian noise with unity variance.

Parameters:

Name Type Description Default
in_size tuple | int

Shape of the output array.

required

Returns:

Type Description
ndarray

A complex array where the real and imaginary components are

ndarray

independent standard normal distributions, scaled to achieve unit variance.

Source code in src/rad_lab/noise.py
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def unity_variance_complex_noise(in_size: tuple | int) -> np.ndarray:
    """
    Generates complex Gaussian noise with unity variance.

    Args:
        in_size: Shape of the output array.

    Returns:
        A complex array where the real and imaginary components are
        independent standard normal distributions, scaled to achieve unit variance.
    """
    real_part = np.random.standard_normal(size=in_size)
    imag_part = np.random.standard_normal(size=in_size)
    return (real_part + 1j * imag_part) / np.sqrt(2)