Receiver thermal noise, converter stages, and automatic gain control

Validated with documented limitations.

1. Current component boundary

The public receiver path is composed from separate graph nodes:

  • receiver_thermal_noise accepts a one-dimensional complex-voltage signal and the facts emitted by the same checked receiver_input_boundary. Those facts carry receiver identity, receiver-input measurement plane, effective noise bandwidth (ENBW), noise figure, impedance, link identity, and sample grid.

  • converter_scalingconverter_roundingconverter_clippingconverter_reconstruction makes voltage scale, integer rounding, component code rails, and voltage reconstruction independently visible.

  • automatic_gain_control is a causal gain loop. It publishes the gain at every sample and does not limit the waveform. A separate receiver_voltage_limiter(rail_v=...) provides an analog voltage rail.

The graph ordering contract places receiver equivalent-input noise before receiver analog processing, conversion, and receiver digital processing. See the node API for the binding contract.

2. Thermal-noise quantity and units

For reference temperature T, ENBW B, linear noise figure F = 10^(noise_figure_db / 10), and receiver impedance R, the node computes

P_noise_W       = max(k_B T B F, minimum_noise_power_w)
variance_V2     = P_noise_W R
variance_I_V2   = variance_V2 / 2
variance_Q_V2   = variance_V2 / 2
E[|I + jQ|^2]   = variance_V2

The watts and volts-squared outputs are therefore equal only when R = 1 Ω. The implementation draws independent real and imaginary standard-normal Torch tensors, scales each rail by sqrt(variance_V2 / 2), and publishes both noise_power_w and noise_variance_v2.

The following current tests are the evidence:

  • tests/unit/nodes/test_receiver_measurement_noise_contract.py::test_thermal_noise_facts_and_variance_follow_ktbfr uses B = 1 MHz, NF = 6 dB, and R = 50 Ω; it checks noise_power_w = k_B T B F, noise_variance_v2 = noise_power_w × 50, and the exact facts used.

  • tests/unit/nodes/test_thermal_noise_transform.py::test_thermal_noise_reports_ktbf_power_and_complex64 checks the same watts-to-volts-squared conversion and complex64 output at B = 20 MHz and R = 50 Ω.

  • tests/unit/nodes/test_noise_plane_port.py::test_physical_thermal_noise_closes_against_receiver_ktbf checks that the published noise plane is exactly the realization added to the receiver-input signal and that its empirical complex variance agrees with the published k T B F R variance within the test’s finite-sample tolerance.

  • tests/unit/nodes/test_thermal_noise_transform.py::test_thermal_noise_reports_numerical_floor checks the explicit floor and its noise_floor_applied evidence.

ENBW is not silently repaired. ReceiverMeasurementParams requires a finite, strictly positive effective_noise_bandwidth_hz; zero and non-finite values are schema errors. Nominal receiver bandwidth remains a separate fact. The thermal node also refuses rank-zero or multi-receiver tensors because the current contract supplies evidence for exactly one receiver.

3. Seeded realization and custody

Noise draws use the supplied torch.Generator. Current deterministic and custody evidence includes:

  • tests/unit/nodes/test_awgn_transform.py::test_awgn_node_keyed_generator_is_exactly_deterministic checks equal output for equal seeds and unequal output for a different seed.

  • tests/unit/nodes/test_noise_plane_port.py::test_published_plane_is_the_realization_that_was_added checks signal_out - signal_in against the published noise tensor.

  • tests/unit/nodes/test_noise_plane_port.py::test_a_second_draw_would_be_caught_by_that_bound demonstrates that substituting another seeded realization fails that equality oracle.

  • tests/unit/nodes/test_receiver_input_boundary.py::test_endpoint_conditioning_measurement_boundary_and_noise_publish_to_real_sds checks persistence of receiver, link, grid, and noise evidence.

This section makes no distribution-fit claim beyond the explicit empirical variance oracle above.

4. Converter behavior

