rfgen.core.protocols and rfgen.core.pipeline

Scientific validation

The channel pipeline transformations and their immutable parameter record have been scientifically validated against published references. See the per-component reports:

Each report covers construct validity, mathematical correctness against cited equations, empirical comparison to published reference numbers, literature grounding, experimental methodology, operating envelope, and documented limitations.

The rfgen.core.protocols module ships the channel-transformation contract: the BaseChannel ABC, the per-call ChannelContext and ChannelRxParams value types, and the SpectralEffect, Transformation, and Group enums. Concrete per-transformation ABCs and their built-in implementations live in sibling modules:

TorchSig, the external RFML benchmark library, appears only in the opt-in adapter path; see TorchSig Interop.

Module

API page

What it ships

rfgen.hardware.tx

Hardware

TX-side concretes (DAC, PA, TX phase noise, TX IQ-imbalance, CFO)

rfgen.engine

Engine

AWGN and the Sionna-backed propagation skeletons

rfgen.receiver.stages

Receiver stages

The receiver stage core: the one implementation of each receiver stage (LO error, mixer, IF filter, resampler, SRO, LNA noise, ADC, RX phase noise, IQ imbalance, AGC)

The per-device fingerprint store lives in rfgen.calibration.fingerprint; see the Device Fingerprint reference for FingerprintParams and DeviceRegistry.

This page documents the common surface. The per-transformation ABCs are listed in the class index with cross-references to their owning module page. The nine receiver ABCs are stage families rather than BaseChannel subclasses; they are listed here for the slot map and documented on the receiver-stages page.

Pipeline model

The pipeline is a sequence of BaseChannel subclasses with one transformation per slot. Each concrete pins its slot via the Transformation class variable; the scene composer reads that variable to dispatch each emitter through the canonical pipeline order.

The 15 transformations split across a pre-sum and a post-sum boundary. TX stages run per emitter; propagation follows scene.channel_application (per path or, when supported, on the summed receiver buffer); the nine RX-capture and RX-hardware stages run post-sum per receiver.

The emitter plugin’s baseband modulation is outside this pipeline; it is the emitter contract, not a channel transformation. In composed scenes, the scene composer writes each placed component’s absolute realized_carrier_hz after frequency placement. Each receiver carries center_freq_hz, bandwidth_hz, sample_rate_hz, and noise_figure_db on its ChannelRxParams instance.

Class index

Class

Kind

Group / Role

BaseChannel

abc

Common ABC; every concrete transformation inherits from this

ChannelContext

datatype

Per-call context passed to every apply() invocation

ChannelRxParams

datatype

Active receiver RF and hardware parameters

ChannelPipeline

dataclass

Validated, ordered chain of BaseChannel transformations

ChannelChainConfig

alias

Alias of ChannelConfig; the config type accepted by ChannelPipeline.from_config

EmptyParams

sentinel

Pydantic model returned from schema() by parameterless transformations

TransformationLogEntry

TypedDict

Schema for one entry of SignalMetadata.transformation_log

FINGERPRINT_PARAM_KEYS

constant

Canonical key tuple consumed from emitter_meta.extras["fingerprint_params"]

SpectralEffect

enum

Declares whether a transformation preserves or updates spectral coordinates

Group

enum

IntEnum of the two pipeline groups

Transformation

enum

IntEnum of the 15 named transformations

BaseDACQuantization

abc

TX impairments: DAC quantization (lives in rfgen.hardware.tx)

BasePANonlinearity

abc

TX impairments: PA nonlinearity (lives in rfgen.hardware.tx); concretes RappPA, SalehPA

BaseTXPhaseNoise

abc

TX impairments: TX phase noise (lives in rfgen.hardware.tx); concrete LeesonTXPhaseNoise

BaseTXIQImbalance

abc

TX impairments: TX IQ imbalance (lives in rfgen.hardware.tx); concrete LinearTXIQImbalance

BaseCFO

abc

TX impairments: CFO (lives in rfgen.hardware.tx); concrete LinearCFO

BaseChannelPropagation

abc

Channel propagation (lives in rfgen.engine); concretes AWGNChannel, SionnaRT, SionnaUMa, SionnaUMi, SionnaRMa, SionnaTDL, SionnaCDL, RayleighBlockFading, SionnaFlatFading, SionnaCIRDataset

