Signal Atlas comms-v1 Phase-2 RF physics validation

Validated with fixes applied, documented limitations, one corrected finding (section 3.7), and known open defects awaiting a user decision (section 5).

Post-migration evidence mapping

This report measured the former SionnaCIRDataset composite at the report’s pinned commit. The current graph authoring surface separates that behavior: declared_power_delay_profile owns the two analytic PDP tables and their RMS facts, pdp_rayleigh_fading owns the seeded complex-Gaussian tap draw, and apply_cir owns unit-transfer CIR application to a separately power-scaled waveform. The composite selector is no longer registered.

The report’s PDP formula, tap-count, and RMS-delay evidence maps to DeclaredPowerDelayProfile; its per-tap Rayleigh statistics map to PDPRayleighFading. Measurements of the old composite’s final IQ, Sionna CIRDataset loader behavior, and process-global RNG containment do not by themselves validate the replacement graph. The replacement has focused typed, determinism, Sionna-application, and stock-graph tests; report-critical comms measurements are rerun against replacement pilots in the later mature-variant regression milestone before any conclusion is carried forward.

1. The component

This validation covers two groups of Phase-2 additions to the core rfgen package, treated as one component because they are the RF-physics surface a single Phase-2 delivery introduced.

Group A (src/rfgen/waveforms/ and src/rfgen/domains/comms/): three Sionna-backed emitters plus a shared bandwidth-sizing helper.

  • NRPuschEmitter (cellular.py): a 5G NR (New Radio) PUSCH (Physical Uplink Shared Channel) transmitter, completed from a Pass-1 stub by delegating all uplink PHY (physical-layer) work to sionna.phy.nr.PUSCHTransmitter/PUSCHConfig.

  • ConformantOFDMEmitter (ofdm_conformant.py): a genuine OFDM (Orthogonal Frequency-Division Multiplexing) resource grid with a real pilot pattern, built on sionna.phy.ofdm.ResourceGrid.

  • FECConstellationEmitter (fec_constellation.py): a single-carrier, FEC-coded QAM emitter supporting four code families (LDPC, Polar, Turbo, convolutional) via Sionna’s sionna.phy.fec encoders.

  • resource_blocks_for_bandwidth (_resource_grid.py, “Amendment 4”): a shared helper mapping a requested occupied bandwidth to a resource-block (one resource block is 12 subcarriers, 3GPP TS 38.211 section 4.4.4.1) count, used by both NRPuschEmitter and ConformantOFDMEmitter’s occupied_bandwidth_hz override, plus the use-case-level sampler constraint (constrain_bandwidth_for_wideband_classes, in use_cases/signal-atlas/comms-v1/rfgen_signal_atlas_comms/joint_sampler/constraints.py) that excludes bandwidth-ladder rungs below each class’s physical minimum.

# NRPuschEmitter usage (Group A). Interface signature; not a standalone program.
signal = NRPuschEmitter().generate(
    class_label="nr_pusch",
    sample_rate=60_000_000.0,
    duration_s=1e-3,
    f_offset_hz=0.0,
    rng=torch.Generator().manual_seed(11),
    params=NRPuschParams(occupied_bandwidth_hz=20_000_000.0),
)
assert signal.metadata.bandwidth_hz == 20_520_000.0  # realized, not requested

Group B (src/rfgen/engine/propagation_sionna.py): three further Sionna-backed statistical channels.

  • RayleighBlockFading: one complex Gaussian gain per call, tiled over the requested time steps (i.i.d. block fading, no delay spread).

  • SionnaFlatFading: a correlated flat-fading channel exercising Sionna’s KroneckerModel/PerColumnModel spatial-correlation machinery, repurposed here as a time-block correlation axis for a single-antenna signal.

  • SionnaCIRDataset: a sionna.phy.channel.CIRDataset-backed channel over one of two declared, generic (non-3GPP) exponential power-delay profiles.

# RayleighBlockFading usage (Group B). Interface signature; not a standalone program.
out_signal = RayleighBlockFading().apply(signal, channel_context)

Every Group A/B class is a simulator-to-simulator component: emitters generate baseband waveforms in isolation, and channels transform them with statistical or Sionna-native physics, never a reconstruction of an over-the-air capture. This validation makes no sim-to-real fidelity or transfer-performance claim about any class in either group.

