Validation: rx_frontend/phase_noise_iq

Validated with documented limitations.


1. The component

rx_frontend/phase_noise_iq contains three independent hardware-impairment surfaces for the receiver side of a software-defined radio (SDR: a radio whose signal processing is done in software after digitization):

  • LeesonRXPhaseNoise: multiplies the downconverted complex baseband by exp(j * phi[n]), where phi[n] is a random phase trajectory whose power spectrum (how signal energy is distributed across frequency) follows the three-region Leeson model (a standard oscillator noise model).

  • TorchSigRXIQImbalance: applies a static differential gain and phase mismatch between the I and Q channels (the two 90-degree-separated components of a complex baseband signal) of a quadrature demodulator.

  • TorchSigImpairments: an opt-in compatibility adapter that invokes explicitly configured public TorchSig 2.1.1 transforms on a single-receiver IQ buffer. Its level value probes the public bundled-constructor signature only and is nonsemantic.

Class signatures

class LeesonRXPhaseNoise(BaseRXPhaseNoise):
    def __init__(
        self,
        *,
        psd_floor_dbc_hz: float = -100.0,
        fc_hz: float = 1e4,
        flicker_corner_hz: float = 1e3,
    ) -> None: ...
    def apply(self, signal: Signal, ctx: ChannelContext) -> Signal: ...

class TorchSigRXIQImbalance(BaseRXIQImbalance):
    def __init__(
        self,
        *,
        amplitude_imbalance_db: float = 0.0,
        phase_imbalance_rad: float = 0.0,
    ) -> None: ...
    def apply(self, signal: Signal, ctx: ChannelContext) -> Signal: ...

class TorchSigImpairments(BaseChannel):
    def __init__(self, config: TorchSigImpairmentsConfig) -> None: ...
    def apply(self, signal: Signal, ctx: ChannelContext) -> Signal: ...

Parameter table

Parameter

Type

Units

Default

Purpose

psd_floor_dbc_hz

float

dBc/Hz

-100.0

Far-from-carrier phase-noise floor; dBc/Hz means decibels below the carrier per unit bandwidth

fc_hz

float

Hz

1e4

Leeson resonator corner: offset frequency below which noise rises as 1/f²

flicker_corner_hz

float

Hz

1e3

Flicker (1/f) corner: offset frequency below which noise rises as 1/f³

amplitude_imbalance_db

float

dB

0.0

Gain difference between I and Q ADC channels

phase_imbalance_rad

float

rad

0.0

Phase difference between I and Q demodulator paths

Worked example

import torch
from rfgen.core_types import Signal, SignalMetadata
from rfgen.channels.protocols import ChannelContext, ChannelRxParams
from rfgen.rx_frontend import LeesonRXPhaseNoise, TorchSigRXIQImbalance

# Build a 1024-sample unit-amplitude signal
n = 1024
iq = torch.stack([torch.ones(n), torch.zeros(n)])
md = SignalMetadata(
    family="comms", class_name="cw", class_taxonomy=("comms", "cw"),
    generator_name="demo", device_id="dev0",
    sample_rate_hz=1e6, bandwidth_hz=1e3, realized_carrier_hz=2.4e9,
    start_sample=0, duration_samples=n, snr_db=float("inf"), extras={},
)
sig = Signal(iq=iq, metadata=md)
rng = torch.Generator(); rng.manual_seed(42)
ctx = ChannelContext(
    emitter_meta=md,
    rx_params=ChannelRxParams(center_freq_hz=2.4e9, bandwidth_hz=1e6,
                               sample_rate_hz=1e6, noise_figure_db=6.0),
    scene_id="demo", sample_idx=0, rng=rng,
)

# Apply phase noise then IQ imbalance
pn_out = LeesonRXPhaseNoise(psd_floor_dbc_hz=-110.0, fc_hz=1e4).apply(sig, ctx)
# pn_out.iq.shape == (2, 1024), dtype float32

rng2 = torch.Generator(); rng2.manual_seed(0)
ctx2 = ChannelContext(emitter_meta=md,
    rx_params=ChannelRxParams(center_freq_hz=2.4e9, bandwidth_hz=1e6,
                               sample_rate_hz=1e6, noise_figure_db=6.0),
    scene_id="demo", sample_idx=0, rng=rng2)
iq_out = TorchSigRXIQImbalance(amplitude_imbalance_db=1.0,
                                 phase_imbalance_rad=0.05).apply(sig, ctx2)
# iq_out.iq.shape == (2, 1024), dtype float32

Taxonomy and scope