BaseLOFrequencyErrorStage

abc

Receiver capture plane: LO frequency error (lives in rfgen.receiver.stages.frequency); concrete LOFrequencyErrorStage

BaseMixerStage

abc

Receiver capture plane: RX mixer (lives in rfgen.receiver.stages.frequency); concrete MixerStage

BaseIFFilterStage

abc

Receiver capture plane: IF filter (lives in rfgen.receiver.stages.filtering); concrete IFFilterStage

BaseResamplerStage

abc

Receiver capture plane: resampler (lives in rfgen.receiver.stages.filtering); concretes PolyphaseResamplerStage (rate conversion) and SampleRateOffsetStage (clock drift)

BaseThermalNoiseStage

abc

Receiver capture plane: LNA noise (lives in rfgen.receiver.stages.analog); concrete ThermalNoiseStage

BaseADCQuantizationStage

abc

Receiver hardware plane: ADC quantization (lives in rfgen.receiver.stages.conversion); concrete ADCQuantizerStage

BaseRXPhaseNoiseStage

abc

Receiver hardware plane: RX phase noise (lives in rfgen.receiver.stages.analog); concrete RXPhaseNoiseStage

BaseIQImbalanceStage

abc

Receiver hardware plane: RX IQ imbalance (lives in rfgen.receiver.stages.analog); concrete IQImbalanceStage

BaseAGCStage

abc

Receiver hardware plane: AGC (lives in rfgen.receiver.stages.conversion); concrete AGCStage

For per-class signatures of TX, propagation, and RX concretes, see the TX Impairments, Engine, and Receiver stages pages.


class rfgen.core.protocols.SpectralEffect

class SpectralEffect(StrEnum):
    PRESERVES = "preserves"
    UPDATES = "updates"

Classifies the intended relationship between the input and output spectral metadata of one BaseChannel call. Spectral metadata means the sample rate, occupied bandwidth, and realized carrier coordinates stored with a signal.

Member

Declared transformation model

PRESERVES

The transformation describes its spectral coordinates as unchanged. This is the default on BaseChannel.

UPDATES

The transformation describes itself as changing spectral coordinates.

The declaration communicates transformation taxonomy; it is not a runtime guarantee that coordinates were preserved or updated. Returned metadata is authoritative. The fixed-window renderer validates only that returned metadata is finite, has positive sample rate and bandwidth, and represents one interval within the capture. It does not assert output frequency or estimate spectral coordinates from I/Q samples.


class rfgen.core.enums.Group

class Group(IntEnum):
    TX      = 1   # per-emitter TX impairments
    CHANNEL = 2   # per-(emitter, RX) pair propagation

Pipeline group tag. The leading digit of a Transformation integer is its group; the trailing digit is intra-group order. Group is distinct from Transformation: Group partitions the pre-sum chain; Transformation names individual operations within a group.

Both members are pre-sum. The receiver is not a pipeline group: its nine transformations run in exactly one place, the shared receiver frontend chain, so there is no partition left for a group to express. The integers 3 and 4 are retired rather than reused, because stored transformation-log entries carry them as pinned constants.

Transformation carries a .group property that returns the matching Group member for TX and CHANNEL transformations, and a .is_receiver predicate that is True for the nine receiver transformations. Reading .group on a receiver transformation raises ValidationError.


class rfgen.core.enums.Transformation

class Transformation(IntEnum):
    # Group.TX (10s)
    DAC             = 11
    PA              = 12
    TX_PHASE_NOISE  = 13
    TX_IQ_IMB       = 14
    CFO             = 15

    # Group.CHANNEL (20s)
    PROPAGATION     = 21

    # Receiver, capture plane (30s)
    RX_LO_FREQUENCY_ERROR = 30
    RX_MIXER        = 31
    IF_FILTER       = 32
    RESAMPLER       = 33
    LNA_NOISE       = 34

    # Receiver, hardware plane (40s)
    ADC             = 41
    RX_PHASE_NOISE  = 42
    RX_IQ_IMB       = 43
    AGC             = 44