2. What we validated

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

  1. Library-first construction (§3.1): every class in both groups delegates its RF math to Sionna, and no PHY, FEC, or fading algorithm is reimplemented in rfgen.

  2. NR PUSCH is conformant by delegation at the transmitter (§3.2): real DMRS placement and a real, sized transport block, though a rendered example window is not guaranteed to contain a whole slot.

  3. Conformant OFDM is standards-shaped, explicitly uncoded (§3.3): a real resource grid and pilot pattern at correct 3GPP numerology, with no transport-block encoding, sharing NR PUSCH’s slot-fragment truncation exposure.

  4. Amendment-4 bandwidth-to-resource-block mapping is physically sound, with an MCS-contingent floor (§3.4): realized occupied bandwidth tracks the requested value; the excluded 200 kHz rung sits below each class’s real structural floor, and that floor is arithmetic conditioned on the pipeline’s pinned MCS, not a fixed physical constant.

  5. All four FEC families genuinely encode and decode (§3.5): real bit recovery through a real Sionna decoder, at the code rate the emitter itself reports as achieved.

  6. The three new channels produce statistically correct, distinct fading (§3.6): Rayleigh amplitude, controllable correlation, and profile-consistent delay spread all hold under direct statistical test.

  7. Determinism holds given the same seed, with a corrected fix (§3.7): a hidden RNG leak was found in all three Group A emitters; the first fix and its regression tests were both incomplete, and this pass corrects both, proven under true process isolation.

Limits and scope-bounded items appear in section 4; known open defects awaiting a user decision appear in section 5; full citations are in section 6.

3. Evidence per claim

3.1 Library-first construction

Claim. No class in Group A or Group B reimplements PHY, FEC, or fading math; every one delegates to a real Sionna (or, for FEC pulse shaping, TorchSig) entry point.

Evidence. Reading cellular.py, ofdm_conformant.py, and fec_constellation.py confirms each constructs a real Sionna object (PUSCHTransmitter, ResourceGrid/OFDMModulator, and one of LDPC5GEncoder/Polar5GEncoder/TurboEncoder/ConvEncoder) and calls it directly; fec_constellation.py’s pulse shaping reuses torchsig.signals.builders.constellation’s SRRC taps and polyphase resampler, the same primitives APSKEmitter already uses. _resource_grid.py is 39 lines of arithmetic (round a bandwidth to the nearest resource-block count) with zero backend calls, confirmed by reading the full module. propagation_sionna.py’s three new channels each construct one of sionna.phy.channel.RayleighBlockFading, FlatFadingChannel, or CIRDataset and call it directly; the module’s own correlation matrices come from sionna.phy.channel.exp_corr_mat, KroneckerModel, and PerColumnModel, not a hand-rolled covariance construction.

3.2 NR PUSCH is conformant by delegation at the transmitter

Claim. NRPuschEmitter produces a real 3GPP NR uplink slot at construction time: a DMRS (Demodulation Reference Signal, the pilot symbols a real receiver uses to estimate the channel) at a real, non-empty resource-element pattern, and a genuinely sized, LDPC-encoded transport block, not a plausible-looking approximation. This conformance holds at the transmitter: every PHY decision is made by Sionna’s own NR module, never reimplemented in rfgen. A separate, distinct question is whether one RENDERED example window is guaranteed to contain a whole slot; it is not, for a mechanism explained below.

Evidence. Constructing a PUSCHConfig at n_size_grid=52, subcarrier_spacing=30 kHz, mcs_index=10 and building its PUSCHTransmitter directly confirms pusch_config.dmrs_symbol_indices == [2] (a single-symbol DMRS at the third OFDM symbol of the 14-symbol slot, one of 3GPP TS 38.211’s standard single-symbol mapping-type-A configurations) and that the resulting pilot_pattern.mask carries exactly 624 pilot resource elements at that symbol, one per active subcarrier (fft_size=624, no guard carriers at this allocation): a real, full-band DMRS mapping, not a placeholder. pusch_config.tb_size resolves to 10,760 bits, confirming a genuinely sized transport block (3GPP TS 38.212’s LDPC base-graph selection, encoder rate matching, and scrambling all execute inside PUSCHTransmitter; none is reimplemented in rfgen). This is a standards-conformant construction because it delegates every PHY decision (resource mapping, DMRS placement, transport-block encoding, OFDM modulation) to Sionna’s own NR module, which independently implements 3GPP TS 38.211/38.212 in full.