LeesonRXPhaseNoise sits in Group.RX_HARDWARE and models a stationary, spectrally-shaped phase perturbation from a local oscillator (LO: the reference frequency source used to downconvert the received signal to baseband). It reuses rfgen._leeson.synthesize_leeson_phase_noise, the same synthesizer used by LeesonTXPhaseNoise, so TX-side and RX-side phase noise are bit-identical at matched seed and parameters.

TorchSigRXIQImbalance models static, per-call gain and phase mismatch between the I and Q paths of a quadrature demodulator. The transform is linear and deterministic given (amplitude_imbalance_db, phase_imbalance_rad).

TorchSigImpairments requires an explicit nonempty TorchSigImpairmentsConfig and exactly TorchSig 2.1.1. It constructs Impairments(level, seed) only as a constructor-compatibility probe, then executes the configured public transform classes AdditiveNoise, TimeVaryingNoise, CarrierPhaseNoise, IQImbalance, Quantize, and RandomDropSamples in the documented canonical order. It preserves tuple-versus-list constructor semantics in validated parameters, records pre- and post-IQ SHA-256 values and the unexecuted probe in the transformation log, and has no rfgen substitute DSP path. Its NumPy bridge accepts CPU tensors only and fails closed before any implicit device transfer.


2. What we validated

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

  1. Leeson PSD shape (§3.1): the synthesised phase trajectory matches the analytical three-region Leeson form within Welch single-realisation tolerance.

  2. Band-integrated variance (§3.2): integrated phase-noise power over a measurement band agrees with the analytical band integral.

  3. Three asymptotic regions (§3.3): the Leeson PSD reproduces its documented floor, resonator, and flicker slope regions analytically.

  4. TX-RX shared synthesizer (§3.4): LeesonTXPhaseNoise and LeesonRXPhaseNoise produce bit-identical output at matched seed and parameters.

  5. IQ imbalance closed form (§3.5): TorchSigRXIQImbalance matches the Razavi differential model to float32 precision.

  6. Pinned TorchSig adapter contract (§3.6): TorchSigImpairments matches direct TorchSig 2.1.1 public-transform application, rejects unavailable or unsupported configurations, and records its compatibility probe without claiming that the bundled chain ran.

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


3. Evidence per claim

3.1 Leeson PSD shape

Claim. The Welch-estimated PSD (power spectral density: how signal energy is distributed across frequency) of the synthesised phase trajectory phi[n] matches the analytical Leeson SSB (single-sideband: measuring noise on one side of the carrier frequency) PSD L(f) within 3 dB at mid-band offsets.

Formula. The analytical Leeson three-region PSD is:

L(Δf) = L_0 + 10·log10(1 + (fc/Δf)²) + 10·log10(1 + (f_flicker/Δf))   [dBc/Hz]

where L_0 is the floor, fc is the resonator corner, and f_flicker is the flicker (1/f) corner. The synthesizer generates phi[n] in the frequency domain by scaling complex Gaussian noise by sqrt(var_per_bin), where var_per_bin = 10^(L/10) · df / 2 (the /2 factor converts the SSB PSD to a two-sided density). Hermitian symmetry ensures a real-valued time-domain output.

Citation. Leeson, D. B. (1966), DOI 10.1109/PROC.1966.4682. Rohde, U. L. (1997), ISBN 978-0-471-52019-8, ch. 5.

Measurement. Four parameter cells (2 floors × 2 resonator corners: psd_floor ∈ {-90, -110} dBc/Hz, fc ∈ {1 kHz, 10 kHz}) each produce maximum mid-band deviation < 0.5 dB against the analytical L(f). The comparison band runs from 10 × flicker corner (10 kHz) to fs/8 (125 kHz) to avoid DC and Nyquist bias. The 3 dB tolerance is the standard Welch single-realisation bound (Welch 1967, DOI 10.1109/TAU.1967.1161901) with 8192-sample Hann windows (a smooth window that reduces spectral leakage) at 50% overlap.

Test. tests/validation/rx_frontend/phase_noise_iq/test_mathematical_fidelity.py::test_leeson_realised_psd_matches_analytical_within_3db (4 parametrised cells).

Figure 1 shows the Welch-estimated PSD overlaid on the analytical Leeson curve for one representative parameter set, confirming the close agreement across the mid-band region.

Figure 1: Welch PSD overlay vs analytical Leeson L(f). Realised (blue) and analytical (dashed black) agree to within 0.5 dB mid-band.

3.2 Band-integrated variance

Claim. The variance of phi[n] integrated over the measurement band (5 kHz to fs/4) agrees with the analytical integral L(f) df over the same band within 15%.

