TorchSig RX classification profile validation

Note

This record validated the benchmark profile against the Record type, which was retired with the legacy record path. The profile itself survives and now reads a LabeledScene; the evidence below is kept as written, and its prose Record references name the type of the day. The signatures below are current, because Reference pages link here to let a reader check a contract.

Its tests/unit/test_torchsig_interop.py::test_torchsig_classification_* citations name tests that no longer exist: that module was deleted by the Signal Dataset adoption (0b1a8fd89), before this retirement, and TorchSigClassificationAugmentation in rfgen.integrations.torchsig.augmentation has had no test since. A rewritten test_torchsig_interop.py covers the conversion pair only.

Validated with documented limitations.

1. The component

TorchSigClassificationAugmentation is an opt-in, classification-only RFML (radio-frequency machine-learning) benchmark adapter. It is not a calibrated receiver-hardware model and is not a BaseChannel. It accepts an eligible completed rfgen Record, applies a selected subset of public TorchSig 2.1.1 RX-style transforms, and produces a separate BenchmarkSample:

class TorchSigClassificationAugmentation(BaseRecordAugmentation):
    def __init__(self, config: TorchSigProfileConfig | Mapping[str, object] | None = None) -> None: ...
    def augment(self, record: LabeledScene, *, rng: torch.Generator) -> BenchmarkSample: ...

The adapter has one fixed profile name, torchsig_rx_classification_v2_1_1, one permitted upstream version, 2.1.1, and one label contract, classification_only. Its user configuration selects an ordered subset of the allow-listed transforms and supplies only serializable public constructor parameters. Configuration is resolved through the explicit rfgen.augmentations entry-point selector torchsig_classification; normal generation never applies it implicitly.

rfgen packed IQ uses a (2, N) CPU torch.float32 tensor, where row zero is the in-phase component and row one is the quadrature component. TorchSig signal transforms consume a one-dimensional NumPy complex64 array. The adapter converts between these representations, then returns a BenchmarkSample containing IQ, the one emitter’s class target, and replay provenance.

adapter = TorchSigClassificationAugmentation()
sample = adapter.augment(class_only_record, rng=torch.Generator().manual_seed(19))
assert sample.iq.shape == class_only_record.iq.shape
assert sample.iq.dtype is torch.float32
assert sample.class_target == class_only_record.emitters[0].class_name

The qualified environment was CPython 3.12.10, PyTorch 2.3.0 on CPU, NumPy 2.4.1, and installed torchsig==2.1.1.

2. What we validated

This validation establishes five load-bearing claims.

  1. Integration boundary and configuration (§3.1): the adapter is explicit, optional, and registry-selected.

  2. Profile selection and exclusions (§3.2): selected public transforms match the documented RX-style subset.

  3. Pinned seeded CPU repeatability (§3.3): the caller’s generator controls replay in the qualified environment.

  4. Eligible-record output contract (§3.4): tested valid inputs return finite (2, N) float32 classification samples.

  5. Minimum input length (§3.5): the adapter must reject unsupported short records before TorchSig execution.

3. Evidence per claim

3.1 Integration boundary and configuration

TorchSigClassificationAugmentation implements rfgen’s BaseRecordAugmentation, not BaseChannel. Its module imports no TorchSig symbol until construction, so importing rfgen.integrations.torchsig with TorchSig unavailable succeeds while constructing the adapter raises the actionable BackendUnavailableError. The installed rfgen.augmentations entry-point registry exposes exactly torchsig_classification and resolves it to this implementation.

TorchSigProfileConfig rejects an unpinned upstream version, a profile name other than torchsig_rx_classification_v2_1_1, and every label contract other than classification_only. TorchSigTransformConfig rejects names outside the profile allow-list. Construction introspects each selected public TorchSig constructor and rejects unknown parameters before a record is processed.

Evidence: direct import-without-backend probe, installed entry-point lookup, and tests/unit/test_torchsig_interop.py::test_torchsig_classification_augmentation_validates_upstream_params_eagerly.

3.2 Profile selection and exclusions

The default profile explicitly composes RandomApply wrappers around NonlinearAmplifier, CoarseGainChange, IQImbalance, CarrierPhaseNoise, CarrierFrequencyDrift, CarrierPhaseOffset, PassbandRipple, ClockDrift, ClockJitter, Quantize, and DigitalAGC. Their selected application probabilities are 0.75, 0.25, 0.50, 0.75, 0.75, 1.00, 0.75, 0.75, 0.75, 0.75, and 0.25 respectively. These are the same probabilities and relative order used by TorchSig 2.1.1’s RX-hardware list after the intentional exclusions.