Mechanism: window duration versus slot duration. NRPuschEmitter.generate resamples Sionna’s native-rate slot output onto the caller’s requested sample_rate/duration_s grid, then crops or zero-pads to exactly the requested sample count (the same crop/pad helper ConformantOFDMEmitter also uses; see §3.3). The number of OFDM symbols actually visible inside one rendered window is duration_s / ofdm_symbol_duration, and duration_s is fixed by the corpus’s window length while a slot’s own duration is fixed by the numerology, so a wider drawn bandwidth (a larger n_rb, hence a wider realized sample_rate = 2 * bandwidth_hz) shrinks how much of one slot fits inside the window, not how much bandwidth fits. Concretely, at the 4096-sample window in force when Phase 2 was measured and mcs=10, subcarrier_spacing_hz=30 kHz: the narrowest bandwidth-ladder rung (1 MHz) delivered about 51 of the slot’s 14 OFDM symbols (more than three whole slots), while the widest rung (20 MHz) delivered only about 2.8 symbols, cutting through the DMRS symbol (index 2) mid-symbol. Re-measuring the identical mechanism at 16,384 samples, now the corpus’s window, shows the DMRS-cut-mid-symbol harm does not reproduce at any rung: the 20 MHz rung delivers about 11 of 14 symbols, which fully covers the DMRS symbol. The more general mechanism still holds at 16,384 samples: the 20 MHz rung still delivers only about 0.79 of one whole slot. Widening the window reduces, but does not eliminate, the exposure: a rendered example is not guaranteed to contain a whole NR resource-grid slot, at either window size measured to date.

3.3 Conformant OFDM is standards-shaped, explicitly uncoded

Claim. ConformantOFDMEmitter builds a real resource grid at correct 3GPP numerology (a numerology fixes the subcarrier spacing and symbol timing a standard defines) with a real pilot pattern, but is explicitly uncoded: no transport-block encoding, scrambling, or rate matching, matching what the module’s own docstring already states.

Evidence. Building a ResourceGrid at the module’s fixed constants (num_ofdm_symbols=14, subcarrier_spacing=15,000 Hz, 3GPP numerology 0, TS 38.211 Table 4.2-1; pilot_pattern="kronecker") with fft_size=512 and guard carriers (206, 206) (100 active subcarriers) confirms a real KroneckerPilotPattern: 200 nonzero pilot resource elements across the two requested pilot symbols (indices 0 and 7), one genuine orthogonal pilot per active subcarrier at each pilot symbol. This is a full-band, block-type pilot arrangement in time (every active subcarrier carries a pilot at the chosen symbols), a standards-plausible design distinct from a specific named 3GPP DMRS comb-pattern; the module’s own docstring already states this precisely with “standards-SHAPED” language that scopes the claim to structure, stopping short of exact protocol conformance. Reading generate() confirms the data path is BinarySource bits mapped directly to QPSK by a bare Mapper("qam", 2), with no encoder, scrambler, or transport-block object constructed anywhere in the method; extras on the returned signal carry no code-rate or transport-block field, so the uncoded claim holds structurally, in the code itself, and not only in the module’s prose (test_conformant_ofdm_places_a_real_pilot_pattern_but_stays_uncoded).

Shared truncation mechanism. ConformantOFDMEmitter builds its time-domain waveform through the same architecture as NRPuschEmitter (§3.2): concatenate as many native-rate Sionna slots as needed, resample onto the requested grid, then crop or zero-pad to the exact requested sample count through the same shared n_samples helper (_waveform_contract.py). Nothing in ConformantOFDMEmitter snaps the rendered length to a whole OFDM symbol or a whole 14-symbol slot, so its pilot-bearing resource grid is exposed to the identical slot-fragment truncation mechanism §3.2 describes for NR PUSCH’s DMRS: a truncated render can cut through a pilot-bearing OFDM symbol exactly as it can cut through NR PUSCH’s DMRS symbol. This is a property of the render path’s shared crop/pad mechanism across both Sionna-slot-based Group A emitters, not an NR-PUSCH-specific caveat.

3.4 Amendment-4 bandwidth-to-resource-block mapping is physically sound, with an MCS-contingent floor

Claim. resource_blocks_for_bandwidth rounds a requested occupied bandwidth to the nearest constructible resource-block count, and the resulting realized bandwidth stays close to the request at every bandwidth-ladder rung the sampler’s cross-axis constraint actually allows to be drawn. The 200 kHz rung is excluded for both wideband classes because it sits below their real structural floor, not as an arbitrary policy choice. For NR PUSCH specifically, that floor is arithmetic conditioned on the pipeline’s pinned MCS index, not a fixed physical constant independent of MCS.