The converter rounds I and Q independently to ties-to-even integer codes, clips each component to its code rail, then reconstructs volts using the realized scale. This is component clipping, not radial magnitude limiting. For b = enob_bits, the positive code rail is 2^(b-1) - 1 and fixed-scale volts per code are full_scale_v / (2^(b-1) - 1). When full_scale_v is None, scaling is synthetic per leading row and uses the largest absolute I or Q component in that row.

Current evidence is in tests/unit/nodes/test_converter_stages.py:

  • test_converter_stages_use_row_scale_ties_to_even_and_component_rails checks the complete staged chain against the shared quantization kernel.

  • test_converter_autorange_is_per_row_and_signal_is_unchanged and test_converter_autorange_preserves_every_leading_row_axis check row-local autoranging without altering the scaling-stage signal.

  • test_component_clipping_is_not_radial_limiting distinguishes component rails from a magnitude limiter.

  • test_converter_chain_preserves_exact_waveform_value_type checks exact type and qualifier custody through reconstruction.

  • The mixed-scale and mixed-source-custody negatives in that file refuse evidence assembled from different converter instances.

No SQNR sweep or IEEE-conformance tolerance is claimed here. The retained PNGs under noise_quantize_agc/figures/ were generated for a retired receiver-stage implementation and are historical artifacts, not evidence for the current split converter nodes.

5. Automatic-gain-control behavior

For each leading row and each sample, the implementation uses Torch tensors in a Python loop over the final time axis:

  1. compute post-gain magnitude g |x|;

  2. select tau_attack above the positive voltage target and tau_decay otherwise;

  3. update gain by the causal single-pole step;

  4. clamp gain to [0, max_gain]; and

  5. multiply the current complex sample by the updated gain.

There is no NumPy processing in the implementation and no torchaudio filter. No runtime-per-sample number is asserted because no current benchmark is part of this validation.

Current evidence is:

  • tests/unit/nodes/test_agc_transform.py::test_agc_converges_to_target_and_exposes_gain_trajectory checks convergence of a constant-envelope input and the published gain.

  • tests/unit/nodes/test_agc_transform.py::test_agc_composed_with_a_limiter_clamps_and_keeps_receivers_independent checks independent receiver rows and proves that limiting belongs to the separate receiver limiter.

  • tests/unit/nodes/test_receiver_measurement_noise_contract.py::test_agc_target_is_voltage_and_non_voltage_signals_are_refused checks that the target is volts and dimensionless IQ is rejected.

  • tests/unit/nodes/test_signal_chain_ordering.py::test_agc_before_equivalent_input_noise_is_refused checks the receiver-stage ordering constraint.

On a strictly zero sample, the denominator guard substitutes one only for the update calculation; the sample remains zero and gain can rise only as far as max_gain. tests/unit/nodes/test_link_budget_defaults.py::test_the_shipped_agc_default_still_bounds_a_signal_free_interval and test_agc_rejects_an_initial_gain_above_its_rail cover that bounded state.

6. Limits

  • The thermal node represents one equivalent receiver noise figure. It does not model a Friis cascade; authors must supply an already-reduced equivalent figure in receiver measurement evidence.

  • Thermal noise currently supports one receiver [time] tensor. Per-receiver evidence for a higher-rank tensor is intentionally not inferred.

  • Converter autoranging is synthetic normalization, not evidence of a calibrated hardware full scale. Use explicit full_scale_v for a declared voltage rail.

  • The AGC is correctness-tested but not performance-benchmarked here.

  • The historical figures are not used to support current numerical claims and should not be regenerated as if they exercised the split public nodes.

7. References

  • J. B. Johnson, “Thermal Agitation of Electricity in Conductors,” Physical Review 32 (1928), 97–109, DOI: 10.1103/PhysRev.32.97.

  • H. Nyquist, “Thermal Agitation of Electric Charge in Conductors,” Physical Review 32 (1928), 110–113, DOI: 10.1103/PhysRev.32.110.

  • CODATA 2018 fundamental constants: k_B = 1.380649 × 10⁻²³ J/K exactly.

  • D. M. Pozar, Microwave Engineering, 4th ed., §10.3, for equivalent receiver noise figure and Friis cascades.