Scientific validation: additive white Gaussian noise channel

Validated.

1. The component

AWGNChannel (additive white Gaussian noise channel, a channel that adds random noise independently to every sample) adds complex Gaussian noise to an in-phase / quadrature (IQ) baseband signal so that the realised signal-to-noise ratio (SNR, the ratio of signal power to noise power) equals a caller-specified value in decibels (dB). It is the standard first-order channel model in digital communications: spectrally flat noise (same power at every frequency), statistically independent samples, and purely additive, with no frequency shift, no multipath fading, and no distortion.

class AWGNChannel(BaseChannelPropagation):
    transformation: ClassVar[Transformation] = Transformation.PROPAGATION

    def __init__(self, *, snr_db: float = 20.0) -> None: ...

    def apply(self, signal: Signal, ctx: ChannelContext) -> Signal: ...

Parameter

Type

Units

Default

Purpose

snr_db

float

dB

20.0

Target signal-to-noise ratio in decibels. The implementation adjusts noise power so that the measured SNR of the output equals this value. Must be finite; negative values produce noise-dominated output.

import torch
from rfgen.channels.protocols import ChannelContext, ChannelRxParams
from rfgen.core_types import Signal, SignalMetadata
from rfgen.propagation import AWGNChannel

# Construct a 2 048-sample continuous-wave (CW) signal: I = 1, Q = 0.
iq = torch.stack((torch.ones(2048), torch.zeros(2048)), dim=0)
md = SignalMetadata(
    family="comms", class_name="cw", class_taxonomy=("comms", "cw"),
    generator_name="example", device_id="dev", sample_rate_hz=1e6,
    bandwidth_hz=1e3, realized_carrier_hz=0.0, start_sample=0,
    duration_samples=2048, snr_db=float("inf"), extras={},
)
sig = Signal(iq=iq, metadata=md)

rng = torch.Generator().manual_seed(0)
ctx = ChannelContext(
    emitter_meta=md,
    rx_params=ChannelRxParams(center_freq_hz=0.0, bandwidth_hz=1e6,
                              sample_rate_hz=1e6, noise_figure_db=0.0),
    scene_id="scene", sample_idx=0, rng=rng,
)

out = AWGNChannel(snr_db=10.0).apply(sig, ctx)
# out.iq has shape (2, 2048) and dtype torch.float32
assert out.iq.shape == (2, 2048) and out.iq.dtype == torch.float32
assert out.metadata.snr_db == 10.0

The component sits in the channel-propagation layer. It does not model the Friis cascaded noise-figure equation (F_total = F1 + (F2 - 1)/G1 + ...) or antenna-temperature conversion (P_noise = k T_ant B F_rx); receiver-side noise figure is handled by LinearLNANoise in the device-fingerprint module. Hardware impairments, including IQ imbalance, phase noise, power-amplifier nonlinearity, carrier frequency offset (CFO), and quantisation, are separate classes in the transmit-impairment and receiver-frontend modules. Frequency-selective fading (Rayleigh, Rician) and the 3GPP TR 38.901 TDL/CDL profiles (standardised multipath models) are covered by the Sionna-backed channel classes in the same module.

2. What we validated

This validation establishes 5 load-bearing claims. Each is restated and supported by evidence in §3.

  1. SNR contract (§3.1): the output SNR matches the requested value across the validated range.

  2. Per-rail noise variance (§3.2): each I and Q rail independently carries half the total noise power, consistent with the textbook formula.

  3. QPSK BER agreement with Proakis closed form (§3.3): the end-to-end bit error rate for a rectangular-pulse QPSK waveform matches the theoretical closed-form curve at five operating points.

  4. Determinism, metadata, and input safety (§3.4): same-seed calls produce bit-identical output, metadata is updated on every call, and the input tensor is never modified in place.

  5. Operating envelope (§3.5): the component produces finite output across the documented SNR range, handles near-zero signal power via a guard clamp, and accepts a wide range of input lengths without error.

Limits and scope-bounded items appear in §4; full citations are in §5.

3. Evidence per claim

3.1 SNR contract

Claim. For any input signal with mean power above the 1e-12 guard floor, the output SNR (measured as mean(|x|²) / mean(|y x|²)) equals the requested snr_db within ±0.5 dB.

