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.
Integration boundary and configuration (§3.1): the adapter is explicit, optional, and registry-selected.
Profile selection and exclusions (§3.2): selected public transforms match the documented RX-style subset.
Pinned seeded CPU repeatability (§3.3): the caller’s generator controls replay in the qualified environment.
Eligible-record output contract (§3.4): tested valid inputs return finite
(2, N)float32 classification samples.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, andIntermodulationProducts; 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
ChannelPipelinestage 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¶
TorchDSP. TorchSig PyPI distribution,
torchsig==2.1.1, installed for this validation. PyPI project page.TorchDSP. TorchSig v2.1.1,
torchsig/transforms/impairments.py. Canonical tagged source.TorchDSP. TorchSig v2.1.1,
torchsig/transforms/transforms.py. Canonical tagged source.TorchDSP. TorchSig v2.1.1,
torchsig/utils/random.pyandtorchsig/transforms/functional.py. Canonical random-source file, canonical functional-source file.