Scientific validation: TorchSig AM emitter¶
Validated with documented limitations.
1. The component¶
TorchSigAMEmitter is a clean IQ (in-phase / quadrature: the two orthogonal components of a complex-valued baseband signal) generator for four standard amplitude-modulation (AM: a family of analog modulations where a carrier wave’s amplitude is varied proportionally to a message signal) modes. It wraps TorchSig’s am_modulator builder and produces deterministic, noise-free waveforms for training and evaluation of RF (radio-frequency: the electromagnetic spectrum used for wireless communication) foundation models.
Class signature.
class TorchSigAMEmitter(BaseEmitter):
family: ClassVar[EmitterFamily] = EmitterFamily.COMMS
supported_classes: ClassVar[tuple[str, ...]] = (
"am-dsb-fc", "am-dsb-sc", "am-ssb-usb", "am-ssb-lsb"
)
def __init__(self) -> None: ... # raises BackendUnavailableError if torchsig not installed
def generate(
self,
*,
class_label: str, # one of supported_classes
sample_rate: float, # Hz
duration_s: float, # seconds
f_offset_hz: float, # baseband frequency shift, Hz
rng: torch.Generator,
device_id: str | None = None,
params: BaseModel | None = None, # TorchSigAMParams or None
) -> Signal: ...
Parameter table.
Name |
Type |
Units |
Default |
Purpose |
|---|---|---|---|---|
|
|
- |
required |
One of the four AM class labels (see below) |
|
|
Hz |
required |
Complex-baseband sample rate; Nyquist guard enforced |
|
|
s |
required |
Output duration; N = round(sample_rate * duration_s) |
|
|
Hz |
required |
Baseband frequency shift applied after modulation; must be finite |
|
|
- |
required |
Seeds the numpy RNG forwarded to TorchSig’s builder |
|
|
Hz |
10 000 |
3 dB occupied bandwidth; must satisfy bandwidth_hz + 2*|f_offset_hz| < sample_rate |
Worked example.
import torch
from rfgen.emitters.torchsig_am import TorchSigAMEmitter, TorchSigAMParams
em = TorchSigAMEmitter()
rng = torch.Generator().manual_seed(42)
sig = em.generate(
class_label="am-dsb-fc",
sample_rate=200_000, # 200 kHz
duration_s=0.01, # 10 ms -> 2 000 samples
f_offset_hz=0.0,
rng=rng,
params=TorchSigAMParams(bandwidth_hz=10_000), # 10 kHz
)
print(sig.iq.shape) # torch.Size([2, 2000])
print(sig.iq.dtype) # torch.float32
print(sig.metadata.class_taxonomy) # ('comms', 'am', 'am-dsb-fc')
Class-label to TorchSig mode mapping.
Class label |
TorchSig |
Common name |
|---|---|---|
|
|
Double-sideband (DSB: two symmetric spectral copies of the message around a carrier), full carrier (broadcast AM) |
|
|
Double-sideband, suppressed carrier |
|
|
Single-sideband (SSB: only one of the two DSB spectral copies), upper sideband |
|
|
Single-sideband, lower sideband |
Taxonomy and scope. The emitter occupies the EmitterFamily.COMMS sub-family ("comms", "am", <label>) in the rfgen taxonomy. Excluded from scope: vestigial-sideband (VSB), independent-sideband (ISB), compatible-quadrature AM (C-QUAM), real audio sources, pre-emphasis, envelope companding, and caller control over the DSB-FC modulation index. All channel impairments (phase noise, IQ imbalance, PA (power amplifier: the transmitter stage that amplifies signal power before the antenna) distortion, propagation) are introduced downstream in the rfgen pipeline.
2. What we validated¶
This validation establishes 8 load-bearing claims. Each is restated and supported by evidence in §3.
Output shape and dtype (§3.1): all four class labels produce a
(2, N)float32 IQ tensor with the correct sample count.Zero-mean IQ after DC-subtract (§3.2): the wrapper’s DC-subtraction makes the output satisfy the rfgen zero-mean contract for all four modes, including DSB-FC where the raw TorchSig output carries a substantial carrier offset.
Determinism (§3.3): the same generator seed yields bit-identical output; a different seed yields different output.
Class-label to TorchSig mode mapping (§3.4): rfgen labels route to the correct TorchSig
am_modeargument.DSB real-valued baseband; SSB complex with correct spectral peak side (§3.5): structural property confirming the modulation geometry for all four modes.
Occupied bandwidth tracks
bandwidth_hz(§3.6): the OBW (occupied bandwidth: the narrowest frequency interval containing most of the signal power) stays within verified tolerances of the requested value across DSB and SSB modes.Error paths surface
EmitterErrorwith self-diagnostic messages (§3.7): unsupported labels, Nyquist violations, and pathological inputs all raise typed errors.Safe operating envelope across a parameter grid (§3.8): no NaN, Inf, or zero-power output inside the documented envelope.
Limits and scope-bounded items appear in §4; full citations are in §5.
3. Evidence per claim¶
3.1 Output shape and dtype¶
Claim. For every class label and any valid (sample_rate, duration_s) pair, generate returns a Signal whose iq field is a (2, N) float32 tensor with N = round(sample_rate * duration_s).
Evidence. test_claim_A_shape_and_dtype in tests/validation/emitters/torchsig_am/test_experiment_contract.py calls generate for each of the 4 class labels at sample_rate = 200 kHz, duration_s = 0.01 s (N = 2 000) and asserts sig.iq.dtype == torch.float32 and sig.iq.shape == (2, 2000). All 4 assertions pass.
The dtype contract comes from the astype(np.float32) cast on both I and Q channels before torch.from_numpy. The sample-count contract follows from n_samples = int(round(sample_rate * duration_s)) in the implementation, which is evaluated before am_modulator is called.
3.2 Zero-mean IQ after DC-subtract¶
Claim. After the wrapper’s step iq_complex = iq_complex - iq_complex.mean(), the output satisfies |mean(I)| / peak(|I|) < 1e-4 and |mean(Q)| / peak(|Q|) < 1e-4 (when Q is non-trivial). This holds even for DSB-FC (double-sideband full-carrier), where TorchSig’s builder produces s(t) = A_c + m * x(t) with a non-zero carrier offset A_c = max(|x|) / mod_index.
Evidence. Two tests anchor this claim.
test_dsb_fc_carrier_is_real_and_dominant in tests/validation/emitters/torchsig_am/test_experiment_psd_bandwidth.py calls am_modulator directly (bypassing the wrapper) across 16 seeds and asserts the maximum |mean|/std ratio exceeds 0.5, confirming the pre-subtract DSB-FC output carries a substantial DC offset. Measurement: maximum ratio above 0.5 on at least one of 16 seeds.
test_claim_B_zero_mean_post_dc_subtract in tests/validation/emitters/torchsig_am/test_experiment_contract.py calls the wrapped generate for all 4 labels and asserts |mean(I)| <= 1e-4 * peak(|I|) (and similarly for Q when Q is non-trivial). All 4 per-label assertions pass.
The subtraction is therefore load-bearing: without it, DSB-FC would fail the rfgen zero-mean contract. The cost is that the carrier component A_c is removed, which affects the envelope statistics discussed in §4.
3.3 Determinism¶
Claim. Two calls to generate with the same torch.Generator seed (same class_label, sample_rate, duration_s, params) produce bit-identical IQ tensors. The same call with a different seed produces a different IQ tensor.
Evidence. test_claim_C_determinism in tests/validation/emitters/torchsig_am/test_experiment_contract.py, executed for all 4 class labels. Three generators seeded at 42, 42, and 43 are created. Seeds 42 and 42 produce identical arrays (verified with np.testing.assert_array_equal); seed 43 produces a different array. 8 total assertions (4 labels × 2 comparisons), all pass.
The determinism chain is: torch.Generator seeds torch.randint which draws one 64-bit integer; that integer seeds numpy.random.default_rng; TorchSig’s builder uses this RNG for its LPF (low-pass filter: a circuit or algorithm that passes frequencies below a cutoff and attenuates those above) design and message realization. Equal seeds produce bit-identical LPF coefficients and message sequences.
3.4 Class-label to TorchSig mode mapping¶
Claim. The mapping {am-dsb-fc → dsb, am-dsb-sc → dsb-sc, am-ssb-usb → usb, am-ssb-lsb → lsb} is implemented correctly; metadata.extras["am_mode"] reports the TorchSig-internal mode string used for each call.
Evidence. test_claim_D_label_to_mode_mapping in tests/validation/emitters/torchsig_am/test_experiment_contract.py. For each of the 4 label/mode pairs, generate is called and sig.metadata.extras["am_mode"] is compared to the expected TorchSig mode string. 4 assertions, all pass.
The mapping is defined in _AM_MODE_BY_LABEL at the module level of src/rfgen/emitters/torchsig_am.py. test_claim_I_metadata_fields additionally verifies the remaining metadata fields: family == "comms", class_taxonomy == ("comms", "am", <label>), bandwidth_hz, sample_rate_hz, and snr_db == inf.
3.5 DSB real-valued baseband; SSB complex with correct spectral peak side¶
Claim (a). At f_offset_hz = 0, the DSB modes produce real-valued baseband IQ: the Q channel satisfies max(|Q|) < 1e-6. The TorchSig builder returns a 1D real array for DSB modes before the wrapper promotes it to complex IQ.
Claim (b). The SSB modes produce complex baseband where both I and Q are non-trivial, and the spectral peak falls on the expected side: USB (upper sideband) peak at positive frequency, LSB (lower sideband) peak at negative frequency.
Evidence (a). test_claim_E_dsb_is_real_valued in tests/validation/emitters/torchsig_am/test_experiment_contract.py. For both DSB labels, max(abs(Q)) is verified below 1e-6. Both assertions pass.
Evidence (b). test_claim_F_ssb_peak_side_and_complex in tests/validation/emitters/torchsig_am/test_experiment_contract.py. For both SSB labels, the test verifies max(|Q|) > 1e-3 (non-trivial quadrature channel) and that the FFT (fast Fourier transform: the standard algorithm for computing the discrete frequency spectrum of a sampled signal) argmax frequency is positive for USB and negative for LSB. 2 assertions pass.
Figure 1 and Figure 2 show the Welch PSD (power spectral density: how signal energy is distributed across frequency) for DSB-FC and DSB-SC respectively. Figure 3 and Figure 4 show the SSB modes.