Mathematical basis. The implementation computes:

signal_power = mean(|x[n]|²).clamp_min(1e-12)
noise_power  = signal_power / 10^(snr_db / 10)
sigma        = sqrt(noise_power / 2)

and draws independent zero-mean Gaussian I and Q noise samples with standard deviation sigma. The total complex noise power is 2 sigma² = noise_power = signal_power / SNR_linear, so the realised SNR by construction equals signal_power / noise_power = SNR_linear. Sklar, Digital Communications, 2nd ed., Prentice-Hall, 2001, Ch. 3 gives this as the standard AWGN per-rail decomposition.

Evidence. Six SNR operating points (snr_db {0, 5, 10, 15, 20, 30} dB), N = 200 000 samples each. Measured SNR recovered from 10 log₁₀(signal_power / noise_power). All six points fell within ±0.5 dB.

snr_db (dB)

Tolerance

Result

0

±0.5 dB

Within tolerance

5

±0.5 dB

Within tolerance

10

±0.5 dB

Within tolerance

15

±0.5 dB

Within tolerance

20

±0.5 dB

Within tolerance

30

±0.5 dB

Within tolerance

Test: tests/validation/propagation/awgn/test_experiment_contract.py::test_measured_snr_matches_requested_within_half_db (6 parametrized cases).

Figure 2 shows the measured complex noise power versus SNR across the −10 to +30 dB range against the theoretical line, with the right panel confirming that the relative error stays below 5 % at every point.

Figure 2: complex noise power vs SNR (measured and theory). Left panel: absolute noise power on a log scale, blue line is theory (P_s / SNR), red dashed line is measured (N = 200 000, continuous-wave input). Right panel: relative error (%) between measured and theoretical; the 5 % tolerance line is shown in red. Supports the SNR contract claim (§3.1).

3.2 Per-rail noise variance

Claim. The I and Q rails are independent and each carries half the total complex noise power, equal to signal_power / (2 * SNR_linear).

Mathematical basis. For complex Gaussian noise n[t] = n_I[t] + j n_Q[t] with independent zero-mean Gaussian rails:

E[|n|²] = E[n_I²] + E[n_Q²] = 2 sigma²

Setting 2 sigma² = noise_power = signal_power / SNR_linear gives sigma² = signal_power / (2 * SNR_linear). This is the standard per-rail variance stated in Sklar (2001), Ch. 3, and Oppenheim & Schafer, Discrete-Time Signal Processing, 3rd ed., Pearson, 2010, Ch. 2.

Evidence: complex noise power. CW input (I = 1, Q = 0), N = 1 000 000, SNR = 10 dB. Measured complex noise power matched signal_power / SNR_linear within 5 % relative error.

Test: tests/validation/propagation/awgn/test_experiment_contract.py::test_measured_complex_noise_variance_matches_theoretical.

Evidence: per-rail symmetry. Same CW input, same conditions. Each rail independently measured at sigma² = signal_power / (2 * SNR_linear) within 5 % relative error.

Test: tests/validation/propagation/awgn/test_experiment_contract.py::test_per_rail_noise_variance_is_half_complex_noise_power.

Evidence: Sklar cross-check. SNR = 20 dB, N = 1 000 000. Each rail measured at sigma² = 1.0 / (2 * 100) = 0.005 within 5 % relative error.

Test: tests/validation/propagation/awgn/test_empirical_known_results.py::test_noise_variance_against_proakis_noise_model.

3.3 QPSK BER agreement with Proakis closed form

Claim. For a rectangular-pulse QPSK waveform (quadrature phase-shift keying, a digital modulation where each symbol encodes two bits as one of four phases), the channel produces a measured BER (bit error rate, the fraction of transmitted bits decoded incorrectly) that matches the Proakis & Salehi closed-form curve within ±20 % relative at five Eb/N0 (energy per bit per noise spectral density) operating points.

Reference formula. Proakis & Salehi, Digital Communications, 5th ed., McGraw-Hill, 2008, eq. 8.2-20:

BER_QPSK(Eb/N0) = Q(sqrt(2 Eb/N0_linear)) = erfc(sqrt(Eb/N0_linear)) / 2

