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