Figure 1 shows the symmetric two-lobed DSB-FC spectrum with the expected shape described in Haykin (2001, §3.3).

Figure 2 shows the DSB-SC spectrum where the carrier at DC is suppressed, leaving only the two message sidebands.

Figure 3 shows the LSB spectrum with the dominant energy lobe in the negative-frequency half-band. TorchSig’s real-IF construction leaves residual energy on the positive side (visible but smaller), which is the weak-SSB limitation discussed in §4.

Figure 4 shows the USB spectrum with the dominant energy lobe in the positive-frequency half-band, mirroring the LSB shape in Figure 3.
3.6 Occupied bandwidth tracks bandwidth_hz¶
Claim. The 99% OBW (occupied bandwidth: the smallest frequency interval containing 99% of the signal’s total power, defined by FCC 47 CFR §73.1570 and ITU-R BS.706-2) of the output stays within [0.9×, 1.5×] of bandwidth_hz for the DSB modes and within [0.5×, 2.0×] for the SSB modes. These tolerances are verified across 8 seeds at bandwidths of 5 kHz, 10 kHz, and 20 kHz for DSB, and 10 kHz for SSB, all at sample_rate = 200 kHz.
The wider SSB tolerance reflects TorchSig’s real-IF construction: the output spectrum retains power on both sidebands, so the 99% OBW window encompasses a larger fraction of the requested value.
Evidence. test_dsb_occupied_bandwidth and test_ssb_occupied_bandwidth in tests/validation/emitters/torchsig_am/test_experiment_psd_bandwidth.py. For each of the 6 DSB cells (3 bandwidths × 2 labels) and 2 SSB cells (1 bandwidth × 2 labels), 8 seeds are drawn and the mean OBW across seeds is asserted within tolerance. All 8 cells pass.
The OBW estimator uses scipy.signal.welch with nperseg=8192 (Hann window) and integrates outward from the spectral peak until 99% of total power is enclosed. The tolerance multipliers are set above the observed seed-to-seed jitter (~5%) to catch real regressions without false positives from TorchSig’s internal LPF parameter randomization (rng.uniform(0.05, 0.25) for the LPF transition bandwidth at each call).
The DSB-FC envelope distinguishability from test_dsb_fc_envelope_distinct_from_dsb_sc in tests/validation/emitters/torchsig_am/test_empirical_known_results.py provides an additional realism anchor: the raw TorchSig DSB-FC output has envelope mean/std ratio above 0.8 and strictly exceeds the DSB-SC ratio across 20 seeds, consistent with the Haykin (2001, §3.3) prediction that DSB-FC envelope |A_c + m·x(t)| is less variable than DSB-SC envelope |m·x(t)|.
test_dsb_fc_modulation_index_distribution_exceeds_broadcast_cap in tests/validation/emitters/torchsig_am/test_empirical_known_results.py measures the realized modulation depth (max(env) - min(env)) / (max(env) + min(env)) across 40 seeds and confirms that more than 50% of seeds produce over-modulated output (depth > 99%), consistent with TorchSig’s Uniform(0.8, 4.0) modulation-index distribution. This is a scope-bounded limitation discussed in §4.
Figure 5 shows the time-domain envelope of DSB-FC (pre-subtract) and DSB-SC, and Figure 6 shows the empirical 99% OBW versus requested bandwidth across all four labels.