Evidence. Direct calls to both emitters’ occupied_bandwidth_hz override confirm: at 1 MHz, both NRPuschEmitter (30 kHz subcarrier spacing, 360 kHz per resource block) and ConformantOFDMEmitter (15 kHz spacing, 180 kHz per resource block) realize 1.08 MHz (ratio 1.08; both ladder values happen to round to a common multiple of the coarser 360 kHz grid); at 5 MHz, both realize 5.04 MHz (ratio 1.008), by the same coincidence. At 20 MHz the two diverge: ConformantOFDMEmitter (uncoded, no downstream validity search) rounds to the nearest resource-block count directly and realizes 19.98 MHz (ratio 0.999, the specific 20 MHz-drawn, 19.98 MHz-realized case this delivery’s own Phase-2 evidence cites); NRPuschEmitter’s naive rounding (n_rb=56) fails Sionna’s own LDPC base-graph construction check (“Only coderate>1/3 supported for BG1”, confirmed directly from the raised ValueError), so _build_pusch_config_for_bandwidth’s documented ±8-RB search lands on the nearest constructible neighbor (n_rb=57), realizing 20.52 MHz (ratio 1.026). Both emitters’ worst observed overshoot across the three eligible rungs stays at or below 8% (test_nr_pusch_bandwidth_ladder_realizes_close_to_requested, test_conformant_ofdm_bandwidth_ladder_realizes_close_to_requested).

On the exclusion itself: nr_pusch_occupied_bandwidth_bounds_hz at the schema’s default 30 kHz spacing returns a structural minimum of 360,000 Hz (one resource block); conformant_ofdm_occupied_bandwidth_bounds_hz at the schema’s default 36-sample cyclic-prefix length returns a structural minimum of 540,000 Hz, bounded by the cyclic-prefix length, not the raw one-resource- block floor, because Sionna’s own ResourceGrid construction requires cyclic_prefix_length <= fft_size. Both floors exceed the 200 kHz rung and sit below the 1 MHz rung, so the sampler’s cross-axis constraint (constrain_bandwidth_for_wideband_classes) correctly excludes exactly the 200 kHz rung and leaves at least one eligible rung to redraw to (test_200khz_rung_is_genuinely_below_each_wideband_classs_physical_floor). Calling ConformantOFDMEmitter.generate directly at 200 kHz bypassing the sampler’s constraint (a real construction, not a mock) confirms why exclusion is the right call over silently clamping: the emitter cannot refuse a below-floor bandwidth on its own (Sionna forces the effective grid up to the cyclic-prefix floor), so a caller that skipped the sampler-level constraint would silently realize 540 kHz against a 200 kHz request (a 2.7x overshoot) instead of hitting the exclusion; NRPuschEmitter raises a clear EmitterError naming the physical minimum instead (test_nr_pusch_below_physical_minimum_raises_rather_than_silently_clamping). Both minimums are read from the same bounds helpers the emitters themselves expose, confirmed by direct comparison against the sampler module’s own computed constants: no minimum is a hardcoded, independently-drifting number.

The 360 kHz floor is MCS-contingent arithmetic, not a fixed physical constant. nr_pusch_occupied_bandwidth_bounds_hz’s minimum is n_rb_min * 12 * subcarrier_spacing_hz, a pure resource-block-grid computation that takes no MCS argument. Whether n_rb=1 is actually constructible at that floor depends separately on the transport-block code rate clearing Sionna’s LDPC base-graph floor, and that floor does depend on MCS: constructing PUSCHTransmitter at n_rb=1, subcarrier_spacing_hz=30 kHz fails at mcs=0 and mcs=1 (“Unsupported coderate (r<1/5)”) and succeeds at mcs=2 and above, including the pipeline’s pinned default mcs=10. Because the pipeline never varies MCS away from that default, every render that hits n_rb=1 in practice succeeds, and the contingency stays invisible during normal operation. The 360 kHz figure is not a bandwidth-grid-only physical constant independent of coding: it is the floor this dataset’s pinned MCS happens to clear, and a caller running NRPuschEmitter at a lower MCS index would see a materially different structural minimum.

3.5 All four FEC families genuinely encode and decode

Claim. FECConstellationEmitter’s four supported code families each build a real Sionna encoder and, run through the matching real Sionna decoder, recover the transmitted bits exactly at high signal quality, at the code rate the emitter itself reports as achieved.

Evidence. An independent encode→map→AWGN→demap→decode round trip (sionna.phy.channel.AWGN, sionna.phy.mapping.Demapper, and the family’s own real decoder: LDPC5GDecoder, Polar5GDecoder with list-8 SCL (Successive Cancellation List) decoding, TurboDecoder, ViterbiDecoder) at code rate (the fraction of transmitted bits that carry real payload, the rest being FEC redundancy) 1/3 (distinct from the pinned unit test’s 1/2) recovered zero bit errors over 15 trials per family at 20 dB Eb/N0 (energy per bit over noise spectral density, a code-rate-normalized SNR) (test_fec_family_round_trip_recovers_bits_at_a_different_rate_than_the_pinned_unit_test). At 2 dB Eb/N0 the same pipeline (independent of the pinned unit test, run directly during this validation) produced non-zero bit-error rates that differ by code family, consistent with each family’s known relative error-correcting strength at this operating point (LDPC 17.2%, Polar 46.7%, Turbo 12.6%, convolutional 11.7%, all at achieved rate ≈0.5 over 20 trials × 20 blocks each), confirming the zero-error result at 20 dB reflects genuine decoding under noise, not a code so redundant it always succeeds regardless of channel quality.