Integer tag pinned to every concrete channel implementation as a ClassVar[Transformation]. The integer value encodes ordering: value // 10 is the Group integer, value % 10 is the intra-group canonical order. The scene composer reads this tag to dispatch each emitter through the pipeline in monotonic order.

Notes

  • Multiple concrete implementations per transformation are allowed; only one transformation tag per class.

  • Do not define a new class with a Transformation value that does not appear in this enum. Renumbering existing members is a breaking change for the config validator.


class rfgen.core.protocols.ChannelContext

@dataclass(frozen=True, slots=True)
class ChannelContext:
    """Per-call context handed to every BaseChannel.apply() invocation."""

    emitter_meta: SignalMetadata
    rx_params: ChannelRxParams
    scene_id: str
    sample_idx: int
    rng: torch.Generator
    tx_pose: GeometryPose | None = None
    geometry_asset_refs: tuple[GeometryAssetRef, ...] = ()
    rt_solver_params: BaseModel | None = None

Kind. Frozen dataclass / value type.

Carries the per-transformation call context into BaseChannel.apply(). The scene driver constructs one ChannelContext per (emitter, RX, transformation) call; it is not shared across calls.

Fields

Field

Type

Description

emitter_meta

SignalMetadata

Ground-truth metadata for the emitter being transformed. TX-side transformations read device_id, realized_carrier_hz, sample_rate_hz; channel propagation reads realized_carrier_hz for Doppler and path-loss scaling; RX-side transformations read bandwidth_hz and sample_rate_hz.

rx_params

ChannelRxParams

The active receiver’s RF and hardware parameters. Carries center_freq_hz, bandwidth_hz, sample_rate_hz, and noise_figure_db.

scene_id

str

Stable scene identifier, used by transformations to derive deterministic per-scene seeds when needed.

sample_idx

int

Zero-based index of this sample within the current shard. Combined with scene_id for deterministic sub-scene seed derivation.

rng

torch.Generator

The per-call RNG. All randomness inside apply() MUST be drawn from this generator. Each call receives a fresh generator state so calls are independent.

tx_pose

GeometryPose | None

Optional typed transmitter pose for Sionna propagation.

geometry_asset_refs

