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 at v2.1.1 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

class_label

str

-

required

One of the four AM class labels (see below)

sample_rate

float

Hz

required

Complex-baseband sample rate; Nyquist guard enforced

duration_s

float

s

required

Output duration; N = round(sample_rate * duration_s)

f_offset_hz

float

Hz

required

Baseband frequency shift applied after modulation; must be finite

rng

torch.Generator

-

required

Seeds the numpy RNG forwarded to TorchSig’s builder

bandwidth_hz

float

Hz

10 000

Requested −3 dB spectral width; must satisfy bandwidth_hz + 2*|f_offset_hz| < sample_rate

Worked example.

import torch
from rfgen.integrations.torchsig.emitters 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 am_mode

Common name

am-dsb-fc

dsb

Double-sideband (DSB: two symmetric spectral copies of the message around a carrier), full carrier (broadcast AM)

am-dsb-sc

dsb-sc

Double-sideband, suppressed carrier

am-ssb-usb

usb

Single-sideband (SSB: only one of the two DSB spectral copies), upper sideband

am-ssb-lsb

lsb

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.

  1. Output shape and dtype (§3.1): all four class labels produce a (2, N) float32 IQ tensor with the correct sample count.

  2. 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.

  3. Determinism (§3.3): the same generator seed yields bit-identical output; a different seed yields different output.

  4. Class-label to TorchSig mode mapping (§3.4): rfgen labels route to the correct TorchSig am_mode argument.

  5. DSB real-valued baseband; SSB complex with correct spectral peak side (§3.5): structural property confirming the modulation geometry for all four modes.

  6. Peak-centered 99%-power interval tracks bandwidth_hz (§3.6): the measured interval stays within verified tolerances of the requested value across DSB and SSB modes. It is not equal-tail regulatory occupied bandwidth.

  7. Runtime error paths surface EmitterError; schema construction raises ValidationError (§3.7): runtime guards provide self-diagnostic EmitterErrors, while invalid parameter schemas fail at construction.

  8. 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). Here DC (direct current) denotes the zero-frequency complex-baseband component represented by the record’s sample mean. 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 implementation mapping lives in src/rfgen/integrations/torchsig/emitters/torchsig_am.py; callers discover the supported labels through the public TorchSigAMEmitter.supported_classes contract. 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 scipy.signal.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: Welch PSD for am-dsb-fc at 10 kHz bandwidth, seed 7, 200 kHz sample rate. Shows the symmetric two-sideband spectrum about DC, confirming the DSB-FC spectral signature. Supports claim §3.5 (DSB real-valued baseband).

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

Figure 2: Welch PSD for am-dsb-sc at 10 kHz bandwidth, seed 7, 200 kHz sample rate. Shows the suppressed-carrier spectral shape contrasted with DSB-FC; symmetric spectral width supports §3.6 bandwidth tracking.

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

Figure 3: Welch PSD for am-ssb-lsb at 10 kHz bandwidth, seed 7, 200 kHz sample rate. Energy concentrated in the negative-frequency half-band confirms the LSB label assignment. Supports claims §3.5 and §3.6.

Figure 3 shows the LSB spectrum with the dominant energy lobe in the negative-frequency half-band. TorchSig begins with a numerically real shaped message, then frequency-shifts it into a complex intermediate, applies the low-pass filter at the translated frequency, shifts it again, and decimates by two. This complex translate-filter-translate-decimate path leaves empirically measured residual energy on the opposite sideband; the source does not use a real-valued intermediate-frequency signal whose conjugate symmetry would explain that residual. The smaller positive-side component is the weak-SSB limitation discussed in §4.

Figure 4: Welch PSD for am-ssb-usb at 10 kHz bandwidth, seed 7, 200 kHz sample rate. Energy concentrated in the positive-frequency half-band confirms the USB label assignment. Supports claims §3.5 and §3.6.

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 Peak-centered 99%-power interval tracks bandwidth_hz

Claim. The peak-centered frequency interval enclosing 99% of measured Welch power 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.

This measured interval is not the occupied bandwidth defined by 47 CFR §2.202(a), which places 0.5% of total mean power below the lower edge and 0.5% above the upper edge. The peak-centered estimator does not enforce those equal tails, particularly for asymmetric SSB spectra. Equal-tail regulatory occupied bandwidth remains unvalidated here.

The wider SSB tolerance reflects the empirically observed opposite-sideband residual from TorchSig’s complex translate-filter-translate-decimate construction, so the peak-centered 99%-power interval encompasses power on both sides of the spectral peak.

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 peak-centered 99%-power interval across seeds is asserted within tolerance. All 8 cells pass. The test names are retained identifiers; they do not change the estimator into equal-tail regulatory occupied bandwidth.

The interval 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 peak-centered 99%-power interval versus requested bandwidth across all four labels.

Figure 5: Time-domain envelope of DSB-FC (pre-DC-subtract, upper panel) and DSB-SC (lower panel) across 10 ms at 200 kHz / 10 kHz bandwidth, seed 7. The high carrier-level bias of the DSB-FC envelope confirms DC subtraction is load-bearing. Supports §3.2 and §4 (envelope alteration).

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: Empirical peak-centered 99%-power interval versus requested bandwidthhz, 5-seed mean per label, 200 kHz sample rate. Dashed line is y = x (ideal). DSB modes stay within 1.5× and SSB modes within 2.0×. Supports claim §3.6; it is not an equal-tail regulatory-OBW measurement.

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 Runtime error paths surface EmitterError; schema construction raises ValidationError