Reading _build_ldpc_encoder/_build_polar_encoder/_build_turbo_encoder/ _build_conv_encoder and FECConstellationEmitter.generate’s own extras assembly confirms the code rate generate() reports (fec_constellation_code_rate) is computed from the encoder’s real (k, n), not the caller’s requested value. Verified directly: a requested LDPC rate of 1/3 is honored exactly (k=2000, n=4000 in the round-trip test above); a rate requested below a family’s real floor is documented to floor-clamp to the achievable rate, and to report that achieved value instead of silently misreporting the request.

Un-modeled real-world imperfection: Polar5GEncoder’s small-block parity bits. Real 3GPP NR Polar-coded control channels at small transport-block sizes (12<=k<=19 information bits) include 3 additional parity-check bits per 3GPP TS 38.212, used by the receiver’s list decoder for early pruning. Sionna’s Polar5GEncoder, which FECConstellationEmitter’s polar path delegates to entirely, does not implement these bits at this block-length range: constructing Polar5GEncoder(k=12, n=18), a small-k corner inside the emitter’s own documented reachable range, surfaces Sionna’s own runtime warning (“For 12<=k<=19 additional 3 parity-check bits are defined in 38.212. They are currently not implemented by this encoder and, thus, ignored.”). This is a library-inherited limitation, not an rfgen defect, and no fix is owed here (fixing it means patching Sionna itself). A polar-labeled example at a small k is therefore not fully 3GPP-conformant at that corner, disclosed here instead of left for a reader to discover.

3.6 The three new channels produce statistically correct, distinct fading

Claim. RayleighBlockFading’s per-draw gain magnitude follows a Rayleigh distribution; SionnaFlatFading’s empirical inter-block correlation tracks its requested correlation coefficient; SionnaCIRDataset realizes exactly its declared profile’s tap delays. All three are measurably distinct from AWGNChannel and from each other.

Evidence. 1,500 independent RayleighBlockFading draws against a fixed input, fit to a Rayleigh distribution by maximum likelihood, pass a Kolmogorov-Smirnov (a nonparametric goodness-of-fit test comparing an empirical distribution to a reference one) test against that fit (p = 0.517, fixed alpha = 0.01 decided before the numbers were seen; not curated to pass): the fading gain’s magnitude is genuinely Rayleigh- distributed, not merely “non-constant” (test_rayleigh_block_fading_amplitude_matches_rayleigh_distribution).

For SionnaFlatFading at num_fading_blocks=8, the empirical correlation coefficient between the first two blocks over 1,500 draws tracks the requested correlation parameter directly: requested 0.0 → empirical 0.012, requested 0.5 → empirical 0.494, requested 0.9 → empirical 0.899, all within 0.08 absolute tolerance (test_flat_fading_empirical_correlation_matches_requested). This confirms Sionna’s KroneckerModel correlation machinery is genuinely wired to the requested parameter.

Un-modeled real-world imperfection: SionnaFlatFading’s correlation family does not match real Rayleigh-fading time correlation. SionnaFlatFading repurposes exp_corr_mat’s exponential SPATIAL- correlation construction (R[i,j] = rho^|i-j|) as a time-block correlation axis; the measurement above confirms this exponential decay is faithfully realized. Real flat (frequency-flat) Rayleigh fading instead correlates in time according to Doppler spread, per the Jakes/Clarke model: normalized time-autocorrelation R(tau) = J0(2*pi*f_D*tau), a Bessel function that oscillates and periodically goes negative, a qualitatively different function family from a monotonic, always-non-negative exponential decay. SionnaFlatFading’s correlation parameter also carries no unit tie to velocity, carrier frequency, or coherence time, unlike SionnaTDL/ SionnaCDL’s doppler_speed_mps. This is a physically different correlation family, not merely an incomplete one: a dataset consumer cannot map any correlation value here to a real-world speed or coherence time, and examples drawn from sionna_flat_fading teach a fading-block correlation shape that does not occur in real mobile Rayleigh fading.