Formula. The analytical variance is ∫_{f_lo}^{f_hi} 10^(L(f)/10) df, computed via the trapezoidal rule on a dense log-spaced frequency grid. The empirical variance is the Welch-estimated PSD integrated over the same band. Both are one-sided, so no factor-of-2 correction is required.

Citation. Welch (1967), DOI 10.1109/TAU.1967.1161901.

Measurement. Two floor values (-100, -120 dBc/Hz), each averaged over 8 independent realisations, produce relative errors of 2–5% against the analytical integral. The 15% tolerance is the standard single-realisation Welch bound, tightened by the 8-realisation average.

Test. test_mathematical_fidelity.py::test_leeson_integrated_variance_matches_analytical_band_integral (2 parametrised cells × 8 realisations each).

3.3 Three asymptotic regions

Claim. The analytical Leeson PSD produces the documented asymptotic slopes: flat floor far from the carrier, -20 dB/decade (resonator-only region between f_flicker and fc), and -30 dB/decade (combined flicker-plus-resonator region below f_flicker).

Citation. Leeson (1966), DOI 10.1109/PROC.1966.4682.

Measurement. Sampling L(f) at 100 Hz (below both corners), 30 kHz (mid-resonator region), and 300 kHz (flat floor): the far-region value is within 0.5 dB of L_0, and the near-carrier excess is in the expected 40–60 dB range above the floor.

Test. test_mathematical_fidelity.py::test_leeson_psd_three_regions_have_correct_slopes.

Figure 2 shows the analytical Leeson PSD for three parameter sets, annotating the three slope regions.

Figure 2: Analytical Leeson SSB PSD for three (fc, flicker_corner) combinations. All three show the flat floor, -20 dB/decade resonator region, and steeper combined region below the flicker corner. Colours distinguish the three parameter sets (blue: fc=1e3 Hz, flicker=1e2 Hz; orange: fc=1e4 Hz, flicker=1e3 Hz; green: fc=1e5 Hz, flicker=1e4 Hz).

3.4 TX-RX shared synthesizer

Claim. LeesonTXPhaseNoise and LeesonRXPhaseNoise call the same rfgen._leeson.synthesize_leeson_phase_noise synthesizer. At matched parameters and matched torch.Generator seed, they produce bit-identical output tensors.

Measurement. 36 parameter cells (4 seeds × 3 floors × 3 resonator corners) all pass torch.equal on the output IQ tensors. Seed determinism verified: same seed produces the same output; different seed produces a different output.

Test. test_cross_implementation_tx_rx.py::test_leeson_tx_and_rx_produce_identical_iq_at_matched_params (36 cells) and test_leeson_rx_phase_noise_seed_determinism.

Figure 3 shows the TX and RX phase trajectories at one representative seed, with the difference trace confirming zero deviation.

Figure 3: TX-side and RX-side phase trajectories at matched seed 42 and parameters. Upper panel shows both traces (blue: TX, orange dashed: RX). Lower panel shows the element-wise difference, which is identically zero, confirming shared synthesizer.

3.5 IQ imbalance closed form

Claim. TorchSigRXIQImbalance implements the Razavi differential I/Q-imbalance model exactly:

I' = I · (1 + a/2)
Q' = I · sin(φ) + Q · cos(φ) · (1 - a/2),    a = 10^(amp_db/20) - 1

where φ = phase_imbalance_rad. Output matches the algebraic closed form within atol=rtol=1e-6 (float32-tight tolerance).

Citation. Razavi, B. (2011), RF Microelectronics, 2nd ed., Prentice Hall, §4.2.4 and §7.4. ISBN 978-0-13-713473-1.

Measurement. Five (amp_db, phase_rad) cells covering identity (0, 0), mild (0.5, 0.01), moderate (1.0, 0.05), severe (2.0, 0.1), and negative-amplitude (-1.0, -0.05) all pass torch.allclose with atol=rtol=1e-6. Identity cell additionally confirms the output equals the input bit-for-bit.

Test. test_iq_imbalance_and_stub.py::test_rx_iq_imbalance_matches_differential_closed_form (5 cells) and test_rx_iq_imbalance_zero_imbalance_is_identity_on_signal.

Figure 4 shows the QPSK (quadrature phase-shift keying: a 4-symbol digital modulation scheme) constellation before and after applying the differential IQ-imbalance model, confirming the textbook asymmetric skew.

Figure 4: QPSK constellation (left) before and (right) after TorchSigRXIQImbalance with amplitude=2 dB and phase=0.15 rad. The right panel shows the characteristic differential skew: I axis scaled up, Q axis compressed and tilted, consistent with the Razavi closed form.