Figure 5 shows the DSB-FC carrier bias (upper panel: envelope rides well above zero) versus DSB-SC (lower panel: envelope crosses zero). Both the in-phase signal (I) and the envelope magnitude are plotted in each panel.

Figure 6 shows all four labels tracking the identity line within the stated tolerances across the 2 kHz to 40 kHz bandwidth range.
3.7 Error paths surface EmitterError with self-diagnostic messages¶
Claim. Every documented error condition raises rfgen.errors.EmitterError. Unsupported class labels include at least one supported label in the error message so the caller can self-diagnose. No error path produces a bare Python exception or a silent wrong result.
Evidence. Eight test functions cover the error paths:
Unsupported label with hint:
test_claim_G_unsupported_labelintest_experiment_contract.py: asserts the error message contains at least one of the 4 supported labels when"am-vsb"is passed. Pass.Unknown class label:
test_unknown_class_label_raisesintest_robustness_pathological.py. Pass.Empty class label:
test_empty_string_label_raisesintest_robustness_pathological.py. Pass.Nyquist guard (
bandwidth_hz + 2*|f_offset_hz| >= sample_rate):test_claim_H_nyquist_guardintest_experiment_contract.pyandtest_bandwidth_exceeds_nyquist_raisesintest_robustness_envelope.py. The guard is stricter than TorchSig’s internal check (bandwidth <= sample_rate/2) because the wrapper adds a baseband frequency shift after the builder runs.Nyquist-boundary equality:
test_offset_at_nyquist_boundary_raisesintest_robustness_pathological.py: the guard uses strict>=, so exact equality is also rejected.Zero duration:
test_zero_duration_raisesintest_robustness_envelope.py. Pass.Negative duration:
test_negative_duration_raisesintest_robustness_pathological.py. Pass.NaN
f_offset_hz:test_nan_offset_propagates_as_emitter_errorintest_robustness_pathological.py: themath.isfiniteguard fires before the bandwidth check becauseabs(NaN) >= sample_rateevaluates toFalseunder IEEE 754, which would let a NaN offset pass the budget check silently. Match on"finite"in the error message. Pass.Inf
f_offset_hz:test_inf_offset_raises_or_caughtintest_robustness_pathological.py. Pass.bandwidth_hz <= 0:test_negative_bandwidth_pydantic_rejectsintest_robustness_envelope.py: PydanticField(gt=0)raisesValidationErrorat parameter construction beforegenerateis called. Pass.
3.8 Safe operating envelope across a parameter grid¶
Claim. Inside the documented envelope (sample_rate from 10 kHz to 10 MHz, bandwidth_hz from 1 kHz to 1 MHz with the Nyquist constraint, duration_s from 1/sample_rate to 5 s, f_offset_hz from 0 to (sample_rate - bandwidth_hz) / 2), all four class labels produce finite, non-zero-power IQ tensors with the correct sample count.
Evidence. Four test functions:
test_envelope_succeedsintest_robustness_envelope.py: parametric grid of 5(sample_rate, bandwidth_hz, duration_s)tuples × 4 labels = 20 combinations. Each call assertsnp.isfinite(iq).all()andnp.max(np.abs(iq)) > 0. All 20 pass.test_no_nan_inf_or_zero_power_across_gridintest_robustness_envelope.py: 4 bandwidths × 3 durations × 4 labels = 48 cells atsample_rate = 200 kHz. All pass.test_one_sample_succeedsintest_robustness_pathological.py:duration_s = 1/sample_rateproduces shape(2, 1)with finite values. Pass.test_very_long_durationintest_robustness_pathological.py: 5 s at 200 kHz = 1 000 000 samples, shape(2, 1000000), finite values. All 4 labels pass.test_frequency_offset_inside_budgetintest_robustness_envelope.py:f_offset_hz = 20 kHzwithbandwidth_hz = 10 kHzandsample_rate = 200 kHz(budget: 10 kHz + 2×20 kHz = 50 kHz < 200 kHz). Spectral centroid verified positive for all 4 labels. Pass.
4. Limits and what’s not validated¶
TorchSig SSB is not Hilbert-pair SSB. The usb and lsb modes use a real-IF translate-filter-translate-decimate construction rather than the canonical Hilbert-transform analytic-signal method. The Hilbert method produces s(t) = x(t) ± j·hilbert(x)(t) with ≥ 35 dB suppression of the unwanted sideband (ITU-R F.349-5, §3.2). TorchSig’s construction retains approximately 50% of power on the unwanted side: roughly 3 dB suppression. Only a small spectral first-moment (the signal’s average frequency weighted by PSD) asymmetry distinguishes USB from LSB, verified by test_ssb_spectral_first_moment_on_expected_side in test_empirical_known_results.py. The emitter is usable for AMC (automated modulation classification: machine-learning identification of modulation type from IQ observations) training where the classifier learns the weak spectral asymmetry, but not for SSB-demodulator validation or fixed-service HF (high-frequency: the 3–30 MHz band used for long-range SSB voice links) communications emulation.
DSB-FC modulation depth exceeds the FCC broadcast cap on the majority of calls. TorchSig draws the modulation index uniformly from [0.8, 4.0]; more than 50% of the 40-seed cohort produces over-modulation (envelope modulation depth above 99%), while FCC 47 CFR §73.1570(b)(1) caps US AM broadcast at 100%. For foundation-model training corpus generation, the wider distribution is a defensible label-noise expansion; for broadcast-compliance simulation it is not. The modulation index is not user-controllable through the wrapper API. Exposing modulation_index as a caller-controlled parameter would require patching TorchSig or bypassing am_modulator in favour of the lower-level AMSignalGenerator class; both options are architectural changes deferred to a future iteration.
DSB-FC envelope statistics are altered by DC subtraction. The textbook DSB-FC envelope |A_c + m·x(t)| is what an envelope-detection receiver exploits to demodulate the message. The wrapper’s iq_complex - iq_complex.mean() step removes A_c, shifting the post-subtract envelope statistics toward those of DSB-SC. The envelope distinguishability claim in §3.6 is therefore asserted on raw TorchSig output, not on the wrapped output. A downstream consumer building an envelope-detection AM demodulator should call torchsig.signals.builders.am.am_modulator directly.
The message is Gaussian noise, not real audio. The baseband message x(t) is band-limited Gaussian noise shaped by TorchSig’s iterative-design LPF. This matches the RadioML 2018.01a convention for analog AM label generation. A classifier trained on this emitter alone does not learn audio-cadence features (pauses, formants, tonal energy).
No broadcast-grade or protocol impairments. Pre-emphasis, envelope companding, AM-stereo / C-QUAM stereo subcarrier, mains hum, AGC (automatic gain control: a circuit that adjusts receiver gain to keep output amplitude stable) overshoot, PA non-linearity, IQ imbalance, and oscillator phase noise are absent. These are introduced at the channel and receiver-frontend layers downstream in the rfgen pipeline.
VSB, ISB, and C-QUAM are not supported. None of these AM variants are available in TorchSig’s am_modulator.
5. References¶
Published works¶
Reference |
Role |
|---|---|
S. Haykin, Communication Systems, 4th ed., Wiley, 2001, §3.3, ISBN 978-0-471-17869-9 |
Canonical DSB-FC envelope |
J. G. Proakis and M. Salehi, Digital Communications, 5th ed., McGraw-Hill, 2008, §3.2, ISBN 978-0-07-295718-2 |
Hilbert-transform SSB and analytic-signal representation |
ITU-R Recommendation BS.706-2 (2002) |
Broadcast AM envelope reference and 99% occupied-bandwidth convention |
ITU-R Recommendation F.349-5 (2010), §3.2 |
Fixed-service HF SSB unwanted-sideband suppression target (≥ 35 dB); benchmark for the weak-SSB limitation documented in §4 |
FCC 47 CFR §73.1570, “Modulation levels: AM, FM, TV, and Class A TV aural” |
AM-broadcast modulation-level rule; §73.1570(b)(1) caps negative-direction modulation at 100%; benchmark for the over-modulation limitation in §4 |
T. J. O’Shea and N. West, “Radio machine learning dataset generation with GNU Radio,” Proceedings of the GNU Radio Conference, vol. 1, no. 1, 2016 |
RadioML 2018.01a convention for Gaussian-noise AM message; basis for the non-audio message design choice |
Libraries¶
PyPI distribution |
Installed version |
Documentation URL |
Role in validation |
|---|---|---|---|
|
2.1.1 |
Provides |
|
|
2.12.1 |
|
|
|
1.18.0 |
https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.welch.html |
|
|
2.4.6 |
|
|
|
2.13.4 |
|
|
|
- |
Figure generation in |