For SionnaCIRDataset, applying the channel against both declared profiles (custom_pdp_short_office, 6 taps at 20 ns spacing; custom_pdp_long_urban_macro, 8 taps at 200 ns spacing) confirms the realized CIR’s tap count matches the declared profile exactly for both (test_cir_dataset_realized_delays_match_declared_profile_exactly), and the two profiles’ closed-form RMS delay spreads (29.8 ns and 365.1 ns, computed from the declared power-weighted tap positions, Rappaport eq. 5.5/5.6) differ by more than 10x, well past the >=10% distinctness bar already pinned by test_custom_pdp_profiles_declare_at_least_two_entries_with_distinct_rms_delay_spread in tests/unit/test_propagation.py. Distinctness from AWGNChannel and from each other is established by the existing test_module_13_fading_channel_output_power_variance_exceeds_awgn_by_3x (same test file): both RayleighBlockFading and SionnaFlatFading’s output-power variance over 1,000 draws exceeds AWGNChannel’s by more than 3x at a fixed input and SNR, confirming each fading channel imposes buffer-wide power swings AWGN’s per-sample noise does not.

3.7 Determinism holds given the same seed, with a corrected fix

Claim. Every random draw inside a Group A or Group B class traces back to the caller’s own rng; the same seed reproduces bit-identical output, a different seed produces different output, and no class leaks into or reads from any process-global random-number stream.

Evidence. All three Group B channels (RayleighBlockFading, SionnaFlatFading, SionnaCIRDataset) already passed this bar before this validation pass: tests/unit/test_propagation.py’s existing test_module_13_channel_draws_randomness_only_from_ctx_rng and test_module_13_channel_does_not_leak_process_global_rng_state (both re-run and confirmed passing during this validation) pin same-seed reproducibility and zero perturbation of the process-global Torch and random state, using the same torch.random.fork_rng/state-snapshot scoping (_SionnaTorchRngScope, _scoped_python_random_seed) that already fixed two of the three hidden-RNG-axis bugs Phase 2 previously found (Sionna’s per-call global-config seed, and CIRDataset’s stdlib-random shuffle buffer and internal DataLoader’s process-global-Torch-RNG draw).

This validation found a fourth instance of the same bug class in Group A, which this pass fixed. All three Group A emitters set sionna.phy.config.seed = seed (Sionna’s documented reseed hook) without scoping it. A direct probe confirms this assignment alone perturbs Torch’s process-global default generator as a side effect: seeding torch.manual_seed to a fixed value, drawing a reference torch.rand(4), re-seeding to the same fixed value, then merely assigning sionna.phy.config.seed before drawing again produces a different torch.rand(4) result. Each emitter’s own output remained reproducible given its own rng (confirmed before the fix: same-seed calls were byte-identical, and output did not depend on the Torch global generator’s pre-state), but the assignment’s side effect leaked into whatever any other code drew next from that global generator later in the same process, an undeclared second RNG axis of exactly the kind _SionnaTorchRngScope already exists to prevent for the channel backends.

The first fix attempt was incomplete, and its own regression tests were vacuous; both are corrected here. An earlier version of the fix wrapped only the sionna.phy.config.seed = seed write and the call that reads it (e.g. transmitter(num_slots)) in torch.random.fork_rng(devices=[]), and this report originally stated the leak “verified gone.” That claim was false. A direct probe shows the process-global Torch RNG is perturbed the FIRST time sionna.phy is imported in a process: a ONE-TIME import-caching effect (Python caches a module in sys.modules after its first import, so only the very first Sionna touch in a process can trigger it), not something config-object construction itself does (constructing a CarrierConfig/TBConfig/PUSCHConfig/ResourceGrid/FEC encoder a second time in the same process perturbs nothing). All three emitters’ first-fix versions performed a Sionna-facing import (_build_pusch_config’s from sionna.phy.nr import ..., or an equivalent) BEFORE entering the fork_rng scope, leaving that one-time leak unscoped on a cold first call. The three “no leak” regression tests written alongside that fix also FAILED under true process isolation and passed only inside the full test suite, because an earlier test in the same file had already imported sionna.phy and consumed the one-time leak first: test-order pollution masking a real defect, the same pattern this repository already fixed once at commit 69a193a4 (test_dataset_consumer permanently corrupting sys.modules).