where Q(x) = 0.5 erfc(x / sqrt(2)) is the Q-function (tail probability of a standard Gaussian), and erfc is the complementary error function.

SNR-to-Eb/N0 conversion. For 4 samples per symbol and k = 2 bits per symbol:

Eb/N0 [dB] = SNR [dB] + 10 log₁₀(4 / 2) ≈ SNR [dB] + 3.01 dB

Evidence. Five operating points, N = 200 000 symbols per point. All five points fell within ±20 % relative of the Proakis theoretical BER.

Eb/N0 (dB)

Theoretical BER

Tolerance

Result

0

0.159

±20 % relative

Within tolerance

2

0.078

±20 % relative

Within tolerance

4

0.023

±20 % relative

Within tolerance

6

3.8 × 10⁻³

±20 % relative

Within tolerance

8

1.9 × 10⁻⁴

±20 % relative

Within tolerance

At the 8 dB point, the expected error count is approximately 38 with binomial standard deviation 6; the ±20 % tolerance corresponds to roughly 12 standard deviations from zero errors, so the tolerance is not artificially wide.

Test: tests/validation/propagation/awgn/test_empirical_known_results.py::test_qpsk_ber_matches_proakis_closed_form (5 parametrized cases).

Figure 1 shows the measured BER at five operating points plotted against the dense Proakis theoretical curve.

Figure 1: QPSK BER vs Eb/N0. Blue line: Proakis closed form (eq. 8.2-20). Red circles: measured BER (N = 200 000 symbols per point, 4-sample rectangular pulse, matched-filter demodulation). Supports the BER-agreement claim (§3.3).

3.4 Determinism, metadata, and input safety

Claim. (a) Two calls with the same torch.Generator seed produce byte-identical IQ output. (b) Two calls with different seeds produce different noise samples. © output.metadata.snr_db is updated to self.snr_db on every call. (d) The transformation log appended to metadata carries the correct group and transformation values and the snr_db parameter. (e) The input Signal tensor is not mutated in place.

Evidence.

Sub-claim

Test

Result

Same seed → byte-identical output

test_same_seed_produces_byte_identical_output

Passes

Different seeds → different noise

test_different_seeds_produce_different_noise

Passes

metadata.snr_db updated

test_metadata_snr_db_updated_after_apply

Passes

Transformation log appended

test_transformation_log_appended

Passes

Input not mutated

test_input_signal_not_mutated

Passes

All tests in tests/validation/propagation/awgn/test_experiment_contract.py.

3.5 Operating envelope

Claim. The component produces finite IQ output across snr_db [−30, +60] dB, handles near-zero signal power via a guard clamp at 1e-12, and accepts input lengths from N = 1 to N = 10⁶ without error or memory fault.

SNR range. Six operating points (snr_db {−30, −10, 0, 10, 30, 60} dB), N = 1 000 each. All produced finite float32 IQ.

Test: tests/validation/propagation/awgn/test_robustness_envelope.py::test_operating_envelope_snr_range_documented.

Extreme SNR probe. Three extreme points (snr_db {−20, 0, 60} dB), N = 10 000. All completed without error and produced finite IQ.

Test: tests/validation/propagation/awgn/test_robustness_envelope.py::test_extreme_snr_produces_finite_output (3 parametrized cases).

Sub-floor regime. Input amplitude 1e-9 gives signal_power = 1e-18, which is below the clamp_min(1e-12) floor. The clamp sets the effective signal power to 1e-12, so noise power equals 1e-12 / SNR_linear rather than the actual signal power divided by SNR_linear. Output is finite; noise power matched the clamped formula within 5 %.

Test: tests/validation/propagation/awgn/test_robustness_envelope.py::test_tiny_signal_power_uses_clamp_floor.

Zero input. All-zero IQ: the clamp prevents division by zero and noise is still added. Output is finite.

Test: tests/validation/propagation/awgn/test_robustness_envelope.py::test_zero_iq_input_produces_finite_noise.

Dtype handling. float64 input IQ is cast to complex64 internally; output is always float32.

Test: tests/validation/propagation/awgn/test_robustness_envelope.py::test_float64_input_iq_produces_float32_output.

