Architecture

Warning

Pre-implementation. This page describes proposed contracts. Class signatures, parameter types, config field names, and behavior are subject to change before code lands. Once implementation exists, content here will be regenerated from docstrings or sourced from running tests.

The framework is six swappable layers, emitters, channels, scenes, labels, annotations, storage, wrapped by one configuration system. The framework owns the contracts between layers and delegates specialized work to mature libraries, backends, and adapters wherever practical.

Minimal Example

preset names a shipped Hydra recipe; see Scenario Presets for the full list. Each key in overrides is a Hydra dotted path targeting a nested config field (e.g. run.num_samples sets cfg.run.num_samples); the dot notation is the same convention Hydra uses on its CLI.

from rfgen.orchestration.local import LocalRunner

cfg = LocalRunner.build_config(
    preset="narrowband_classifier_baseline",
    overrides={"run.num_samples": 5, "storage.path": "./out/demo"},
)
runner = LocalRunner(cfg)
records = list(runner.run())         # returns list[Record]

# Each record has IQ, labels, and metadata
record = records[0]
print(record.iq.shape)               # torch.Size([2, 1_000_000]) -> (n_rx_antennas=2, n_samples)
print(record.scene.scene_geometry_hash)  # "sha256:…"  (record.scene is SceneMetadata)
print(len(record.bboxes))            # number of emitter bounding boxes (record.bboxes is tuple[BBox, ...])

Pipeline

Generation runs in two phases. Phase 1 turns a config into IQ, metadata, and structured labels deterministically. Phase 2 attaches inference-generated text annotations asynchronously against already-stored records.

The Phase 1 channel chain is split into four ordered groups, each named for the operation family it owns. TX impairments model per-emitter transmitter-side effects: DAC quantization, power-amplifier (PA) nonlinearity, phase noise, IQ imbalance, and carrier frequency offset (CFO). Channel propagation is the over-the-air propagation step delegated to Sionna. RX capture models the receiver’s analog front-end as the signals are summed: RF-to-baseband mixing, IF filtering, fractional resampling to the receiver sample rate, summation across emitters, and low-noise-amplifier (LNA) thermal noise. RX hardware models the digital-side receiver effects: ADC quantization, receiver phase noise, receiver IQ imbalance, and automatic gain control (AGC). Each acronym is defined in the Glossary; the per-group transformations and their backends are catalogued in Concepts / Channels.

        flowchart TD
    cfg["<b>CONFIGURATION</b><br/>Hydra + Pydantic<br/>emitter · channel · scene<br/>label · annotator · storage"]

    subgraph p1 ["Phase 1"]
        direction TB
        subgraph scene ["SCENE: planner + driver"]
            direction TB
            plan["<b>plan</b><br/>assign f_c, start_sample, tx_pos<br/>per emitter slot"]
            loop["<b>for each emitter slot (pre-sum)</b><br/>EMITTER → TX impairments<br/>(DAC, PA, phase noise, IQ imb., CFO)<br/>→ channel propagation (Sionna)"]
            sum["<b>per receiver (post-sum)</b><br/>RX capture (mix, filter,<br/>resample, sum, LNA noise)<br/>→ RX hardware (ADC, AGC, etc.)<br/>→ Signal w/ component Signals"]
            plan --> loop --> sum
        end
        label["<b>LABEL</b><br/>bbox + segmentation<br/>+ per-emitter metadata"]
        scene --> label
    end

    storage[("<b>STORAGE</b><br/>Zarr + WebDataset")]

    annotation["<b>ANNOTATION</b><br/>Templater → inference client → Verifier<br/>(model IDs pinned outside concepts)"]

    cfg --> p1
    label --> storage
    storage -. append .-> annotation

    classDef phase1 fill:#e8f3ff,stroke:#3068a8,color:#1c3a66;
    classDef phase2 fill:#fff1e8,stroke:#a86230,color:#663d1c;
    classDef store  fill:#f0f0f0,stroke:#666,color:#222;
    class p1,scene,label phase1
    class annotation phase2
    class storage store
    

Phase 1 is deterministic and compute-bound: the same resolved config, global seed, shard ID, sample index, backend versions, and device determinism settings should reproduce the same IQ and metadata. Phase 2 is cost-bound, async, and separately resumable: re-annotating an existing dataset never requires re-generating IQ.

Config Distributions And Realized Records

In this page “realized” is the technical antonym of “the prior”: it is the concrete value the random draw produced for one record (the realized SNR, the realized emitter count, the realized bounding-box coordinates), as opposed to the distribution the config declared. The generator is stochastic by design, but reproducible. A fully-merged GenerationConfig (the post-defaults-merge, post-validation config; “resolved” elsewhere in the docs) and its scenario preset usually describe a dataset distribution, not one exact recording. A run with num_samples=1000 creates 1000 concrete Record objects by drawing from that distribution with deterministic seeds.

        flowchart LR
    cfg["GenerationConfig<br/>scenario preset<br/>fixed values + distributions"]
    seed["Seed schedule<br/>global_seed<br/>shard_id<br/>sample_idx"]
    composer["Scene composer<br/>realizes one scene plan"]
    record1["Record 000001<br/>3 emitters<br/>Wi-Fi + BLE + radar"]
    record2["Record 000002<br/>1 emitter<br/>LoRa"]
    record3["Record 000003<br/>5 emitters<br/>dense ISM mix<br/>(2.4/5/24 GHz Wi-Fi/BLE)"]
    audit["Dataset audit<br/>requested vs realized<br/>class balance, density, SNR"]

    cfg --> composer
    seed --> composer
    composer --> record1
    composer --> record2
    composer --> record3
    record1 --> audit
    record2 --> audit
    record3 --> audit
    