tuple[GeometryAssetRef, …]`

Geometry assets active for this channel call. SionnaRT requires the appropriate scene asset; the statistical Sionna backends do not consume an asset.

rt_solver_params

BaseModel | None

Optional solver configuration. SionnaRT validates it as RTSolverConfig; the five statistical Sionna backends validate it as StatisticalSolverConfig; non-Sionna local transformations ignore it.

Fingerprint fallback contract

When emitter_meta.extras does NOT contain the "fingerprint_params" slot, transformations that consume per-device priors MUST read defaults from FingerprintParams() (the device-fingerprint Pydantic model instantiated with declared default-prior values, which corresponds to the population-level prior mean). Such transformations MUST emit a transformation_log entry with fallback=True and fallback_reason="missing_fingerprint_params". Contract tests verify this fallback shape.


class rfgen.core.protocols.ChannelRxParams

@dataclass(frozen=True, slots=True)
class ChannelRxParams:
    """Active receiver RF and hardware parameters."""

    center_freq_hz: float
    bandwidth_hz: float
    sample_rate_hz: float
    noise_figure_db: float
    rx_pose: GeometryPose | None = None
    antenna_id: str | None = None
    tag: str | None = None

Kind. Frozen dataclass / value type.

The per-receiver RF and hardware parameters passed through ChannelContext.rx_params. Built by the scene driver from the active ReceiverConfig for each receiver in the scene.

Fields

Field

Type

Required

Default

Description

center_freq_hz

float

yes

Receiver LO frequency in Hz. Used by LinearRXMixer to compute f_lo = emitter.realized_carrier_hz - rx.center_freq_hz.

bandwidth_hz

float

yes

Receiver analog capture bandwidth in Hz. Used by ScipyFIRIFFilter (cutoff design) and LinearLNANoise (kTBF noise power).

sample_rate_hz

float

yes

Receiver ADC sample rate in Hz. Target output rate for ScipyPolyResampler.

noise_figure_db

float

yes

Receiver noise figure in dB. It is carried as receiver context; the shipped LinearLNANoise uses its own configured noise_figure_db parameter for P_n = k_B * T0 * bandwidth_hz * 10**(noise_figure_db/10).

rx_pose

GeometryPose | None

no

None

Typed 3D receiver pose in scene-frame coordinates. It is optional for non-Sionna local transformations; every shipped Sionna backend requires it. The read-only position_m compatibility property returns rx_pose.position_m or None; it is not a constructor field.

antenna_id

str | None

no

None

Antenna-pattern identifier. Consumed by Sionna RT and PHY backends for AoA-dependent gain.

tag

str | None

no

None

Human-readable receiver label. The scene composer sets this to ReceiverConfig.rx_id for the active receiver. It is the runtime receiver label threaded through channel calls; in stored multi-RX scene metadata the full receiver catalog is additionally mirrored under SceneMetadata.extras["receivers"].


class rfgen.core.protocols.BaseChannel

class BaseChannel(ABC):
    transformation: ClassVar[Transformation]
    spectral_effect: ClassVar[SpectralEffect] = SpectralEffect.PRESERVES

    @abstractmethod
    def apply(self, signal: Signal, ctx: ChannelContext) -> Signal: ...

    @abstractmethod
    def schema(self) -> type[BaseModel]: ...

    @classmethod
    def schema_from_init(cls) -> type[BaseModel]: ...

Common ABC. Every concrete transformation inherits from a per-transformation ABC, which itself inherits from BaseChannel. BaseChannel is the type the scene composer dispatches against.

Class attributes

Attribute

Type

Description

transformation

ClassVar[Transformation]

Identifies which of the 15 named transformations this implementation performs. The ABC enforces at class-definition time via __init_subclass__ that every concrete (non-abstract) subclass sets this to a Transformation enum member; missing or incorrectly-typed values raise TypeError.

spectral_effect

SpectralEffect

Stored as ClassVar[SpectralEffect] and defaults to PRESERVES. Set UPDATES for a transformation that describes a changed sample rate, occupied bandwidth, or realized carrier model. Returned metadata is authoritative; fixed-window rendering validates only finite, positive, representable metadata and does not estimate spectrum from I/Q.

Method: apply

@abstractmethod
def apply(self, signal: Signal, ctx: ChannelContext) -> Signal

Applies the transformation to signal and returns a new Signal with updated IQ and metadata. ctx carries the per-call context.

Parameters

Name

Type

Description

signal

Signal

Input IQ plus metadata; shape conventions follow the emitter contract

ctx

ChannelContext

Per-call context: emitter_meta, rx_params, scene_id, sample_idx, rng

Returns

A new Signal with the same IQ shape (unless the transformation changes sample rate) and metadata updated to reflect the applied operation.

Method: schema

@abstractmethod
def schema(self) -> type[BaseModel]

Returns the Pydantic model describing this transformation’s parameters. Subclasses with no parameters return EmptyParams; subclasses with keyword-only __init__ parameters MAY delegate to cls.schema_from_init().

Method: schema_from_init (classmethod, cached)

@classmethod
@functools.cache
def schema_from_init(cls) -> type[BaseModel]

Builds a Pydantic model from the subclass’s __init__ signature. Each keyword argument (excluding self and ctx) becomes a field with the parameter annotation forwarded as the field type and the parameter default forwarded as the field default; parameters without a default become required. Subclasses with no keyword arguments return EmptyParams.

The result is cached per-class via functools.cache, so repeated calls in hot loops are O(1) after the first invocation.

Extension contract

Every subclass of BaseChannel (whether via a per-transformation ABC or directly) MUST satisfy these invariants:

  1. IQ shape. apply() MUST return a Signal whose iq has shape (2, N) where N == signal.iq.shape[-1], with the following exception: BaseResampler implementations MAY return a different N equal to int(round(signal.iq.shape[-1] * ctx.rx_params.sample_rate_hz / signal.metadata.sample_rate_hz)). No other transformation may change the sample count.

  2. No in-place mutation. apply() MUST NOT modify signal.iq or signal.metadata in place. Return a new Signal object; the input must be unchanged after the call.

  3. Metadata update. apply() MUST return authoritative metadata and update every field its transformation affects. SpectralEffect communicates the transformation’s spectral model; it does not cause fixed-window rendering to enforce a frequency-preservation or frequency-update relationship. Fixed-window rendering validates only finite, positive, representable post-transform metadata. Implementations SHOULD append a TransformationLogEntry describing the realised parameters.

  4. Randomness source. All randomness MUST be drawn from ctx.rng. Subclasses MUST NOT use module-level random state, random.random(), or torch.rand() without a generator.

  5. transformation ClassVar. Must equal the Transformation enum member identifying this slot. The ABC enforces this at class definition; missing or incorrectly-typed values raise TypeError.

  6. No side effects. apply() MUST NOT perform I/O, network calls, or mutations of shared state. State that persists across calls (e.g., per-device filter kernels) must be stored as instance attributes, not module globals.

Extension points

Method

Status

Notes

apply()

Must override

Core transformation logic; the primary abstract method

schema()

Must override

Return a Pydantic model for config validation; return EmptyParams if no parameters

transformation

Must set as ClassVar

Set to the matching Transformation member at class definition; the ABC enforces this

Notes

  • Do not inherit directly from BaseChannel to add a new transformation implementation. Inherit from the per-transformation ABC for the relevant slot, such as BasePANonlinearity for PA nonlinearity.

  • Concrete-to-concrete inheritance is forbidden per project conventions.


class rfgen.core.protocols.EmptyParams

class EmptyParams(BaseModel):
    model_config = ConfigDict(frozen=True, extra="forbid")

Sentinel Pydantic model returned from BaseChannel.schema() by transformations that take no parameters. The model has no fields, so EmptyParams.model_fields == {} and EmptyParams().model_dump() == {}.


class rfgen.core.protocols.TransformationLogEntry

class TransformationLogEntry(TypedDict):
    name: str
    group: int
    transformation: int
    params: dict[str, object]
    applied_at_iso: str
    fallback: bool
    fallback_reason: str

TypedDict schema for one entry of SignalMetadata.transformation_log. Channel implementations append one TransformationLogEntry per applied transformation; audit and replay tooling consumes the log key-by-key.

Keys

Key

Type

Description

name

str

Human-readable transformation name (e.g. "PA", "LinearCFO").

group

int

Group integer (1…4).

transformation

int

Transformation integer.

params

dict[str, object]

Pydantic model_dump() of the realised parameter values for this call.

applied_at_iso

str

Fixed ISO-8601 UTC provenance marker "1970-01-01T00:00:00+00:00". It is not a host-clock execution timestamp, so it cannot change a content-addressed record on replay. Every TransformationLogEntry uses this literal; consumers must not infer execution timing from it.

fallback

bool

True iff the transformation read default fingerprint values because emitter_meta.extras did not carry a "fingerprint_params" slot.

fallback_reason

str

Free-form reason string when fallback is True; empty string otherwise.


ChannelChainConfig

ChannelChainConfig = ChannelConfig

Kind. Module-level alias (not a new class).

ChannelChainConfig is an alias of ChannelConfig, the Pydantic config model that already carries the chain field and its cross-entry invariants. The alias gives ChannelPipeline.from_config(ChannelChainConfig) a distinct name without duplicating the config model.

Construct via ChannelConfig directly:

from rfgen.config.channel import ChannelConfig
from rfgen.core.pipeline import ChannelChainConfig  # same object

assert ChannelChainConfig is ChannelConfig  # True

See ChannelConfig on the Config page for the full field list.


class rfgen.core.pipeline.ChannelPipeline

@dataclass(frozen=True, slots=True)
class ChannelPipeline:
    chain: tuple[BaseChannel, ...] = ()
    receiver_stages: tuple[BaseReceiverStage, ...] = ()

    def __init__(
        self,
        transformations: Sequence[BaseChannel] = (),
        receiver_stages: Sequence[BaseReceiverStage] = (),
    ) -> None: ...

    @classmethod
    def from_config(cls, config: ChannelChainConfig) -> ChannelPipeline: ...

Kind. Frozen dataclass. Lives in rfgen.core.pipeline, exported from rfgen.core.pipeline.__all__.

A validated, ordered chain of BaseChannel transformations, plus the receiver stages held beside it. The pipeline carries no DSP code: it holds the BaseChannel instances in canonical pipeline order, the receiver stages in configured order, and exposes the four partition properties consumed by the scene composer. Two mutation paths are blocked by the frozen-dataclass contract:

  1. pipeline.chain[i] = other raises TypeError (tuple is immutable).

  2. pipeline.chain = (...) raises dataclasses.FrozenInstanceError (frozen dataclass).

Attributes

Attribute

Type

Description

chain

tuple[BaseChannel, ...]

Validated transformation chain, in canonical pipeline order. Read-only; set once at construction.

receiver_stages

tuple[BaseReceiverStage, ...]

Validated receiver-frontend stages, in configured order. Read-only; set once at construction.

Constructor

ChannelPipeline(
    transformations: Sequence[BaseChannel] = (),
    receiver_stages: Sequence[BaseReceiverStage] = (),
)

Validates and freezes the transformation chain and the receiver stages.

Parameters

Name

Type

Default

Description

transformations

Sequence[BaseChannel]

()

Ordered sequence of BaseChannel instances. An empty sequence is allowed; the scene composer documents which groups it requires.

receiver_stages

Sequence[BaseReceiverStage]

()

Ordered sequence of receiver-frontend stages, in canonical plane order. These are not chain entries: the composer runs them through the shared receiver frontend chain, in two segments. See Receiver stages.

Raises

ChannelError when any of the following invariants is violated:

Invariant

Trigger

All entries are BaseChannel instances

Any entry fails isinstance(entry, BaseChannel)

Group order is monotonic non-decreasing

An entry’s transformation.group integer is less than the preceding entry’s

Intra-group ordinals are monotonic non-decreasing

Within the same Group, an entry’s transformation.value % 10 is less than the preceding entry’s

At most one Group.CHANNEL entry

More than one propagation transformation appears in the chain

No receiver transformation in the chain

A chain entry pins a transformation for which is_receiver is True; receiver stages go in receiver_stages

All receiver_stages entries are BaseReceiverStage instances

Any receiver entry fails isinstance(entry, BaseReceiverStage)

The error message names the offending pair so the configuration author can correct the chain without reading the validator source.

ValidationError (not ChannelError) is raised when the receiver stages put a capture-plane stage after a hardware-plane one.

Partition properties

The two partition properties split chain by Group using the value // 10 rule. Their concatenation reconstructs list(chain):

tx_transforms + ([propagation] if propagation else []) == list(chain)

Property

Type

Description

tx_transforms

tuple[BaseChannel, ...]

Entries whose transformation.group is Group.TX.

propagation

BaseChannel | None

The single Group.CHANNEL entry, or None if absent.

Receiver entries are not chain entries. They are receiver-frontend stages held alongside the chain, and three attributes expose them:

Attribute

Type

Description

receiver_stages

tuple[BaseReceiverStage, ...]

Every configured receiver stage, in configured order.

capture_plane_stages

tuple[BaseReceiverStage, ...]

The subset whose family plane is ReceiverStagePlane.CAPTURE. These form the receiver chain’s first segment, running before the joint receiver-background injection.

hardware_plane_stages

tuple[BaseReceiverStage, ...]

The subset whose family plane is ReceiverStagePlane.HARDWARE. These form the second segment, running after the injection.

The plane is a stage-family ClassVar, not a configuration field, so no chain and no YAML file can move a stage to the other plane.

Dunder methods

Method

Signature

Behavior

__len__

() -> int

Returns len(self.chain).

__iter__

() -> Iterator[BaseChannel]

Yields each BaseChannel in chain order.

__eq__

(other: object) -> bool

Compares pipelines by (transformation, realised instance attributes) per entry. Two pipelines are equal when they have the same length and each position pairs entries with identical Transformation identifiers and identical sorted current instance attributes from vars(channel). It does not round-trip through schema().model_dump(). Instance identity is not required. Returns NotImplemented when other is not a ChannelPipeline.

__repr__

() -> str

Returns "ChannelPipeline(<n> transformations: <names>)" where <n> is len(chain) and <names> is a comma-separated list of the concrete class names in chain order. An empty chain renders as "ChannelPipeline(0 transformations: )". The contract test asserts that every transformation’s class name appears in the repr.

Method: from_config (classmethod)

@classmethod
def from_config(cls, config: ChannelChainConfig) -> ChannelPipeline

Builds a pipeline by resolving each chain entry through the plugin registry.

Each ChannelChainEntry in config.chain is resolved through an EntryPointRegistry over the rfgen.channels entry-point group. The discovered class is instantiated with the entry’s params mapping as keyword arguments. The resulting instances are then passed through the standard ChannelPipeline validator, so config-time and code-time construction enforce identical invariants.

Parameters

Name

Type

Description

config

ChannelChainConfig

A validated ChannelConfig instance.

Returns

A new ChannelPipeline whose chain contains one BaseChannel instance per non-receiver config.chain entry, and whose receiver_stages contains one BaseReceiverStage per receiver entry. Receiver entries resolve through the same registry and the same selector names; they land beside the chain rather than in it.

Raises

Exception

Trigger

ChannelError

Chain-level validation fails after plugin instantiation.

PluginNotFoundError

An entry’s Transformation does not correspond to a registered rfgen.channels plugin.

Validation invariants summary

Invariant

Error type

Error details

All entries are BaseChannel

ChannelError

Names the offending entry index and its actual type.

Group order is monotonic non-decreasing

ChannelError

Names the offending pair and their group labels.

Intra-group ordinals are monotonic non-decreasing

ChannelError

Names the offending pair, transformation names, and values.

At most one Group.CHANNEL entry

ChannelError

Lists all Group.CHANNEL class names found in the chain.

Minimal example

from rfgen.core.pipeline import ChannelPipeline
from rfgen.hardware.tx import LinearDACQuantizer, LinearCFO
from rfgen.engine.propagation_generic import AWGNChannel
from rfgen.receiver.stages.frequency import MixerStage
from rfgen.receiver.stages.filtering import IFFilterStage
from rfgen.receiver.stages.conversion import ADCQuantizerStage

pipeline = ChannelPipeline(
    transformations=[
        LinearDACQuantizer(),
        LinearCFO(f_offset_hz=100.0),
        AWGNChannel(snr_db=20.0),
    ],
    receiver_stages=[
        MixerStage(),
        IFFilterStage(),
        ADCQuantizerStage(),
    ],
)

print(len(pipeline))                    # 3, the chain only
print(pipeline.tx_transforms)           # (LinearDACQuantizer, LinearCFO)
print(pipeline.propagation)             # AWGNChannel instance
print(pipeline.receiver_stages)         # (MixerStage, IFFilterStage, ADCQuantizerStage)
print(pipeline.capture_plane_stages)    # (MixerStage, IFFilterStage)
print(pipeline.hardware_plane_stages)   # (ADCQuantizerStage,)
print(repr(pipeline))
# ChannelPipeline(6 transformations: LinearDACQuantizer, LinearCFO, AWGNChannel, MixerStage, IFFilterStage, ADCQuantizerStage)

__len__ counts chain entries only; __repr__ counts both.

Passing a receiver stage in transformations raises: receiver entries belong in receiver_stages.

From config:

ChannelChainEntry.transformation is an int field backed by the Transformation IntEnum, so a dict passed to model_validate supplies the member or its ordinal, not the member name. YAML channel-profile files name the member instead (transformation: DAC); the profile loader resolves that name to the member before validation.

from rfgen.core.pipeline import ChannelPipeline, ChannelChainConfig
from rfgen.core.enums import Transformation
from rfgen.config.channel import ChannelConfig

cfg = ChannelConfig.model_validate({
    "chain": [
        {"transformation": Transformation.DAC, "params": {}},
        {"transformation": Transformation.PROPAGATION, "params": {"snr_db": 20.0}},
        {"transformation": Transformation.ADC, "params": {}},
    ]
})
pipeline = ChannelPipeline.from_config(cfg)
# pipeline.chain           -> (LinearDACQuantizer, AWGNChannel)
# pipeline.receiver_stages -> (ADCQuantizerStage,)

Per-transformation ABCs

The per-transformation ABCs live alongside their concretes in the implementation modules listed at the top of this page. Each ABC sets its transformation ClassVar to the matching Transformation member and leaves its transform method and schema abstract for concrete subclasses to override: apply for the BaseChannel families, apply_iq for the receiver stage families.

The TX-side ABCs and their concretes moved to their own package and page; see Hardware for the five Group.TX families.

Channel propagation (rfgen.engine.propagation_generic, rfgen.engine.propagation_sionna, rfgen.engine.propagation_sionna_rt)

ABC

Transformation

Concretes shipped

BaseChannelPropagation

PROPAGATION = 21

AWGNChannel (default, pure-torch); SionnaRT, SionnaUMa, SionnaUMi, SionnaRMa, SionnaTDL, SionnaCDL, RayleighBlockFading, SionnaFlatFading, SionnaCIRDataset (lazy-import skeletons gated behind the rfgen[sionna] extra)

See Engine for per-class signatures.

Receiver capture and hardware planes (rfgen.receiver.stages)

ABC

Transformation

Plane

Concretes shipped

BaseLOFrequencyErrorStage

RX_LO_FREQUENCY_ERROR = 30

CAPTURE

LOFrequencyErrorStage

BaseMixerStage

RX_MIXER = 31

CAPTURE

MixerStage

BaseIFFilterStage

IF_FILTER = 32

CAPTURE

IFFilterStage

BaseResamplerStage

RESAMPLER = 33

CAPTURE

PolyphaseResamplerStage, SampleRateOffsetStage

BaseThermalNoiseStage

LNA_NOISE = 34

CAPTURE

ThermalNoiseStage (pure-torch, no Friis cascade)

BaseADCQuantizationStage

ADC = 41

HARDWARE

ADCQuantizerStage (inline mid-tread uniform quantizer)

BaseRXPhaseNoiseStage

RX_PHASE_NOISE = 42

HARDWARE

RXPhaseNoiseStage (shared rfgen.hardware._leeson synthesizer)

BaseIQImbalanceStage

RX_IQ_IMB = 43

HARDWARE

IQImbalanceStage (inline differential I/Q model)

BaseAGCStage

AGC = 44

HARDWARE

AGCStage (custom torch loop; documented Library-First gap)

These are receiver stages rather than BaseChannel implementations: each transforms interleaved IQ through apply_iq, and the shared rfgen.receiver.frontend.ReceiverFrontendChain owns the container. The ten registered selector names and the YAML configuration surface are unchanged. See Receiver stages for the per-class signatures.

TorchSig benchmark augmentation is outside ChannelPipeline; select the optional classification adapter documented in TorchSig integration.


See Also

  • Concepts / Channels: mental model, boundaries, and data flow for the 4-group pipeline.

  • ChannelPipeline: validated chain container; constructor, partition properties, and from_config factory.

  • Group: group membership enum.

  • Transformation: 15-member enum pinned on every concrete implementation. Fourteen transformations have dedicated ABCs; propagation uses BaseChannel through BaseChannelPropagation.

  • Reference / API / Enums: Group and Transformation entries.

  • Device Fingerprint: FingerprintParams and DeviceRegistry consumed by TX-side and RX-hardware transformations.

  • Engine: per-class signatures for AWGNChannel and the Sionna-backed propagation concretes.

  • Receiver stages: physical RX-capture and RX-hardware signatures.


Anchor aliases (cross-reference compatibility)

The anchors below preserve compatibility cross-reference targets. They resolve to the current per-transformation ABC index or its owning API page.

Group and Transformation define the current channel slots. The class index lists the per-transformation ABCs, five on the TX side and nine on the receiver; propagation is the remaining transformation slot.

Compatibility links for removed concrete names resolve to the current per-transformation contracts in TX Impairments, Engine, and Receiver stages. TorchSigImpairments is not a channel contract; use the optional classification augmentation boundary instead.

ChannelPipeline is a frozen dataclass in rfgen.core.pipeline with no ChainKind flag. ChannelChainConfig is an alias for ChannelConfig.

Compatibility names with the ...Channel suffix (SionnaUMaChannel, SionnaUMiChannel, SionnaRMaChannel, SionnaTDLChannel, SionnaCDLChannel, SionnaRTChannel) resolve to SionnaRT, SionnaUMa, SionnaUMi, SionnaRMa, SionnaTDL, and SionnaCDL. See Engine for the per-class signatures.