The corrected fix widens each emitter’s torch.random.fork_rng scope to begin before the FIRST Sionna-facing statement in that emitter (the import sionna.phy/from sionna.phy... import ... line, whichever comes first) and end after the generation call, so config-object/encoder construction and the sionna.phy.config.seed write and read all sit inside one scope (cellular.py, ofdm_conformant.py, fec_constellation.py). The three regression tests now spawn a fresh subprocess per assertion (sys.executable -c ..., inheriting the parent’s environment unmodified, never a from-scratch mapping) so no earlier test, in this file or any other, can have already imported sionna.phy in that process; each measures torch.get_rng_state() immediately before and after the emitter’s own cold first call in that fresh process. Run against the pre-fix code, all three subprocess-isolated tests fail; run against the corrected fix, all three pass (test_nr_pusch_does_not_leak_into_process_global_torch_rng, test_conformant_ofdm_does_not_leak_into_process_global_torch_rng, test_fec_constellation_does_not_leak_into_process_global_torch_rng, the last parameterized over all four FEC families). All 75 existing emitter unit tests and the full 111-test signal-atlas-comms-v1 use-case suite pass unchanged after the corrected fix.

4. Limits and scope-bounded claims

  • NR PUSCH’s realized occupied bandwidth can overshoot the requested value by up to 8% at coarser bandwidth-ladder rungs. This follows directly from 30 kHz-numerology resource-block granularity (360 kHz per RB) combined with the documented ±8-RB LDPC-constructibility search; it is not a defect in the mapping, but a reader relying on tight bandwidth precision at the 1 MHz rung specifically should budget for it.

  • The Reference contract’s emitter table (docs/reference/api/waveforms.md) has a documentation-completeness gap this validation did not close. FECConstellationEmitter and ConformantOFDMEmitter have no row in the class index, and the page’s “17 built-in selectors” count predates the five Phase-2/prior selectors that bring the real count to 22. This validation fixed the two claims in that table and in docs/glossary.md that actively contradicted the shipped code (NR PUSCH’s “stub” status, and FEC being “not modeled”) because leaving them would have made this report cite a glossary that disagreed with it; adding full new table rows and refreshing the selector count is substantive documentation authoring distinct from RF-physics validation, better suited to a dedicated documentation-completeness pass.

  • A pre-existing mypy finding in cellular.py (_build_pusch_config_for_bandwidth) predates this validation and was left as-is. It is a type-annotation mismatch (a caught exception’s declared type does not include one branch’s real exception type), confirmed present on the unmodified pre-validation commit; it does not affect runtime behavior and is outside this pass’s RF-physics scope.

  • 31 pre-existing SionnaRT (ray-tracing) unit-test failures are unrelated to this validation. They stem from sionna.__version__ not existing on the installed Sionna 2.0.1 distribution (a version-detection assumption in propagation_sionna_rt.py), confirmed present before this validation’s changes and unrelated to any Group A or Group B class; SionnaRT is Phase-1 machinery outside this delivery’s Amendment-4/Group-B scope.

  • This dataset generates a simulator-to-simulator corpus. No class validated here claims fidelity to an over-the-air capture, and this validation makes no sim-to-real transfer or real-receiver-performance claim about any of them.

  • ConformantOFDMEmitter’s pilot pattern is standards-plausible, not a named 3GPP configuration. Its full-band, block-type Kronecker pilots differ from a specific 3GPP DMRS comb pattern; §3.3 states this distinction precisely and does not imply full downlink-reference-signal conformance.

  • The two SionnaCIRDataset power-delay profiles are analytically generated (Rappaport’s exponential PDP model), not transcribed standards-body table values. propagation_sionna.py’s own module comment states this design choice; this validation confirmed the realized channel matches the declared profile exactly, not that the profile itself matches a specific published measurement campaign.

5. Known open defects (awaiting user decision)