N extremes. N = 1 and N = 10⁶ both complete without error.

Tests: test_single_sample_signal_completes, test_large_n_completes_without_oom.

4. Limits and what’s not validated

Validated scope. The implementation reproduces the textbook AWGN model: per-rail variance, SNR contract, and Proakis QPSK BER all hold to the tolerances in §3 across the operating envelope documented below.

Operating envelope. The validated parameter ranges are:

Parameter

Validated range

Boundary behaviour

snr_db

−30 dB to +60 dB

Finite output across the range; no overflow or NaN

mean(|x|²)

Clamped to 1e-12 floor

Below 1e-12 the SNR contract does not hold; noise is computed from the clamp floor

N (signal length)

1 to 10⁶

No minimum enforced; no OOM at 10⁶

Input dtype

float32 or float64

Cast to complex64 internally; output is always float32

Float32 precision limit. float32 (single-precision floating-point, providing approximately seven significant decimal digits) carries roughly seven significant decimal digits. At SNR = 60 dB the noise standard deviation is about 1e-3 relative to a unit-amplitude signal, leaving four significant digits of headroom. Beyond approximately 90 dB the noise falls below float32 rounding; this regime is not tested.

Friis cascaded noise figure. The class does not implement F_total = F1 + (F2 1)/G1 + ... or antenna-temperature conversion via P_noise = k T_ant B F_rx (where k is Boltzmann’s constant, T_ant is antenna noise temperature, B is bandwidth, and F_rx is receiver noise figure). Receiver-side noise figure is handled by LinearLNANoise in the device-fingerprint module.

Frequency-selective fading. Rayleigh fading (random amplitude envelope following a Rayleigh distribution, caused by multipath propagation), Rician fading (similar but with a dominant line-of-sight component), and the 3GPP TR 38.901 TDL/CDL profiles (standardised multipath channel models for cellular systems) are out of scope and are covered by the Sionna-backed channel classes in the same module.

Coloured noise. All noise samples are independent; band-edge anti-aliasing-filter colouration (where a filter shapes the noise power spectrum so it is no longer flat) is a receiver-frontend concern.

Hardware impairments. IQ imbalance, phase noise, power-amplifier nonlinearity, CFO, and quantisation are separate classes in the transmit-impairment and receiver-frontend modules.

Validated SNR window. Behaviour outside the −30 dB to +60 dB window is not characterised by the current test suite.

5. References

Published works

Citation

Role

Proakis, J. G., and Salehi, M. Digital Communications, 5th ed., McGraw-Hill, 2008. ISBN 978-0-07-295716-7. Ch. 8.2, eq. 8.2-15, 8.2-20.

QPSK BER closed form under AWGN; primary empirical reference for §3.3

Sklar, B. Digital Communications: Fundamentals and Applications, 2nd ed., Prentice-Hall, 2001. ISBN 978-0-13-084788-7. Ch. 3.

AWGN per-rail variance formula (N₀/2); primary citation for per-rail decomposition in §3.1 and §3.2

Oppenheim, A. V., and Schafer, R. W. Discrete-Time Signal Processing, 3rd ed., Pearson, 2010. ISBN 978-0-13-198842-2. Ch. 2.

Discrete-time mean power as Parseval-consistent average; basis for P_s = mean(|x|²)

Hoydis, J., Cammerer, S., et al. “Sionna: An Open-Source Library for Next-Generation Physical Layer Research.” arXiv:2203.11854, 2022. DOI: 10.48550/arXiv.2203.11854.

Cross-implementation reference: sionna.phy.channel.AWGN uses the same scaled-Gaussian draw

Libraries

PyPI distribution

Installed version

Docs URL

Role in validation

torch

2.12.1

https://pytorch.org/docs/stable/

Gaussian sampler (torch.randn), tensor arithmetic, torch.Generator for deterministic RNG

numpy

2.4.6

https://numpy.org/doc/stable/

math.erfc (via scipy) and array operations in test helpers

scipy

1.18.0

https://docs.scipy.org/doc/scipy/

Not directly invoked; math.erfc from Python stdlib is used instead (confirmed equivalent)

matplotlib

3.11.0

https://matplotlib.org/stable/

Figure generation in generate_figures.py