The adapter does not call Impairments(level=1): that upstream dataset chain also starts with RandAugment. It excludes RandAugment, TX transforms, channel models, Spurs, and IntermodulationProducts. The last two create additional spectral products that rfgen cannot attribute to the sole classification emitter. This is a benchmark-transform selection, not a claim of physical RX hardware fidelity.

Evidence: direct inspection and instantiation of all eleven public classes in the installed distribution; TorchSig’s v2.1.1 impairment implementation and v2.1.1 transform implementation.

3.3 Pinned seeded CPU repeatability

augment() derives one integer seed from its supplied torch.Generator and passes it to the TorchSig transform objects and root composition. Ten independently created caller generators for seeds 0 through 9 yielded byte-identical IQ and identical provenance for repeat calls with the same input. A single caller generator advanced between two calls, yielding distinct recorded seeds and distinct output. No global random-state API is used by the adapter.

This establishes byte identity only for the environment named in §1. The output is CPU-resident because the integration performs a CPU NumPy conversion. GPU execution, gradients through the adapter, and byte identity across Python, NumPy, PyTorch, or TorchSig versions are outside this claim.

Evidence: direct 10-seed repeatability probe and tests/unit/test_torchsig_interop.py::test_torchsig_classification_augmentation_default_is_byte_deterministic; TorchSig v2.1.1 random utilities.

3.4 Eligible-record output contract

For valid CPU inputs of 3, 8, 64, 256, and 4096 samples, the full default profile completed for 100 caller seeds per length. Every output had shape (2, N), torch.float32 dtype, CPU placement, and finite values. The class target was copied from the sole emitter. The provenance stored the integration name, pinned TorchSig version, profile, label contract, derived seed, and resolved transform configuration.

The adapter rejects records with more or fewer than one emitter, any bounding boxes, segmentation, non-(2, N) IQ, or a dtype other than torch.float32. It returns a BenchmarkSample, never a Record, so it does not silently retain stale detection or segmentation labels. This boundary is necessary: TorchSig default IQ imbalance can add DC energy, and the selected clock and frequency-drift transforms can alter time or frequency support without rfgen box propagation.

Evidence: direct 500-run layout/dtype/finiteness probe and tests/unit/test_torchsig_interop.py::test_torchsig_classification_augmentation_rejects_exhaustive_labels.

3.5 Minimum input length

The profile’s supported minimum is three complex samples. The adapter checks N < 3 before CPU conversion and raises a clear ConfigError, so upstream short-record exceptions such as low >= high, empty-array reductions, and stride errors do not leak through the public contract. In a 100-seed probe at each of lengths one and two, every invocation returned that intentional error; all 100 three-sample invocations completed with valid output. Lengths one and two are covered by the parametrized acceptance test test_torchsig_classification_augmentation_rejects_unqualified_short_iq.

Evidence: direct 100-seed boundary probes at lengths 1, 2, and 3 against the installed profile; TorchSig v2.1.1 transform functions.

4. Limits and scope-bounded claims

  • This adapter preserves only one classification target. It rejects multi-emitter, bounding-box, and segmentation records because its selected TorchSig transforms do not propagate rfgen’s exhaustive labels.

  • The explicit profile deliberately differs from the bundled Impairments(level=1) dataset transform chain by excluding RandAugment, Spurs, and IntermodulationProducts; it must not be described as the complete upstream impairment bundle.

  • CPU conversion is part of the adapter boundary. It produces CPU tensors and has no CUDA, accelerator, or autograd contract.

  • The profile overlaps physical RX concepts including gain, quantization, IQ imbalance, phase noise, and clock error. It is a separate benchmark augmentation and must not be represented as an additional ChannelPipeline stage or as hardware-realism evidence.

  • The supported input envelope begins at three complex samples. Inputs with one or two samples are rejected because the selected upstream transforms do not define a stable operation for them.

5. References

  1. TorchDSP. TorchSig PyPI distribution, torchsig==2.1.1, installed for this validation. PyPI project page.

  2. TorchDSP. TorchSig v2.1.1, torchsig/transforms/impairments.py. Canonical tagged source.

  3. TorchDSP. TorchSig v2.1.1, torchsig/transforms/transforms.py. Canonical tagged source.

  4. TorchDSP. TorchSig v2.1.1, torchsig/utils/random.py and torchsig/transforms/functional.py. Canonical random-source file, canonical functional-source file.