Add a custom channel transformation

Warning

Pre-implementation. APIs describe the target plugin surface.

Add one channel transformation for a custom transmit impairment, propagation wrapper, receiver-capture step, or receive-hardware step. This guide implements the low-noise-amplifier (LNA) noise slot as a concrete example; other slots use their matching per-transformation Base* abstract base class (ABC).

When to use this

Use this when no existing Sionna, TorchSig, analytic, or receiver-backend implementation matches your experiment. Prefer wrapping a mature tool over hand-coding RF behavior.

Prerequisites

Read Concepts / Channels and choose the Group and Transformation slot your channel belongs to. The API reference lists the slot-specific ABCs under Reference / rfgen.channels § Per-transformation ABCs.

Use the ABC for the transformation you implement. This example subclasses BaseLNANoise because Transformation.LNA_NOISE owns receiver LNA noise. Do not subclass BaseChannel directly unless the reference page says that slot has no narrower ABC.

Minimal implementation

The params model is a Pydantic validation model. rfgen validates the plugin’s params block against the class returned by schema() before constructing the channel.

import torch
from pydantic import BaseModel, Field

from rfgen.channels import BaseLNANoise, Transformation
from rfgen.channels.context import ChannelContext
from rfgen.core.types import Signal
from rfgen.core.registry import register_channel


class MyNoiseParams(BaseModel):
    scale: float = Field(default=0.01, ge=0.0)


@register_channel(name="my_noise")
class MyNoise(BaseLNANoise):
    transformation = Transformation.LNA_NOISE

    def __init__(self, scale: float = 0.01):
        self.params = MyNoiseParams(scale=scale)

    def apply(self, signal: Signal, ctx: ChannelContext) -> Signal:
        noise = self.params.scale * torch.randn_like(signal.iq, generator=ctx.rng)
        return signal.replace(iq=signal.iq + noise)

    def schema(self) -> type[BaseModel]:
        return MyNoiseParams

The framework supplies signal and ctx when BaseChannel.apply runs. ctx.rng is the deterministic random-number generator for this sample, so every random draw must use it. Return a new Signal and preserve metadata fields unless the selected transformation owns the change.

Register

In-tree:

@register_channel(name="my_noise")
class MyNoise(BaseLNANoise):
    ...

Third-party package:

[project.entry-points."rfgen.channels"]
my_noise = "my_pkg.channels:MyNoise"

Configure

For the current channel config contract, put post-sum receiver-capture plugins such as my_noise in post_sum; entries resolve through the plugin registry to a concrete BaseChannel subclass.

channel:
  post_sum:
    - name: my_noise
      params:
        scale: 0.01

Verify

my_noise_smoke is a local channel config preset you create for this plugin. narrowband_classifier_baseline_xs is an extra-small scenario preset used as a generation smoke test.

rfgen list-channels | grep my_noise
rfgen validate channel=my_noise_smoke
rfgen generate +preset=narrowband_classifier_baseline_xs channel=my_noise_smoke

Add contract tests for shape, dtype, metadata preservation, deterministic RNG use, transformation ordering, and multi-RX behavior.

Troubleshoot

Symptom

Fix

ChannelPipeline rejects the chain

Check the channel transformation value and ordering.

Re-runs are not deterministic

Ensure all random draws use the supplied generator.

Labels break downstream

Preserve metadata and update only fields owned by the transformation.

Next steps