Some config fields are fixed values. Others are ranges, weights, or named distributions:

Config intent

Example

Realized record

Fixed value

density.mode: fixed, count: 3

Every scene has three emitter slots.

Range

count_min: 2, count_max: 6

Each scene records the actual emitter count drawn for that scene.

Weighted choice

Wi-Fi weight 0.7, BLE weight 0.2, radar weight 0.1

Each emitter slot records the chosen class and parameters.

Continuous prior

Signal-to-noise ratio (SNR) range [-5, 30] dB

Metadata stores the target and realized SNR after the channel chain (the LNA-noise transformation in Group.RX_CAPTURE, modeled by BaseLNANoise, sets the realized noise floor; see Concepts / Channels for the group composition).

Placement strategy

iid_uniform, ism_realistic, forced_overlap (frequency); see Scene Composition Algorithm for the full set

Metadata stores actual start samples, frequency offsets, and overlap outcomes.

This distinction is central to the architecture:

  • Config describes what population of examples the dataset should contain.

  • The scene composer realizes one concrete scene at a time from that config.

  • Labels and storage persist realized metadata, not just the requested distribution.

  • Audits compare requested distributions against realized dataset statistics.

Fixed datasets are still supported: set fixed counts, fixed classes, fixed parameters, and deterministic placement. Most useful training datasets mix fixed structure with stochastic nuisance variables, such as fixed waveform families but random timing, frequency offset, SNR, payload, and channel realization.

Six Layers

Layer

Input

Output

Contract

Emitters

emitter config and seeded RNG

clean baseband Signal (a complex-valued IQ waveform centered at zero frequency, before any channel impairment or carrier shift; see Glossary) plus per-emitter metadata

BaseEmitter

Channels

component Signal or summed receiver signal, depending on stage

transformed Signal plus channel metadata

BaseChannel

Scenes

emitter pool, channel chain, scene config

receiver IQ plus preserved component Signal objects

BaseSceneComposer

Labels

composed IQ, component metadata, scene metadata

Record with structured labels

BaseLabeler

Annotations

stored Record metadata and labels

append-only text fields

BaseAnnotator

Storage

Record

persisted records and optional asset blobs

BaseStore

Full API signatures live in Reference / API. The data objects that move between layers are summarized in Core Types.

Scene layer: placement strategy ABCs

The Scenes layer exposes three pluggable ABCs:

Substrate

ABC

Notes

Composer

BaseSceneComposer

Primary scenes ABC; orchestrates emitter selection, placement, channel routing, and receiver summation

Time placement

BaseTimePlacement

Strategy ABC for per-emitter start-sample scheduling (e.g. radar pulse trains, Wi-Fi beacons, frequency-hopping schedules)

Frequency placement

BaseFrequencyPlacement

Strategy ABC for per-emitter carrier frequency selection

BaseTimePlacement and BaseFrequencyPlacement are pluggable extension points. Custom strategies subclass the relevant ABC and are selected through validated configuration.

Annotations layer: substrate ABCs

The Annotations layer exposes three ABCs with distinct roles:

Substrate

ABC

Notes

Annotator

BaseAnnotator

Primary annotations ABC; owns templating, validation, and persistence decisions

Inference client

BaseInferenceClient

Provider-agnostic inference call surface injected into annotators

Batch orchestrator

BaseBatchAnnotationOrchestrator

Phase 2 batch submission, polling, and result fetching

BaseInferenceClient and BaseBatchAnnotationOrchestrator are not in the BaseAnnotator inheritance hierarchy. They are injected dependencies that allow swapping inference providers or batch backends without changing annotator code.

Cross-Cutting Contracts

The contracts below are framework-wide invariants every layer respects, regardless of which implementation is plugged in. They are what keeps the six layers composable: a backend swap stays mechanical only as long as these hold.

Contract

Rule

Detail

Data model

Layers pass Signal, component metadata, and Record, not naked tensors or ad hoc dicts.

Core Types

Asset storage

Large Sionna RT geometry/material assets are content-addressed and stored once.

Records, Receivers, and Assets

Determinism

(global_seed, shard_id, sample_idx) identifies deterministic Phase 1 output. shard_id is the content-addressed identifier of a shard (the unit of parallel work), derived from a hash of the materialized config plus a shard index; see Reference / Determinism.

Reference / Determinism

Configuration

Behavior changes through validated config, not hidden feature flags.

Reference / Config Schema

Streaming

The public contract is record/Signal iteration; batching is an implementation optimization.

Reference / Project Layout

ABC pluggability

Every layer has one primary abstract base class (ABC). Per-slot ABCs are permitted under the layer ABC where multiple implementations exist (e.g. the channel layer’s 14 per-transformation ABCs sitting under BaseChannel). Concrete classes inherit from ABCs only; concrete-to-concrete inheritance is forbidden (an ABC may extend another ABC, but a concrete class never extends another concrete class). See Background / Design Decisions.

Two-ABC layers

Some layers require two ABCs to cover different lifecycles or roles:

Consumer Access

The six layers above produce and persist records. Downstream training pipelines consume them through a separate consumer-side toolkit (sampler, collator, PyTorch Dataset adapter) that wraps the storage layer’s read API. See Consumer Access for the consumer-side concept page and the ABCs it introduces.

Cross-Cutting Conventions

The topics below also span every layer, but are shared frames of reference, not rules. The contracts above operate inside the conventions below.

Convention

What it covers

Detail

Frequency and time frames

Each emitter outputs IQ samples as if it were the only signal at frequency 0 (its own local baseband); the scene composer shifts each emitter to its scene-level carrier frequency and start time before summation.

Coordinate Systems

Multi-RX records

Default output is one record per receiver; joint tensors are opt-in.

Records, Receivers, and Assets

See Also