Four corpus-definition defects were found after this report’s original validation pass. None is fixed here: each awaits a user design decision. Unlike section 4’s items, which describe accepted, scope-bounded constraints, the four defects below are not accepted; they are open. This section exists so this report is never read as a clean bill of health while they remain so.

  • DEFECT D1: 12 of ConformantOFDMEmitter’s catalog labels collapse into one. The Signal Atlas comms-v1 use case’s render path always overrides ConformantOFDMEmitter’s occupied_bandwidth_hz from the drawn bandwidth-ladder rung for every nr-grid-<N>rb class label, and that override fully determines the rendered resource-grid size, so the rendered waveform stops depending on which of the 12 nr-grid-<N>rb labels was drawn. Rendering nr-grid-1rb, nr-grid-8rb, and nr-grid-20rb at a fixed seed and bandwidth produces bit-for-bit identical IQ, independently reproduced three times (a blind classifier, a physical PAPR/kurtosis cross-check, and a direct live render). Effectively 69, not 80, of the catalog’s classes are distinguishable. This is a use-case wiring defect, not a defect in ConformantOFDMEmitter itself, whose documented override behavior is an intentional, separately correct feature.

  • DEFECT D3: coding_scheme has no causal effect on the rendered signal, and its recorded provenance value can directly contradict what was actually rendered. condition.coding_scheme is drawn, cross-axis-constrained to Sionna-PHY-family labels, and recorded in provenance, but the render path never threads it into any emitter parameter: nr_pusch always renders LDPC-coded regardless of the drawn value, nr_ofdm_conformant always renders uncoded, and fec_constellation labels always render coded. Roughly half of all Sionna-PHY-family draws therefore carry a provenance label that misstates the rendered signal’s actual coding status.

  • DEFECT D4: the reviewed, tested delay-spread augmentation is dead code; production draws from a different, untested distribution. A tested augment_with_scenario function draws TDL/CDL delay spread uniformly over [10 ns, 3000 ns], but the render path never calls it. Production instead draws delay spread log-uniformly over [10 ns, 1000 ns] through a separate, earlier code path. The corpus’s actual delay-spread statistics differ from the reviewed and tested contract by roughly an order of magnitude.

  • DEFECT D5: the Doppler axis is inert on every fading channel; the corpus contains no time-varying fading. condition.doppler_speed_mps is threaded correctly into each fading channel’s Sionna construction call, but every one of sionna_tdl, sionna_cdl, sionna_cir_dataset, rayleigh_block_fading, and sionna_flat_fading is realized as a single time-step snapshot, so Doppler has no time axis to act on and is mathematically inert on all five. Varying doppler_speed_mps from 5 to 200 m/s at a fixed seed produces byte-identical IQ on every fading channel; only awgn_channel is correctly unaffected, since Doppler is not applicable there by construction. No example in the corpus exhibits intra-window channel evolution, though mobility diversity is one of this dataset’s stated differentiators over the earlier reference corpus this dataset goes beyond (see the use case’s own README for that comparison).

use_cases/signal-atlas/comms-v1/tests/test_axis_causality.py (added in this repair pass) pins D3 and D5 as pytest.mark.xfail(strict=True) cases, each naming its defect ID in the reason. A future fix must update that marker: an unexpected pass on a strict=True xfail fails the suite, so the fix cannot silently land without the marker changing too.

6. References

  1. 3GPP, NR; Physical channels and modulation, 3GPP TS 38.211, specification record. Cited for the DMRS mapping-type-A single-symbol configuration and the 15 kHz/30 kHz numerology confirmed in §3.2 and §3.3.

  2. 3GPP, NR; Multiplexing and channel coding, 3GPP TS 38.212, specification record. Cited for the LDPC base-graph selection and transport-block encoding confirmed in §3.2.

  3. 3GPP, NR; Physical layer procedures for data, 3GPP TS 38.214, specification record. Cited by NRPuschParams.mcs’s own docstring for the MCS index table; not independently re-derived in this pass.

  4. T. S. Rappaport, Wireless Communications: Principles and Practice, 2nd ed., Prentice Hall, 2002, ISBN 0-13-042232-0, section 5.4 (power-delay profiles and RMS delay spread, eq. 5.5/5.6). Cited by propagation_sionna.py’s own module comment for the exponential PDP model SionnaCIRDataset’s two declared profiles use; confirmed in §3.6.

  5. NVIDIA, Sionna nr.PUSCHTransmitter, PyPI distribution sionna, installed version 2.0.1, source.

  6. NVIDIA, Sionna fec.ldpc/fec.polar/fec.turbo/fec.conv encoders and decoders, PyPI distribution sionna, installed version 2.0.1, sources: ldpc, polar, turbo, conv.

  7. NVIDIA, Sionna ofdm.ResourceGrid/KroneckerPilotPattern, PyPI distribution sionna, installed version 2.0.1, source.

  8. NVIDIA, Sionna channel.RayleighBlockFading, channel.FlatFadingChannel, channel.CIRDataset, channel.exp_corr_mat, PyPI distribution sionna, installed version 2.0.1, sources: RayleighBlockFading, FlatFadingChannel, CIRDataset.

  9. TorchSig contributors, torchsig.signals.builders.constellation SRRC taps and polyphase resampler, PyPI distribution torchsig, installed version 2.1.1, PyPI project page.

  10. SciPy developers, scipy.stats.rayleigh and scipy.stats.kstest, PyPI distribution scipy, installed version 1.18.0, used for this validation’s Rayleigh-fit and goodness-of-fit tests in §3.6, rayleigh and kstest documentation.

  11. PyTorch contributors, torch.random.fork_rng and torch.Generator, PyPI distribution torch, installed version 2.13.0, randomness documentation and torch.Generator documentation. Cited for the RNG-scoping fix applied in §3.7.