rfgen.config¶
Pydantic v2 models that validate every config the framework consumes. Hydra
composes YAML trees and CLI overrides into a single dict; rfgen.config
turns that dict into typed, validated objects before a single sample is
generated. A failed config never costs compute.
Warning
Pre-implementation. Class signatures, field names, defaults, and
validation rules are proposals. Once code lands, this page will be
regenerated from docstrings via sphinx.ext.autodoc. The shape below
matches what autodoc emits so the swap is mechanical.
Closed-set fields below use StrEnum
members from rfgen.enums, never plain Literal[...] or str.
YAML still reads mode: poisson; Pydantic deserializes to the enum member at
validation time. Open-set plugin selectors such as ExecutorConfig.name stay
typed str because they resolve through the plugin registry at instantiation
time, not at validation. See
background/design-decisions § Closed enums use StrEnum.
Sub-package layout¶
rfgen.config ships as a sub-package so per-area validators live next to the
schemas they validate:
Module |
Top-level models |
|---|---|
|
|
|
SceneConfig, DensityConfig, RxArrayConfig, ReceiverConfig, MultiRXConfig, SceneAssetsConfig, SceneGeometryConfig |
|
|
|
|
|
|
|
|
|
|
|
AnnotatorConfig, LLMConfig, AnnotationOrchestratorConfig, and the five typed batch-resource models |
|
|
|
|
|
|
|
|
|
GenerationConfig (single composition root) |
rfgen.config.__init__ re-exports every top-level model plus the two canonical
entry-point helpers below. A documented
from rfgen.config import GenerationConfig, RunConfig, ... works regardless
of sub-module path.
Module summary¶
from rfgen.config import GenerationConfig, from_hydra
from omegaconf import OmegaConf
cfg = from_hydra(OmegaConf.load("configs/config.yaml"))
print(cfg.scene.sample_rate_hz, cfg.scene.density.max_emitters)
print(len(cfg.channel.chain), cfg.executor.name)
Top-level GenerationConfig
composes the layered groups (emitter zoo, channel, scene, placement, label,
annotator, storage) plus executor, optional credentials, and run metadata.
Open plugin selection uses a name field that resolves through
EntryPointRegistry. The
channel slice is the exception in the shipped Layer 9 code path: live channel
behavior is selected by ChannelConfig.chain, while the legacy top-level
ChannelConfig.name / params / snr_db_range fields are compatibility
placeholders pinned to their defaults until a runtime consumer exists.
Class index¶
Class |
Kind |
Notes |
|---|---|---|
config (root) |
Top-level config composing every layer |
|
config |
Run metadata: |
|
config |
Signal grid, density, geometry, multi-RX layout, assets |
|
config |
Per-scene emitter count distribution |
|
config |
Receiver-array preset (Rx count, array geometry, spacing) |
|
config |
One explicit receiver in |
|
config |
Geometry XOR explicit receiver list |
|
config |
Scene-asset URIs (geometry blob, materials, antennas) |
|
config |
Scene-geometry backend selection plus overlap policy |
|
config |
Grid-source selection for the |
|
config |
Pool of emitters and per-class sampling weights |
|
config |
One pool entry: family, classes, params, weight, fingerprint |
|
config |
Per-device CFO/SFO/IQ-imbalance/phase-noise/PA priors |
|
config |
Channel chain selection, ordered transformation list, SNR range |
|
config |
One transformation entry in |
|
config |
Labeler and per-task label parameters |
|
config |
One entry in |
|
config |
Inference templating layer config |
|
config |
One inference endpoint (provider, model, sampling params) |
|
config |
Phase-2 batch annotation backend and provider resources; separate from |
|
config |
Dataset-audit thresholds and bounded aggregation limits |
|
config |
Store backend, output URI (required), on-disk layout |
|
config |
Distributed executor selection (open-set name) |
|
config |
Cloud credential provider selection |
|
config |
Reference-host metadata for wall-clock and cold-import tests |
For the full normative schema (defaults, validators, YAML examples), see Config Schema.
Canonical entry points¶
Two module-level helpers cover every documented validation path. CLI,
dataset-consumer, and validation-and-audit consume one of these two; raw
GenerationConfig.model_validate is reserved for tests and internal use.
from_hydra¶
from rfgen.config import from_hydra, GenerationConfig
from omegaconf import DictConfig
def from_hydra(cfg: DictConfig | dict[str, object]) -> GenerationConfig: ...
Convert a Hydra/OmegaConf node into a validated GenerationConfig. The bridge:
OmegaConf.is_config(cfg)is true →OmegaConf.to_container(cfg, resolve=True)resolves interpolations into a plain dict.cfgis a plaindict→ used as-is (symmetry withvalidate_config()).Anything else (including
ListConfig, scalar nodes, arbitrary objects) raises ConfigError withcontext={"input_type": type(cfg).__name__}.
After resolution the top-level container must be a dict. If it is not (for example an OmegaConf list), the helper raises:
ConfigError: Hydra config must be a dict at the top level; got list
with context={"top_level_type": "list"}. The validated dict is then routed
through validate_config(), so Pydantic failures surface as
ConfigError.
validate_config¶
from rfgen.config import validate_config
def validate_config(data: object) -> GenerationConfig: ...
Validate a plain dict and surface failures as ConfigError. Implementation:
try:
return GenerationConfig.model_validate(data)
except ValidationError as exc:
raise ConfigError(
message=str(exc),
context={"errors": json.loads(exc.json())},
) from exc
The original pydantic.ValidationError is preserved as the cause. The
context payload is JSON-serializable by construction: ValidationError.json()
already coerces non-serializable ctx values (raw ValueError instances
from custom validators) to strings, so the round-trip through json.loads
yields a list[dict] matching Pydantic’s documented error-payload shape.
class rfgen.config.GenerationConfig¶
class GenerationConfig(BaseModel):
emitter_zoo: EmitterZooConfig = Field(default_factory=EmitterZooConfig)
channel: ChannelConfig = Field(default_factory=ChannelConfig)
scene: SceneConfig = Field(default_factory=SceneConfig)
placement: PlacementConfig = Field(default_factory=PlacementConfig)
label: LabelConfig = Field(default_factory=LabelConfig)
annotator: AnnotatorConfig | None = None
storage: StorageConfig # required
executor: ExecutorConfig = Field(default_factory=ExecutorConfig)
credentials: CredentialsConfig | None = None
run: RunConfig # required
Top-level config. Composes the emitter zoo, channel, scene, placement, label,
annotator, and storage groups plus executor, optional credentials, and run
metadata. Built by model_validate from the Hydra-resolved dict (typically
through from_hydra()). Plugin-name typos
surface as
PluginNotFoundError at
plugin instantiation, not at validation.
model_config = ConfigDict(extra="forbid", validate_assignment=True). Unknown
keys are rejected (they are the most common source of silent config drift).
No-override build error¶
GenerationConfig() (no overrides, no Hydra input) raises
pydantic.ValidationError because exactly two leaf fields have no defensible
default:
run.run_id(no defensible default for a stable run identifier).storage.path(no defensible default for an output URI).
A mode="before" model validator on GenerationConfig seeds empty run and
storage sub-dicts when the input omits them entirely, so Pydantic descends
into the nested schemas and the resulting error names exactly those two
missing leaves rather than naming the parent slots. The contract test asserts:
import pytest
from pydantic import ValidationError
from rfgen.config import GenerationConfig
with pytest.raises(ValidationError) as exc:
GenerationConfig()
missing = {".".join(str(p) for p in err["loc"]) for err in exc.value.errors()
if err["type"] == "missing"}
assert missing == {"run.run_id", "storage.path"}
Every other top-level slot has a default_factory or accepts None, so it
cannot contribute to a missing-leaf report.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
no |
|
Pool of emitters with per-family sampling weights. The “non-empty families” check is deferred to the scene composer (Layer 4). |
|
|
no |
|
Channel chain selection. Resolved against the registry to a BaseChannel. |
|
|
no |
|
IQ tensor shape, density, placement strategies, multi-RX layout, geometry. |
|
|
no |
|
Grid-source selection for the |
|
|
no |
|
Primary labeler and per-task label parameters. |
|
|
AnnotatorConfig | None |
no |
|
Inference-grounded annotation block. When |
|
yes |
– |
Storage backend; |
|
|
no |
|
Distributed executor selection (open-set |
|
|
CredentialsConfig | None |
no |
|
Cloud credential provider; SDKs fall back to default chains when |
|
yes |
– |
Run-level metadata; |
Notes¶
Single validation point. Open plugin names such as
executor.nameare strings at config time; they resolve against the entry-point registry at instantiation, not atmodel_validate.Phase 1 and Phase 2 use separate schemas. GenerationConfig remains the Phase 1 root and rejects a top-level
annotation_orchestratorkey. The shippedrfgen annotatecommand composes AnnotationOrchestratorConfig separately for synchronous local or managed-provider Phase 2 execution.Output path is on storage, not run. The output directory is
storage.path;rundoes not have anoutput_pathfield.Credentials are optional. Most managed-compute environments inject ambient credentials.
class rfgen.config.RunConfig¶
class RunConfig(BaseModel):
run_id: str = Field(min_length=1) # required, no default
num_samples: int = Field(gt=0, default=10_000)
shard_size: int = Field(gt=0, default=1_000)
seed: int = 42
shard_failure_threshold: float = Field(gt=0.0, le=1.0, default=1.0)
fail_fast: StrictBool = False
Run metadata block. run_id has no default and is one of the two no-default
leaves driving the no-override-build error contract on
GenerationConfig. The output
directory is StorageConfig.path,
not a field of RunConfig. A @model_validator(mode="after") warns (does
not raise) when num_samples % shard_size != 0; the last shard is allowed
to be partial.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
str ( |
yes |
– |
Stable, human-meaningful identifier for the run. No defensible default. The contract test for |
|
int (> 0) |
no |
|
Total number of Record objects to generate. |
|
int (> 0) |
no |
|
Records per shard. The total shard count is |
|
int |
no |
|
Master RNG seed. Per-shard and per-sample seeds derive deterministically from this value plus the shard index. |
|
float ( |
no |
|
Fraction |
|
StrictBool |
no |
|
When |
class rfgen.config.SceneConfig¶
class SceneConfig(BaseModel):
sample_rate_hz: float = Field(gt=0, default=20_000_000.0)
duration_s: float = Field(gt=0, default=0.020)
bandwidth_hz: float = Field(gt=0, default=10_000_000.0)
center_hz: float = 0.0
density: DensityConfig = Field(default_factory=DensityConfig)
time_placement: TimePlacementStrategy = TimePlacementStrategy.IID_UNIFORM
time_placement_params: dict[str, object] = Field(default_factory=dict)
frequency_placement: FrequencyPlacementStrategy = FrequencyPlacementStrategy.IID_UNIFORM
frequency_placement_params: dict[str, object] = Field(default_factory=dict)
rx_array: RxArrayConfig = Field(default_factory=RxArrayConfig)
multi_rx: MultiRXConfig | None = None
channel_application: ChannelApplicationMode = ChannelApplicationMode.SCENE
geometry: SceneGeometryConfig = Field(default_factory=SceneGeometryConfig)
assets: SceneAssetsConfig = Field(default_factory=SceneAssetsConfig)
Top-level scene config. The Nyquist invariant
(sample_rate_hz >= 2 * bandwidth_hz) plus the SionnaRT typed-geometry
invariants are enforced at validation time, before any compute runs.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
float (> 0) |
no |
|
Sample rate of the synthesized IQ tensor in Hz. |
|
float (> 0) |
no |
|
Duration of each scene in seconds. Output samples = |
|
float (> 0) |
no |
|
Total occupied bandwidth. Must satisfy |
|
float |
no |
|
Center frequency in Hz; |
|
no |
|
Per-scene emitter-count distribution. |
|
|
no |
|
Time-domain placement strategy (closed enum). Event-timed emitters (radar PRI jitter, frequency-hop schedules, beacon cadence) are requested by selecting an |
|
|
|
no |
|
Free-form per-strategy kwargs forwarded to the time placement strategy constructor. |
|
no |
|
Carrier-frequency placement strategy (closed enum). |
|
|
|
no |
|
Free-form per-strategy kwargs forwarded to the frequency placement strategy constructor. |
|
no |
|
Receiver-array preset (used when |
|
|
MultiRXConfig | None |
no |
|
Optional explicit multi-RX layout (geometry XOR receivers list). |
|
no |
|
Whether channels apply per-scene or per-emitter. |
|
|
no |
|
Scene-geometry backend selection plus overlap policy. |
|
|
no |
|
Scene-asset pointers. |
Validators¶
Nyquist.
@model_validator(mode="after")enforcessample_rate_hz >= 2 * bandwidth_hz. Violations raiseValidationErrorwith a “Nyquist requires at least 2x oversampling” message.SionnaRT requires assets. A second
@model_validator(mode="after")enforces thatgeometry.backend == SceneGeometryBackend.SIONNA_RTimplies eitherassets.scene_geometry_reforassets.scene_geometry_uriis set. The validator synthesises aValidationErrorwithloc=("assets", "scene_geometry_uri")so the error path names the offending leaf rather than the root. Other backends (NONE,MITSUBA) do not require scene-geometry assets.SionnaRT validates typed geometry inputs after normalization. A companion
@model_validator(mode="after")rejects two RT-only misconfigurations before the composer runs: hidden TX-pose surrogates undertime_placement_paramsorfrequency_placement_params(loc=("tx_pose",)), andassets.scene_geometry_refvalues whosekindis not a scene asset (loc=("assets", "scene_geometry_ref", "kind")). For explicitmulti_rx.receivers[i]entries, ordinary YAML that omitsrx_poseis normalized first by ReceiverConfig from legacyposition_mand quaternionorientation; the RT validator only seesrx_pose is Noneif a caller bypasses or defeats that normalization.
Example¶
from rfgen.config import SceneConfig
cfg = SceneConfig.model_validate({
"sample_rate_hz": 20_000_000,
"duration_s": 0.020,
"bandwidth_hz": 10_000_000,
"density": {"mode": "uniform", "min_emitters": 1, "max_emitters": 10},
"rx_array": {"num_rx": 4, "array": "ula_4", "spacing_lambda": 0.5},
})
assert cfg.sample_rate_hz == 20_000_000
assert cfg.density.max_emitters == 10
class rfgen.config.DensityConfig¶
class DensityConfig(BaseModel):
mode: DensityMode = DensityMode.RANGE
min_emitters: int = Field(ge=0, default=1)
max_emitters: int = Field(ge=1, default=10)
poisson_rate: float | None = None # mean emitter count, used directly (not per-MHz); required when mode is DensityMode.POISSON
Per-scene emitter-count distribution. A @model_validator(mode="after") enforces
max_emitters >= min_emitters and requires poisson_rate when
mode is DensityMode.POISSON.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
no |
|
Distribution for the per-scene emitter count. |
|
|
int (≥ 0) |
no |
|
Lower bound on emitter count (inclusive). |
|
int (≥ 1) |
no |
|
Upper bound on emitter count (inclusive); must be ≥ |
|
float | None |
no |
|
Mean emitter count per scene draw, used directly as the mean of the Poisson distribution (before clipping to |
class rfgen.config.RxArrayConfig¶
class RxArrayConfig(BaseModel):
num_rx: int = Field(ge=1, default=1)
array: str = "single"
spacing_lambda: float = 0.5
Receiver-array geometry preset. Used when
MultiRXConfig does not declare an
explicit receivers list. The two are mutually exclusive; see
MultiRXConfig.
array is open-string (not a closed enum) at this layer because concrete
array presets register through the receiver-frontend module; the closed-set
geometry tags live in MultiRXConfig.geometry.
The selectors the composer resolves today (case-insensitive, leading/trailing whitespace stripped) are:
|
Meaning |
|
|---|---|---|
|
Single receiver (default). |
Requires |
|
Uniform linear array; count comes from |
Uses |
|
Uniform linear array of |
|
|
Parsed but always raises |
n/a |
|
Parsed but always raises |
n/a |
Any other value raises SceneError (“unknown rx_array.array selector”). Note
that ura/arbitrary are accepted by the selector parse but rejected one
step later when the geometry is expanded, because the shipped config surface
has no fields to describe them; use an explicit
MultiRXConfig.receivers list for
those layouts. This selector path only applies when multi_rx.geometry is
absent.
class rfgen.config.ReceiverConfig¶
class ReceiverConfig(BaseModel):
rx_id: str = Field(min_length=1)
position_m: tuple[float, float, float] = (0.0, 0.0, 0.0)
orientation: tuple[float, float, float, float] = (1.0, 0.0, 0.0, 0.0)
rx_pose: GeometryPoseConfig | None = None
antenna_id: str | None = None
center_freq_hz: float | None = None
bandwidth_hz: float | None = None
sample_rate_hz: float | None = None
noise_figure_db: float = 0.0
One explicit receiver in
MultiRXConfig.receivers. Use
ReceiverConfig when the geometry presets are not expressive enough (mixed
arrays, distributed sensing, asymmetric noise figures).
Validators. A @model_validator(mode="after") auto-derives rx_pose:
when the caller omits rx_pose (the common case for ordinary YAML), it is
back-filled from the legacy position_m and quaternion orientation fields
(the quaternion is converted to Euler angles). Passing an explicit typed
rx_pose: GeometryPoseConfig bypasses that legacy quaternion normalization
and uses the given pose directly.
Receiver label threading. rx_id is the stable configured receiver label.
The scene composer copies it into
ChannelRxParams.tag for the
active receiver on every channel call, and in multi-RX scenes mirrors the
full configured receiver list into SceneMetadata.extras["receivers"][i]["rx_id"]
for record-side lookup.
class rfgen.config.MultiRXConfig¶
class MultiRXConfig(BaseModel):
geometry: ArrayGeometry | None = None
receivers: list[ReceiverConfig] = Field(default_factory=list)
Multi-receiver layout: either a closed-enum geometry preset OR an explicit
list of ReceiverConfig entries.
The two are mutually exclusive. A @model_validator(mode="after") raises
ValidationError when geometry is not None AND receivers is non-empty.
This is one of the seven enumerated invalid combinations.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
ArrayGeometry | None |
no |
|
Closed-enum array preset (e.g. |
|
|
no |
|
Explicit per-receiver list. Mutually exclusive with |
class rfgen.config.SceneAssetsConfig¶
class SceneAssetsConfig(BaseModel):
scene_geometry_uri: str | None = None
scene_geometry_ref: GeometryAssetRef | None = None
material_db_uri: str | None = None
material_db_ref: GeometryAssetRef | None = None
antenna_pattern_uri: str | None = None
antenna_pattern_refs: tuple[GeometryAssetRef, ...] = ()
Scene-asset pointers (Mitsuba scene blob, material database, antenna patterns).
The typed *_ref fields are the preferred content-addressed surface. The
legacy *_uri fields remain valid and are converted by the scene composer into
compatibility GeometryAssetRef entries so RT provenance,
ChannelContext.geometry_asset_refs, and scene-level geometry hashes remain
populated end to end. For URI-only configs the composer synthesizes
deterministic compatibility hashes from (asset kind, normalized URI), so
SceneMetadata.scene_geometry_hash and sibling *_hash fields are populated
even without typed refs. Typed refs remain the authoritative surface when you
need a caller-supplied blob hash.
When typed refs are present, their kind is validated at config time:
material_db_ref.kind must be MATERIAL_DB, and every
antenna_pattern_refs[*].kind must be ANTENNA_PATTERN.
All fields are optional at this schema. The SionnaRT-requires-assets validator on
SceneConfig raises with
loc=("assets", "scene_geometry_uri") when
SceneConfig.geometry.backend == SceneGeometryBackend.SIONNA_RT and
both scene_geometry_uri and scene_geometry_ref are None.
class rfgen.config.SceneGeometryConfig¶
class SceneGeometryConfig(BaseModel):
backend: SceneGeometryBackend = SceneGeometryBackend.NONE
overlap_policy: SceneOverlapPolicy = SceneOverlapPolicy.ALLOW
rt_solver: RTSolverConfig | StatisticalSolverConfig | None = None
Scene-geometry backend selection. backend is a closed enum with three
members:
SceneGeometryBackend.NONE: no geometry; flat-fading or analytical channels.SceneGeometryBackend.SIONNA_RT: Sionna ray-traced geometry. RequiresSceneConfig.assets.scene_geometry_refor the legacyscene_geometry_uri. The pivot validator onSceneConfigenforces this.SceneGeometryBackend.MITSUBA: Mitsuba scene blob. When scene assets are supplied, the composer threads them intoSceneMetadata.geometry_asset_refsand the sibling*_hashaudit fields even without RT propagation.
overlap_policy controls how the scene composer handles emitters whose
time-frequency rectangles overlap; see SceneOverlapPolicy.
rt_solver carries the optional solver knobs forwarded through
ChannelContext.rt_solver_params: use
RTSolverConfig for
SceneGeometryBackend.SIONNA_RT, or
StatisticalSolverConfig
when a statistical Sionna backend consumes the same field.
class rfgen.config.GeometryPoseConfig¶
class GeometryPoseConfig(BaseModel):
position_m: tuple[float, float, float]
orientation_rad: tuple[float, float, float]
velocity_mps: tuple[float, float, float] = (0.0, 0.0, 0.0)
frame: Literal["scene"] = "scene"
Pydantic mirror of rfgen.core_types.GeometryPose, the frozen dataclass
SionnaRT actually consumes. Used
for SceneConfig.tx_pose and ReceiverConfig.rx_pose. orientation_rad is
Euler angles (roll, pitch, yaw) in the scene frame; velocity_mps feeds
SionnaRT’s Doppler wiring (a node
with nonzero velocity produces genuinely time-varying channel taps when
RTSolverConfig.cir_num_time_steps > 1, otherwise it has no effect).
frame is fixed to "scene" for v1; no other coordinate frame is
supported yet.
class rfgen.config.RTSolverConfig¶
class RTSolverConfig(BaseModel):
max_depth: int = 3
los: bool = True
specular_reflection: bool = True
diffuse_reflection: bool = False
refraction: bool = True
synthetic_array: bool = True
scene_frequency_hz: float | None = None
tx_array: ArraySpec = ArraySpec()
rx_array: ArraySpec = ArraySpec()
cir_l_min: int | None = None
cir_l_max: int | None = None
normalize_delays: bool = False
cir_num_time_steps: int | None = None
cir_sampling_frequency_hz: float | None = None
allow_no_path: bool = False
Sionna RT solver knobs, forwarded verbatim into Sionna’s real PathSolver
call (max_depth, los, specular_reflection, diffuse_reflection,
refraction, synthetic_array) and scene setup. scene_frequency_hz sets
scene.frequency directly; when unset,
SionnaRT falls back to
metadata.realized_carrier_hz, which requires SceneConfig.center_hz to be
a positive absolute carrier (a baseband scene has no physical frequency to
ray-trace at). tx_array/rx_array are typed
ArraySpec sub-models forwarded to
Sionna’s PlanarArray constructor (num_rows, num_cols, pattern,
polarization); a typo’d key or an out-of-range pattern/polarization
value fails at config-construction time with a Pydantic ValidationError
naming the offending field, rather than leaking a raw TypeError from Sionna
at solve time. More than
one antenna element on either array raises
ChannelError, since rfgen’s
Signal type has no per-antenna representation. allow_no_path controls
whether a zero-path solve raises ChannelError
(False, the default) or returns all-zero IQ (True).
class rfgen.config.ArraySpec¶
class ArraySpec(BaseModel):
num_rows: int = 1
num_cols: int = 1
pattern: Literal["iso", "dipole", "hw_dipole", "tr38901", "custom"] = "iso"
polarization: Literal["V", "H", "VH", "cross"] = "V"
id: str | None = None
Typed antenna-array spec for RTSolverConfig’s
tx_array/rx_array. The permitted polarization set, and three of the four
pattern members (iso/dipole/hw_dipole/tr38901), map onto Sionna’s
registered PlanarArray pattern/polarization names. Sionna’s own
PlanarArray constructor requires pattern and polarization on every call
with no library-side default; the pattern="iso" / polarization="V" defaults
here are rfgen-chosen conveniences (a single isotropic, vertically-polarized
element, the simplest non-directional single-antenna array), not inherited
Sionna defaults. extra="forbid", so an unknown key (e.g. patern) or an
out-of-range pattern/polarization value raises a Pydantic
ValidationError at construction.
pattern="custom" is rfgen’s own escape hatch, not a name Sionna itself
registers: it requires an ANTENNA_PATTERN
GeometryAssetRef on
ctx.geometry_asset_refs, whose entrypoint must be a .py file defining a
module-level v_pattern(theta, phi) -> mi.Complex2f function, the same
vertically-polarized pattern-function contract Sionna’s own built-in
patterns use internally. SionnaRT
loads that function, wraps it in Sionna’s own PolarizedAntennaPattern
(unchanged polarization/model handling), and registers it with Sionna under
a content-hash-derived name before building the array.
The array-identity string recorded in
GeometryProvenance prefers
an explicit id when set, else synthesizes
planar-<num_rows>x<num_cols>-<pattern>-<polarization>. Both
SionnaRT and the scene composer’s
pre-solve provenance stand-in derive that identity from this one model, so the
solved and stand-in provenance never disagree.
class rfgen.config.PanelArraySpec¶
class PanelArraySpec(BaseModel):
num_rows_per_panel: int = 1
num_cols_per_panel: int = 1
polarization: Literal["single", "dual"] = "single"
polarization_type: Literal["V", "H", "VH", "cross"] = "V"
antenna_pattern: Literal["omni", "38.901"] = "omni"
Typed antenna-array spec for the three system-level statistical propagation
backends (SionnaUMa,
SionnaUMi, SionnaRMa) and for SionnaCDL. These construct Sionna’s
sionna.phy.channel.tr38901.PanelArray, a different type from
ArraySpec’s target
(sionna.rt.PlanarArray) with a different keyword surface, hence a separate
model. Sionna’s own constraint, enforced here at config-construction time
(fails fast with a Pydantic ValidationError rather than Sionna’s own
deep-in-construction AssertionError), is that polarization="single"
requires polarization_type to be "V" or "H", while polarization="dual"
requires "VH" or "cross".
class rfgen.config.StatisticalSolverConfig¶
class StatisticalSolverConfig(BaseModel):
carrier_frequency_hz: float | None = None
direction: Literal["uplink", "downlink"] = "downlink"
o2i_model: Literal["low", "high"] = "low"
enable_pathloss: bool = True
enable_shadow_fading: bool = True
ut_array: PanelArraySpec = PanelArraySpec()
bs_array: PanelArraySpec = PanelArraySpec()
model: Literal["A", "B", "C", "D", "E"] = "A"
delay_spread_s: float = 100e-9
min_speed_mps: float = 0.0
max_speed_mps: float | None = None
cir_l_min: int | None = None
cir_l_max: int | None = None
cir_num_time_steps: int | None = None
cir_sampling_frequency_hz: float | None = None
Solver knobs for the five statistical (non-ray-traced) Sionna TR 38.901
propagation backends: SionnaUMa,
SionnaUMi, SionnaRMa (system-level, topology derived from
ctx.tx_pose/ctx.rx_params.rx_pose) and SionnaTDL, SionnaCDL
(link-level, no topology at all). One config surface shared across both
families, mirroring how RTSolverConfig
is one config surface for ray-tracing knobs not every RT call path
exercises; each backend reads only the subset of fields its underlying
Sionna model accepts (e.g. o2i_model is ignored by RMa, TDL/CDL, and
ut_array/bs_array are ignored by SionnaTDL). carrier_frequency_hz
falls back to metadata.realized_carrier_hz when unset, same as
RTSolverConfig.scene_frequency_hz. model is a 3GPP scenario letter
("A"-"E") consumed only by SionnaTDL/SionnaCDL – Sionna names both
model families’ scenario letters the same way, but TDL-A…E and CDL-A…E
are distinct physical scenarios. cir_l_min/cir_l_max/
cir_num_time_steps/cir_sampling_frequency_hz are the same discrete-time
channel conversion knobs as RTSolverConfig’s fields of the same name.
class rfgen.config.PlacementConfig¶
class PlacementConfig(BaseModel):
time_strategy: TimePlacementStrategy = TimePlacementStrategy.IID_UNIFORM
freq_strategy: FrequencyPlacementStrategy = FrequencyPlacementStrategy.IID_UNIFORM
grid_source: str = "json_manifest" # open: rfgen.grid_sources entry-point name
channel_plan_source: str = "json_manifest" # compatibility alias for grid_source
Selects the canonical time and frequency placement strategies plus the
BaseGridSource plug-in used by the realistic_density frequency placement
strategy to look up per-band channel grids. time_strategy and
freq_strategy are closed enum fields; YAML uses their enum values.
grid_source is an open-set string resolved through the rfgen.grid_sources
entry-point group at strategy instantiation. The default "json_manifest"
value resolves to JsonManifestGridSource, which loads per-band JSON files
from rfgen/placement/data/grids/<band>.json. channel_plan_source remains
as a compatibility alias and must match grid_source when both are provided.
Third-party BaseGridSource plugins register under rfgen.grid_sources
without touching this schema or the strategy implementation.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
no |
|
Canonical time-domain placement strategy selector. |
|
|
no |
|
Canonical frequency-domain placement strategy selector. |
|
|
str |
no |
|
Open-set entry-point name resolved through |
|
str |
no |
|
Compatibility alias for |
class rfgen.config.EmitterZooConfig¶
class EmitterZooConfig(BaseModel):
families: list[EmitterFamilyConfig] = Field(default_factory=list)
Pool of emitters with per-family sampling weights. The default is an empty
list so a no-override GenerationConfig() build surfaces only the two
genuinely required missing fields (run.run_id and storage.path). The
“families must be non-empty” runtime check is deferred to the scene composer
(Layer 4); the composer raises a SceneError
when it tries to draw from an empty pool, with a message pointing at this
config block.
class rfgen.config.EmitterFamilyConfig¶
class EmitterFamilyConfig(BaseModel):
family: EmitterFamily
classes: list[str] = Field(min_length=1)
weight: float = Field(gt=0.0, default=1.0)
params: dict[str, object] = Field(default_factory=dict)
fingerprint: FingerprintConfig | None = None
Per-family entry in
EmitterZooConfig.families.
Names a family (closed enum), restricts it to a non-empty subset of
supported_classes, sets a relative sampling weight, forwards params to
BaseEmitter.generate, and optionally
attaches a FingerprintConfig.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
yes |
– |
Top-level emitter family (closed enum). |
|
|
|
yes |
– |
Subset of |
|
float (> 0) |
no |
|
Relative sampling weight; normalized across all entries. |
|
|
no |
|
Forwarded to |
|
FingerprintConfig | None |
no |
|
Per-device impairment priors. |
class rfgen.config.FingerprintConfig¶
class FingerprintConfig(BaseModel):
enabled: bool = True
num_devices: int = Field(ge=1, default=10)
cfo_hz_range: tuple[float, float] = (-1000.0, 1000.0)
sfo_ppm_range: tuple[float, float] = (-20.0, 20.0)
iq_imbalance_db_range: tuple[float, float] = (-1.0, 1.0)
phase_noise_dbc_hz_range: tuple[float, float] = (-110.0, -90.0)
pa_model: PAModel = PAModel.RAPP
Prior over per-device CFO / SFO / IQ-imbalance / phase-noise / PA-model parameters. Consumed by the device-fingerprint module. See Fingerprint Math.
pa_model is a closed PAModel enum.
Unknown values fail during Pydantic schema validation.
class rfgen.config.ChannelConfig¶
class ChannelConfig(BaseModel):
name: str = "torchsig_impairments" # legacy compatibility field
chain: list[ChannelChainEntry] = Field(default_factory=list)
params: dict[str, object] = Field(default_factory=dict)
snr_db_range: tuple[float, float] = (-10.0, 30.0)
Channel chain selection. The live runtime surface is chain, an ordered list
of ChannelChainEntry items, each
naming a closed-enum Transformation.
The top-level name, params, and snr_db_range fields remain in the schema
only as legacy compatibility placeholders; the shipped validators require the
default name, empty params, and the default snr_db_range so user config
cannot silently set values that runtime ignores.
A @model_validator(mode="after") enforces three cross-entry invariants. All
three are part of the seven enumerated invalid combinations:
At most one
Group.CHANNELentry. More than one entry whosetransformation.group == Group.CHANNELraisesValidationError. The channel-propagation slot is single-instance per chain.Grouporder is monotonic non-decreasing across the full chain. A later entry may move from TX to CHANNEL to RX, but it may not backtrack from a later group into an earlier one.Adjacent same-group ordinals are monotonic non-decreasing. Two adjacent entries in the same group whose transformation ordinals decrease raise
ValidationError. This catches a common config-time bug where a TX impairment is inserted out of order after a later one.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
str |
no |
|
Legacy compatibility field. Must stay at the default value; live channel selection uses |
|
|
no |
|
Ordered transformation list. |
|
|
no |
|
Legacy compatibility field. Must stay empty; live per-transformation params belong on |
|
|
no |
|
Legacy compatibility field. Must stay at the default value; runtime does not consume it. |
class rfgen.config.ChannelChainEntry¶
class ChannelChainEntry(BaseModel):
transformation: Transformation # closed enum
params: dict[str, object] = Field(default_factory=dict)
One transformation entry in
ChannelConfig.chain. The
transformation field is a closed
Transformation enum member;
params is validated against the concrete channel’s own schema() at
instantiation, not at config validation.
class rfgen.config.StorageConfig¶
class StorageConfig(BaseModel):
backend: StorageBackend | str = StorageBackend.ZARR_LOCAL
path: str = Field(min_length=1) # required, no default
compression: str = "blosc"
chunk_samples: int = Field(gt=0, default=128)
record_axis: RecordAxis = RecordAxis.PER_RX
assets_path: str | None = None
Selects the BaseStore backend and
configures on-disk layout. path has no default and is one of the two
no-default leaves driving the no-override-build error contract on
GenerationConfig.
SIGMF path-scheme validator¶
A @model_validator(mode="after") enforces that
backend == StorageBackend.SIGMF implies path starts with one of:
file://
Any other prefix raises ValidationError. This is one of the seven
enumerated invalid combinations. Other backends do not enforce a URI scheme
at this layer; per-backend validation happens at store construction.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
StorageBackend | str |
no |
|
Built-in backend enum or a custom |
|
str ( |
yes |
– |
Output URI; required. The contract test for |
|
str |
no |
|
Compression codec. Shipped built-ins validate it as |
|
int (> 0) |
no |
|
IQ chunk length in samples for Zarr and HDF5 only. Explicitly setting it for WebDataset or SigMF raises |
|
no |
|
Multi-RX record layout. |
|
|
str | None |
no |
|
Path to the content-addressed asset store. |
class rfgen.config.ExecutorConfig¶
class ExecutorConfig(BaseModel):
name: str = "local" # open-set plugin selector
parallelism: int = Field(default=1, gt=0)
Selects and parameterizes the
BaseDistributedExecutor.
name is open-set (executors register through the rfgen.executors
entry-point group). New backends do not require updating this schema.
parallelism is the requested PySpark partition count. Synchronous local
execution ignores it. The value must be a positive integer, and booleans are
rejected before Pydantic’s integer coercion. Unknown keys, including the
removed free-form params mapping, fail validation.
class rfgen.config.LabelConfig¶
class LabelConfig(BaseModel):
name: str = "joint"
params: dict[str, object] = Field(default_factory=dict)
extra_labelers: list[str | LabelerSpec] = Field(default_factory=list)
seg_n_fft: int = Field(gt=0, default=1024)
seg_hop: int = Field(gt=0, default=256)
segmentation_mode: SegmentationMode = SegmentationMode.SINGLE_LABEL
segmentation_tie_break: SegmentationTieBreak = SegmentationTieBreak.LOWER_EMITTER_INDEX
Selects the primary labeler, additional labeler plugins, and per-task label
parameters. STFT parameters (seg_n_fft, seg_hop) define the
time-frequency grid for segmentation masks. segmentation_mode selects
single-label vs multi-label masks, and segmentation_tie_break documents the
single-label overlap rule. Segmentation capability is selected by the active
labeler itself: bbox stays metadata-only, while segmentation and joint
emit segmentation by construction. See Label Schema.
class rfgen.config.LabelerSpec¶
class LabelerSpec(BaseModel):
name: str = Field(min_length=1)
params: dict[str, object] = Field(default_factory=dict)
One entry in
LabelConfig.extra_labelers. Either a
plain registry-name string or a LabelerSpec with per-labeler params.
class rfgen.config.AnnotatorConfig¶
class AnnotatorConfig(BaseModel):
enabled: StrictBool = True
types: list[AnnotationType] = Field(default_factory=lambda: [AnnotationType.CAPTION])
bulk_llm: LLMConfig # required
verifier_llm: LLMConfig | None = None
verifier_subset_pct: float = Field(ge=0.0, le=100.0, default=0.0)
Inference templating layer config. bulk_llm is required; verifier_llm is
optional unless verifier_subset_pct is greater than 0.0. The shipped config
accepts percentages from 0.0 through 100.0. Intermediate verifier subsets
must be paired with preselected membership from select_paes_verifier_subset(...)
before records are handed to the annotator. When the parent slot is None on
GenerationConfig, the annotation
phase is skipped and these fields never participate in validation.
When the block is present and enabled is False, config validation still
runs for the block, but the runtime annotation phase is a no-op and clients are
not constructed.
enabled uses Pydantic StrictBool: only YAML/JSON booleans (true/false)
are valid. Numeric and string-like booleans such as 0, 1, "false", and
"true" are rejected at config-validation time.
The field names bulk_llm and verifier_llm are retained from the
config-surface naming; the configured endpoints register through the
rfgen.inference_clients entry-point group and are not restricted to text-only
LLMs.
class rfgen.config.LLMConfig¶
class LLMConfig(BaseModel):
provider: str # open-set plugin selector
model: str
temperature: float = Field(ge=0.0, le=2.0, default=0.2)
max_tokens: int = Field(gt=0, default=512)
json_schema_mode: StrictBool = True
One inference endpoint configuration. Used twice inside
AnnotatorConfig: once as
bulk_llm (high-volume, low-cost) and optionally once as verifier_llm
(second-pass hallucination check). provider is open-set; inference clients
register through the rfgen.inference_clients entry-point group.
json_schema_mode requests structured output where the provider supports it.
It uses Pydantic StrictBool: only YAML/JSON booleans (true/false) are
valid. Numeric and string-like booleans such as 0, 1, "false", and
"true" are rejected at config-validation time.
The class name LLMConfig and the field names bulk_llm / verifier_llm are
retained for config-surface stability; the configured providers can be text-only
LLMs, vision-language models, or audio-language models.
class rfgen.config.AnnotationOrchestratorConfig¶
class AnnotationOrchestratorConfig(BaseModel):
name: str = "local_loop" # open-set plugin selector
min_poll_interval_s: float = Field(default=10.0, ge=0.0)
resources: BatchResources | None = None
Selects and parameterizes the batch annotation orchestrator (e.g.
vertex_batch, anthropic_batch, openai_batch, local_loop). name is
open-set; orchestrators register through the
rfgen.annotation_orchestrators entry-point group.
The Phase-2 annotate command consumes this schema separately from
GenerationConfig. Each managed
orchestrator requires its matching typed resource model. local_loop rejects
managed resources. The removed bulk_model, verifier_model, and params
fields are unknown keys and fail validation.
Managed execution is resumable and uses at-least-once result delivery keyed by
the stable annotation-job identity (sample_id, annotation_type, template_id, run_id). The command checkpoints normalized results before storage writes,
records each durable key, and cleans up provider staging only after every result
is durable. Retrying a persisted handle skips completed keys; retrying after a
cleanup failure repeats cleanup without refetching or rewriting durable
annotations. Configure provider staging and retention through resources; the
schema has no alternate retry or idempotency-key field.
Fields¶
Field |
Type |
Required |
Default |
Description |
|---|---|---|---|---|
|
str |
no |
|
Open-set plugin name. |
|
float (>= 0) |
no |
|
Minimum seconds between provider status requests. Booleans and negative values fail validation. |
|
|
no |
|
Provider-specific staging and model resources. Required for managed orchestrators. |
Typed batch resources¶
All resource models forbid unknown keys, strip surrounding whitespace from strings, and reject blank strings. URI validators require a non-empty authority and the provider-specific scheme.
Resource model |
Orchestrator |
Required fields |
Optional controls |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
BatchResources is the union of these five models. Provider validation is
exact: for example, name="vertex_batch" accepts only
VertexBatchResources, not another resource model with similar fields.
class rfgen.config.AuditConfig¶
class AuditConfig(BaseModel):
snr_mean_error_db: float = Field(default=0.5, ge=0.0, le=100.0)
class_kl_divergence: float = Field(default=0.05, ge=0.0, le=1.0)
overlap_error: float = Field(default=0.02, ge=0.0, le=1.0)
occupancy_error: float = Field(default=0.05, ge=0.0, le=1.0)
max_records: int = Field(default=1_000_000, gt=0)
max_class_cardinality: int = Field(default=10_000, gt=0)
max_snr_bucket_cardinality: int = Field(default=10_000, gt=0)
max_emitter_count_cardinality: int = Field(default=10_000, gt=0)
Controls dataset-audit failure thresholds and bounds every aggregation whose size depends on dataset metadata. Threshold fields reject booleans and enforce their documented ranges. Record and cardinality bounds must be positive integers and also reject booleans.
Field |
Default |
Contract |
|---|---|---|
|
|
Maximum absolute realized-versus-requested mean SNR error in dB; range |
|
|
Maximum class-distribution KL divergence; range |
|
|
Maximum absolute cochannel-overlap error; range |
|
|
Maximum absolute spectral-occupancy error; range |
|
|
Maximum records consumed by one audit. |
|
|
Maximum realized or requested class keys retained by audit histograms. |
|
|
Maximum requested-SNR buckets retained by the audit. |
|
|
Maximum distinct realized emitter counts retained by the audit. |
class rfgen.config.CredentialsConfig¶
class CredentialsConfig(BaseModel):
provider: str = "static"
params: dict[str, object] = Field(default_factory=dict)
Selects the BaseCredentialsProvider.
Usually omitted; cloud SDKs fall back to default credential chains. The
config exists so a user can pin a non-default resolution path. Documented
provider values: "static", "gcp_adc", "aws_default",
"azure_default", plus any third-party provider registered under
rfgen.credentials.
class rfgen.config.ReferenceHostConfig¶
class ReferenceHostConfig(BaseModel):
cpu_model: str = Field(min_length=1)
cpu_cores: int = Field(ge=1)
ram_gb: int = Field(ge=1)
disk_type: Literal["nvme", "ssd", "hdd"]
os: str = Field(min_length=1)
python_version: str = Field(min_length=1)
torch_version: str = Field(min_length=1)
Reference-host metadata used by per-test-suite YAML fixtures. The schema
lives at rfgen.config.reference_host so Layer 7 (dataset-consumer) and
Layer 9 (cli, validation-and-audit) consumers import it from below
without a forward-layer edge. The fixture YAML files
(tests/conf/dataloader_reference_host.yaml,
tests/conf/cli_reference_host.yaml) are authored under
validation-and-audit; only the schema lives here.
disk_type is Literal["nvme", "ssd", "hdd"] rather than a closed enum
because the value is a fixture-only tag with no runtime selection logic
hanging off it; the closed-set rule applies to fields that drive code paths.
Method: matches_runtime¶
def matches_runtime(self) -> bool:
...
Returns True iff the configured host matches the test runner’s
platform.python_version(), torch.__version__, plus a CPU model probe.
Tests use the result to decide between running and xfail.
The CPU probe uses platform.processor(). When that returns the empty
string (the default on Linux), matches_runtime() returns False rather
than silently passing the substring check. This is the explicit choice: a
non-match on an unknown host is safer than a silent pass that lets a
wall-clock or cold-import contract test report success on the wrong CPU.
Enumerated invalid combinations¶
Every config that fails one of the cross-field invariants below raises
pydantic.ValidationError. The table below lists the documented
cross-field invariants enforced by the shipped config models. Plugin- or
backend-specific failures surface later, at instantiation time.
# |
Failure |
Where enforced |
|---|---|---|
1 |
|
|
2 |
Any of |
Field constraints ( |
3 |
More than one entry in |
|
4 |
|
|
5 |
|
|
6 |
|
|
7 |
|
|
8 |
|
|
9 |
|
|
10 |
|
|
from_hydra adds two transport-level failure modes that surface as
ConfigError, not ValidationError:
non-config / non-dict input (e.g. a ListConfig) and a top-level container
that resolves to anything other than a dict. Both are documented under
from_hydra().
Closed-set vs open-set field rule¶
Closed-set fields use Layer 1 StrEnums from rfgen.enums. Open-set plugin
selectors stay typed str and resolve through the registry at instantiation.
Closed-set ( |
Open-set ( |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
YAML may use the enum string value directly because Pydantic deserializes it at validation time; Python code MUST use the enum member (see Style § Enum vs string in Python examples).
Retired channel config blocks¶
AWGNConfig and ReceiverFrontendConfig belonged to the previous five-stage
channel design (one coarse RX_FRONTEND stage followed by a standalone
AWGN stage). Both are retired by the semantic-group, fourteen-transformation
pipeline documented in channels.md:
thermal noise now enters as LNA_NOISE inside Group.RX_CAPTURE via
BaseLNANoise, and the coarse RX
frontend is split across BaseRXMixer,
BaseIFFilter,
BaseResampler,
BaseLNANoise,
BaseADCQuantization,
BaseRXPhaseNoise,
BaseRXIQImbalance, and
BaseAGC. Per-transformation parameter
blocks live with each ABC’s concrete backends; see
ChannelConfig for the chain-level
config that aggregates them.
See Also¶
Config Schema: full normative defaults and YAML examples.
rfgen.enums: closed-enum members referenced above.
Cloud Backends: credential-provider and executor backends.