3.6 Pinned TorchSig adapter contract

Claim. Each admitted public transform produces a finite complex64 result with the same shape as direct TorchSig 2.1.1 application at the CPU IQ bridge, with max_abs <= 1e-6. Construction without TorchSig raises BackendUnavailableError; omitted configuration raises TypeError; a package version other than 2.1.1 or an unadmitted transform raises UnsupportedCapabilityError; malformed public-transform parameters raise ValueError; non-CPU input raises ChannelError before any implicit device transfer.

Measurement. Direct-oracle tests cover all six admitted transform classes with non-no-op parameters, verify the transformation-log version, ordered class parameters, seed, pre/post SHA-256 values, and impairments_probe.executed is false, and exercise the installed rfgen.channels entry point through a built wheel.

Test. tests/unit/test_layer26_torchsig_compat.py::test_every_admitted_transform_matches_direct_torchsig_211_non_noop_oracle, test_config_rejects_unknown_or_out_of_order_transform_contracts, and test_torchsig_impairments_entry_point_constructs_configured_adapter_and_applies.


4. Limits and what’s not validated

Operating envelope. The following input ranges were probed and produce finite, well-formed output:

Surface

Parameter

Validated range

Observed behaviour

Phase noise

n_samples

2 to 2^18

Finite real output at every length

Phase noise

psd_floor_dbc_hz

-200 to 0 dBc/Hz

Finite output across full range

Phase noise

fc_hz

1 kHz to 100 kHz

Finite output

Phase noise

f0_hz (metadata)

1 Hz to 1 THz

Carrier-agnostic (metadata only)

IQ imbalance

amplitude_imbalance_db

-20 to +20 dB

Finite output; zero input stays zero

IQ imbalance

phase_imbalance_rad

-π/4 to +π/4 rad

Finite output

Bad inputs are rejected with informative ValueErrors: n_samples <= 0 and sample_rate_hz <= 0 both raise at the synthesizer entry point.

Not validated.

  • Signals longer than 2^18 samples are not exercised. Per-window Welch statistics are not expected to change, but the asymptotic slope-region match for very long records is not directly verified.

  • Non-stationary phase noise (time-varying floor, per-burst phase reset, oscillator warm-up transients) is not modelled. The Leeson synthesizer is statistically stationary by construction.

  • Time-varying IQ imbalance (gain or phase drift over the record length) is not modelled. The transform is applied once per apply() call with constant parameters.

  • The upstream bundled Impairments(level, seed) chain is not executed. TorchSig 2.1.1 does not expose selection of the explicit transform list through that bundle, so rfgen uses it only as a public-constructor compatibility probe. The configured public classes, not the bundle, define the executed DSP path.

  • The Leeson model’s f0_hz parameter is metadata-only and is not used in the PSD formula. The carrier frequency does not affect the phase-noise shape in this implementation, which is consistent with the baseband representation used throughout rfgen but departs from formulations that include the oscillator’s loaded Q-factor term.

Deferred items.

  • A multi-window Welch cross-check (tightening the slope-region asymptotic bounds) is not required for the documented contract and is deferred.

  • Admission of a later TorchSig minor is deferred until a new compatibility row verifies its public constructors and direct-oracle behavior.


5. References

Published works

Reference

DOI / ISBN

Leeson, D. B. (1966). “A Simple Model of Feedback Oscillator Noise Spectrum.” Proceedings of the IEEE 54(2):329–330.

DOI: 10.1109/PROC.1966.4682

Rohde, U. L. (1997). Microwave and Wireless Synthesizers: Theory and Design. Wiley, ch. 5.

ISBN: 978-0-471-52019-8

Razavi, B. (2011). RF Microelectronics, 2nd ed. Prentice Hall, §4.2.4 and §7.4.

ISBN: 978-0-13-713473-1

Welch, P. D. (1967). “The Use of Fast Fourier Transform for the Estimation of Power Spectra.” IEEE Transactions on Audio and Electroacoustics 15(2):70–73.

DOI: 10.1109/TAU.1967.1161901

Libraries

PyPI distribution

Installed version

Documentation URL

Role in this validation

torch

2.12.1

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

IQ tensor arithmetic, torch.fft.irfft for phase-noise synthesis, torch.equal for bit-identity checks

scipy

1.18.0

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

scipy.signal.welch for Welch PSD estimation in the mathematical-fidelity and band-variance tests

numpy

2.4.6

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

Array construction, np.trapezoid for band-integral computation, float64 precision in test helpers

matplotlib

3.11.0

https://matplotlib.org/stable/

Figure generation (all four PNGs in the figures directory)