Claim. Runtime generate guards raise rfgen.core.errors.EmitterError, and unsupported runtime class labels include at least one supported label in the error message so the caller can self-diagnose. Invalid parameter schemas raise Pydantic ValidationError before generate is called. No documented validation path silently produces a wrong result.

Evidence. Eight test functions cover the error paths:

  • Unsupported label with hint: test_claim_G_unsupported_label in test_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_raises in test_robustness_pathological.py. Pass.

  • Empty class label: test_empty_string_label_raises in test_robustness_pathological.py. Pass.

  • Nyquist guard (bandwidth_hz + 2*|f_offset_hz| >= sample_rate): test_claim_H_nyquist_guard in test_experiment_contract.py and test_bandwidth_exceeds_nyquist_raises in test_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_raises in test_robustness_pathological.py: the guard uses strict >=, so exact equality is also rejected.

  • Zero duration: test_zero_duration_raises in test_robustness_envelope.py. Pass.

  • Negative duration: test_negative_duration_raises in test_robustness_pathological.py. Pass.

  • NaN f_offset_hz: test_nan_offset_propagates_as_emitter_error in test_robustness_pathological.py: the math.isfinite guard fires before the bandwidth check because abs(NaN) >= sample_rate evaluates to False under 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_caught in test_robustness_pathological.py. Pass.

  • bandwidth_hz <= 0: test_negative_bandwidth_pydantic_rejects in test_robustness_envelope.py: Pydantic Field(gt=0) raises ValidationError at parameter construction before generate is 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_succeeds in test_robustness_envelope.py: parametric grid of 5 (sample_rate, bandwidth_hz, duration_s) tuples × 4 labels = 20 combinations. Each call asserts np.isfinite(iq).all() and np.max(np.abs(iq)) > 0. All 20 pass.

  • test_no_nan_inf_or_zero_power_across_grid in test_robustness_envelope.py: 4 bandwidths × 3 durations × 4 labels = 48 cells at sample_rate = 200 kHz. All pass.

  • test_one_sample_succeeds in test_robustness_pathological.py: duration_s = 1/sample_rate produces shape (2, 1) with finite values. Pass.

  • test_very_long_duration in test_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_budget in test_robustness_envelope.py: f_offset_hz = 20 kHz with bandwidth_hz = 10 kHz and sample_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 complex 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 (Recommendation ITU-R BS.640-3, §1.7). 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 HF-broadcast 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. Caller-controlled modulation depth would require a different backend surface, such as patching TorchSig or using its lower-level AMSignalGenerator class at v2.1.1.

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 TorchSig message is Gaussian noise, not real audio. The observed TorchSig baseband message x(t) is band-limited Gaussian noise shaped by TorchSig’s iterative-design LPF. A classifier trained on this emitter alone does not learn audio-cadence features (pauses, formants, tonal energy). Target-corpus AM message-source semantics require lawful target evidence.

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 s(t) = A_c[1 + k_a m(t)] cos(2π f_c t), DSB-SC, and SSB derivations; envelope distinguishability prediction

J. G. Proakis and M. Salehi, Digital Communications, 5th ed., McGraw-Hill, 2008, §3.2, ISBN 978-0-07-295716-7

Hilbert-transform SSB and analytic-signal representation s(t) = x(t) ± j·hilbert(x)(t)

Recommendation ITU-R BS.706-2 (02/1998), Data system in monophonic AM sound broadcasting (AMDS)

Supplementary-data context for monophonic AM broadcasting; it does not define the peak-centered 99%-power interval estimator used here

Recommendation ITU-R BS.640-3 (10/1997), Single sideband (SSB) system for HF broadcasting, §1.7

Requires at least 35 dB unwanted-sideband suppression relative to the wanted sideband; benchmark for the weak-SSB limitation documented in §4

47 CFR §2.202(a) and §73.1570(b)(1), eCFR (accessed 2026-07-25; Title 47 current through 2026-07-23)

§2.202(a) defines occupied bandwidth by the two 0.5%-power tails; §73.1570(b)(1), last amended by 89 FR 7255 (2024), caps recurring negative AM peaks at 100%

Libraries

PyPI distribution

Installed version

Documentation URL

Role in validation

torchsig

2.1.1

v2.1.1 AM builder source

Provides am_modulator; the IQ synthesis backend under test

torch

2.12.1

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

torch.Generator for seeded RNG; torch.from_numpy for tensor output

scipy

1.18.0

https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.welch.html

scipy.signal.welch Welch PSD estimator used in all bandwidth validation tests

numpy

2.4.6

numpy.random.default_rng and numpy.exp

Per-call RNG, baseband frequency shift, and envelope arithmetic

pydantic

2.13.4

https://docs.pydantic.dev/latest/

TorchSigAMParams Pydantic v2 model; Field(gt=0) constraint on bandwidth_hz

matplotlib

3.10.8

https://matplotlib.org/3.10.8/api/index.html

Figure generation in tests/validation/emitters/torchsig_am/generate_figures.py