rfgen.config¶
Pydantic v2 models validate one generation job before a sample is generated.
Hydra composes YAML trees and CLI overrides into a mapping; rfgen.config
turns that mapping into a typed GenerationConfig. A failed configuration
does not start generation.
Entry points¶
from omegaconf import OmegaConf
from rfgen.config import GenerationConfig, from_hydra, validate_config
config = from_hydra(OmegaConf.load("configs/config.yaml"))
assert isinstance(config, GenerationConfig)
from_hydra(cfg) resolves an OmegaConf mapping then validates it as a
GenerationConfig. validate_config(data) validates a plain mapping. Both
raise rfgen.core.errors.ConfigError with Pydantic error details when the
input is invalid.
from_hydra accepts either an OmegaConf node or a plain dict. It resolves
interpolations before validation and rejects a non-mapping top level with a
ConfigError. validate_config is the matching boundary for an already
materialized mapping. Direct GenerationConfig.model_validate(...) remains
useful when a caller deliberately wants Pydantic’s native ValidationError.
Composition root¶
GenerationConfig composes these generic framework concerns:
Area |
Primary model |
Responsibility |
|---|---|---|
Run |
|
Run identifier, sample count, shard size, and seed. |
Scene |
|
Signal grid, density, receiver geometry, and assets. |
Scene plan |
Authored per-run scene-plan template: clock, world asset URI, systems, event schedule with per-event communications blocks, and target sampling bounds. |
|
Emitters |
|
Emitter pool and optional device-impairment priors. |
Channel |
|
Ordered signal transformations. |
Labels |
|
Structured label production. |
Storage |
|
Store backend and destination URI. |
Execution |
|
Local or registered distributed executor selection. |
Additional public models cover annotation, credentials, placement, and
reference-host metadata. rfgen.config.__init__ re-exports these supported
models, so from rfgen.config import GenerationConfig, RunConfig is the
stable concise import path.
GenerationConfig¶
GenerationConfig is the single configuration object consumed by generic
generation. Its constructor is equivalent to the following Pydantic surface:
GenerationConfig(*, emitter_zoo=EmitterZooConfig(), channel=ChannelConfig(), scene=SceneConfig(), placement=PlacementConfig(), label=LabelConfig(), annotator=None, augmentation=None, storage=None, executor=ExecutorConfig(), credentials=None, run=None, projections=None, observation=ObservationConfig(), plan=None).
Field |
Type |
Required |
Default |
Primary behavior |
|---|---|---|---|---|
|
|
no |
empty zoo |
Declares explicitly selected emitter families. |
|
|
no |
empty chain |
Declares ordered signal transformations. |
|
|
no |
default signal grid |
Declares scene geometry, duration, density, and receivers. |
|
|
no |
IID placement |
Selects time/frequency placement strategies. |
|
|
no |
|
Selects structured label production. |
|
|
no |
|
Optional later annotation configuration. |
|
|
no |
|
Optional registered record augmentation. |
|
|
no |
|
Names the generic output backend and destination when an executor will persist records. |
|
|
no |
|
Selects local or registered execution. |
|
|
no |
|
Optional local credential-provider configuration. |
|
|
no |
|
Supplies run identity, shard sizing, and deterministic seed for executor-backed generation. |
|
|
no |
|
Nonempty ordered observation-projection instances, and mandatory: a run declares what it observes through this field and nothing else. IDs must be unique. |
|
no |
1 GiB tensor budget |
Bounds the complete aggregate Signal Dataset record. |
|
|
|
no |
|
Optional scene-plan template (in |
ObservationProjectionConfig has four fields: a unique portable
projection_id, a namespaced plugin selector, a positive strict
contract_version, and a parameter mapping. Unified observation execution
requires plan, projections, and a Signal Dataset store. The runtime resolves
the complete set before it opens the store or renders a projection. See
Observation API and Generate one unified observation
dataset.
Complete ScenePlan, projection, and observation field tables are in
Observation Configuration.
The scene plan and its exclusivity rules¶
plan is a template, not a plan: a ScenePlan carries one sample’s
identity and its concrete targets, while a configuration is per run and
describes many samples. A configured literal would make every sample identical
and make the second sample’s write-once provenance artifact collide on
scene_id. rfgen.scene.minting.mint_scene_plan(template, run_seed=..., sample_index=...) mints one plan per sample, and sample_index is the
run-global index, shard_index * shard_size + local_index, in both the
local and the Dataproc driver.
A plan is the single source of truth for what it covers, and the two halves of that rule are enforced differently because they are different kinds of object.
The radar renderer’s own plan-owned fields are not fields at all. targets,
system.location_m, system.rotation_deg,
system.transmitter_element_locations_m,
system.receiver.element_locations_m, and
system.waveform.pulse_repetition_intervals_s were removed from
RadarRendererParams, so extra="forbid" refuses them by name. A check that
merely ignored them would leave an author’s value silently discarded.
The scene fields below are typed with defaults, so exclusivity is checked
against model_fields_set: comparing to the default would silently accept an
author who wrote the default, and reject an explicit value that agrees with the
plan.
Scene field |
Plan field that owns it |
Check |
|---|---|---|
|
|
|
|
per-event |
|
|
|
|
|
|
|
The scene.* rows are checked for every projection, because those fields belong
to the scene configuration every projection composes through.
One rule sits beside the table. plan.clock.duration_s must equal
scene.duration_s. That is an agreement check rather than an exclusivity row:
no plan fact routes into the communications record length, so the scene field
stays authoritative and settable, and only a configuration whose two halves
describe different scenes is refused.
One further rule governs how the world is named. plan.scene_geometry_uri is
optional, and a template that leaves it unset plans against an empty world.
When it is set, it must be an absolute filesystem path or open with one of
the supported URI prefixes, which are exactly file://, gs://, s3://,
https://, and sionna://builtin/. A relative path resolves against the working
directory of whichever process mints the plan, and since the world’s bytes are
digested into the plan hash and therefore into time_reference, a relative name
would make the plan identity depend on where the run was launched from. A URI
whose scheme is not on that list is refused by name
(plan_world_asset_unsupported_scheme) rather than accepted, because nothing
resolves it as a URI: s3a://bucket/world.xml would fall through to the same
working-directory resolution a bare relative path takes, and being well formed
does not make it locatable from a second machine. A relative path is refused
under plan_world_asset_relative_path, and both errors list the supported
prefixes.
A file:// URI carries a supported prefix and still has to name an absolute
path, so file://world.xml is refused under
plan_world_asset_relative_file_uri. Everything between file:// and the first
slash is a URI authority, not a path: world.xml there is dropped when the
reference is resolved, leaving the working directory itself. The two spellings
that mean this machine, file:///path and file://localhost/path, are
accepted; any other authority names a host nothing here fetches from. A local
asset that cannot be read where minting runs is refused for the same family of
reasons, naming the resolved path, rather than falling back to a URI-identity
digest; a remote gs:// URI keeps that fallback, which is deterministic because
it never consults readability at all.
The kind the world asset takes is derived from that URI, by the scheme-then-path-suffix
table in GeometryAssetKind’s suffix rule.
plan.scene_geometry_kind is the optional escape hatch for the one case a
suffix genuinely cannot decide: a URI naming a content-addressed blob, an
extensionless export, or a directory-style entrypoint. Leaving it unset is the
ordinary case and means the suffix table decides alone; absence is what keeps
every template written before the field existed on the kind it already had.
The field is accepted only where the suffix table falls through. On a URI
the table classifies positively, a kind that agrees is accepted as a redundant
restatement, and a kind that disagrees is refused under
scene_geometry_kind_conflicts_with_suffix, naming the URI, the suffix-derived
kind, and the declared kind. The refusal rather than a precedence rule is the
same answer plan_world_asset_disagreement gives for a URI disagreement, and
for the same reason: both facts are authored by one person in one file, so one
of them is a typo and there is no reading under which silently preferring
either is more likely to be what was meant. It also closes a cache collision
that only the override can reach, since a local asset’s digest is sha256 of
its bytes and does not mention the kind, so one file declared two ways carries
one content_hash while naming two different ingest paths. An author who
genuinely has a USD stage named .xml should rename the file rather than teach
the configuration to disbelieve it.
Two further refusals: scene_geometry_kind_not_a_world for a kind that can
never be world geometry (material_db, antenna_pattern), because accepting
one would defer the failure to the engine; and scene_geometry_kind_without_uri
when a kind is declared beside no scene_geometry_uri at all, because a kind
without an asset describes nothing.
A fourth refusal is about the other route to the world.
plan.scene_geometry_kind is resolved on the minting route and on no other:
DefaultSceneComposer derives its kind from the bare
scene.assets.scene_geometry_uri through the same suffix table, and it never
sees the plan. So declaring a kind on the plan side while also naming a world
on the scene side describes one asset under two kinds, and for a local asset
both kinds arrive at the same digest and the same world-cache key, where
whichever route loads first wins the entry. That pair is refused under
scene_geometry_kind_beside_composer_world, naming both paths. The remedy is
the typed scene.assets.scene_geometry_ref, which carries the kind and the
digest as one authored reference the composer prefers over the legacy URI;
note that under a geometry backend that loads a world the typed reference
accepts only the kinds that backend can load, so a USD kind has no route to the
scene side at all. That limit is scoped to the backend: under
scene.geometry.backend: none the allowlist does not run and the kind is
unrestricted, which is inert rather than a second route, because a scene with
no geometry backend emits no geometry asset refs at all. The refusal message
says whichever of those two things is true for the backend it read, and lists
kinds only when there is a list.
An unknown spelling of the field is refused by the field itself, and the
message lists the kinds worth naming there rather than every member of the
enum: material_db and antenna_pattern are refused as non-world kinds,
opengert_mitsuba_xml_bundle is unreachable through this field because an
OpenGERT export is Mitsuba XML and its .xml suffix classifies it positively,
and deepmimo_export names a format no shipped ingest reads.
scene.geometry.geometry_ingest¶
The engine ingest that turns world geometry into an engine world, named from
the rfgen.geometry_ingests entry-point group. Leaving it unset is the
ordinary case and resolves to sionna_mitsuba, the in-repo ingest, which reads
sionna_builtin_scene, mitsuba_xml_bundle, and
opengert_mitsuba_xml_bundle and converts nothing. Writing that name
explicitly is the same answer: every consumer applies the resolution rather
than a literal “is it set” test, so spelling the default cannot fork a world
cache or move a stored byte. A name no installed distribution registers raises
the registry’s own PluginNotFoundError listing what is available, so no
refusal is declared for it.
A kind the selected ingest does not declare is refused with
geometry_format_not_ingestible before any engine work, naming the ingest, the
kind, the URI, and the kinds that ingest reads. Selecting an ingest is
therefore a statement about one engine’s capability, not a claim about the
asset.
One combination is refused at the configuration boundary. A non-default
geometry_ingest beside a radar projection raises
geometry_ingest_unroutable_for_projection. The radar renderer builds
its world from its own validated params and has no view of the generation
configuration, and the scene plan carries no ingest field, so the name cannot
reach it; letting the radar path silently use the default while the
communications path honoured a configured ingest would build two different
worlds under one scene reference, which is the failure
plan_world_asset_disagreement exists to prevent. A non-default ingest is a
communications-path capability today.
scene.assets, scene.duration_s, scene.multi_rx, and scene.rx_array are
deliberately not in the table: this cycle routes no plan fact into any of
them, so forbidding them would remove the only working input without providing a
replacement. A cross-domain pair therefore points the communications
scene.assets.scene_geometry_uri and the plan’s world asset at the same asset
by hand, and nothing in this cycle checks that they agree.
Each communications event of the template carries a mandatory comms block:
the four identity fields (device_id, transmitter_role, link_id,
link_direction), the five emitter-side fields (emitter_key, class_label,
bandwidth_hz, frequency_offset_hz, tx_power_dbm), a duration policy
restricted to kind: fixed, and an optional sampling block. The block itself
is mandatory because ComposerEvent requires the four identity fields, and
emitter_key is always authored. The other four are each required to be
supplied rather than authored: fixed on the event, or drawn by the matching
sampling policy (class_labels, bandwidth_hz, frequency_offset_hz,
tx_power_dbm). An event that leaves one neither fixed nor sampled is refused
and the error names it. The fixed-duration restriction exists because a plan
authors start_s and stop_s while a non-fixed policy draws a duration, and the two agree only by coincidence. The
template validator names each failure: a missing block is
plan_template_comms_block_missing, another policy kind is
plan_duration_policy_not_fixed, and the rule is symmetric, so an
active_radar event carrying a comms block is refused under
plan_template_comms_block_unexpected rather than having the block ignored.
Eight of the nine are realized-signal stamps: the four identity fields and
class_label are carried onto the emitter metadata as authored, emitter_key
selects the emitter that generates the IQ, tx_power_dbm becomes that emitter’s
transmit power, and frequency_offset_hz is the offset the composer mixes to,
so realized_carrier_hz is scene.center_hz plus it. bandwidth_hz is the one
exception: it is a placement constraint, the placement-support bound the
emitter’s realized bandwidth must not exceed and whose occupied span
|frequency_offset_hz| + bandwidth_hz / 2 must fit inside half of
scene.bandwidth_hz.
It does not resize or filter the emitted signal, so an event declaring 5 MHz
whose emitter realizes 200 kHz is accepted and the emitter metadata reports
200 kHz. The authored value is preserved verbatim in the record’s
extras.planned_event, so the declared bound and the realized occupancy are
both readable and distinguishable.
The model forbids unknown fields and validates assignment. An in-memory caller
may omit run and storage; generate_record(...) needs neither. Executors
and generate_local_signal_dataset(config) call require_execution_config() and fail with a
ConfigError before component resolution if either block is absent. Once a
block is provided, run.run_id and storage.path remain required leaf fields.
The Dataproc selector additionally requires
storage.backend: signal_dataset and a gs:// storage path. Plugin selector
names are intentionally not resolved during model validation; resolution
happens when the corresponding generic framework component is built.
build_credentials_provider() returns None when no credentials block is
configured, otherwise it constructs the selected built-in or entry-point
provider. build_augmentation() similarly constructs the optional
rfgen.augmentations entry-point class. Both raise ConfigError when the
resolved provider or augmentation does not satisfy its declared contract.
RunConfig¶
RunConfig(*, run_id, num_samples=10_000, shard_size=1_000, seed=42, shard_failure_threshold=1.0) defines the bounded work for
one generic generation run.
Field |
Type |
Required |
Validation and behavior |
|---|---|---|---|
|
|
yes |
Trimmed, non-empty stable run identifier. |
|
|
no |
Positive total record count. |
|
|
no |
Positive records per shard; shard count is |
|
|
no |
Master seed used to derive deterministic shard/sample seeds. |
|
|
no |
|
Boolean values are rejected for numeric fields. A non-divisible sample count is valid and emits a warning because the final shard is partial.
StorageConfig¶
StorageConfig(*, path, backend=StorageBackend.SIGNAL_DATASET, assets_path=None) configures one record store. The model has exactly three
fields. path is required and must be non-empty. A recognized backend string is
converted to StorageBackend; a non-empty unrecognized string is retained so an
installed dataset-store plugin can resolve it at its applicable boundary.
Field |
Type |
Required |
Default |
Behavior |
|---|---|---|---|---|
|
|
no |
|
Shipped store or registered store selector. |
|
|
yes |
– |
Output location, trimmed but otherwise store-governed. |
|
|
no |
|
Optional generic asset-store location. |
Signal Dataset accepts local paths and gs:// with the GCS extra.
The model sets extra="forbid", so a configuration carrying compression,
chunk_samples, dataset_filename, or record_axis is refused with a Pydantic
ValidationError naming the field. Those keys configured a fixed-IQ store
layout nothing writes; they were removed rather than accepted and ignored, so a
stale config fails at load instead of producing a dataset in an unrequested
shape.
Store selectors resolve through BaseDatasetStore.
ExecutorConfig and DataprocServerlessConfig¶
ExecutorConfig(*, name="local", parallelism=1, dataproc=None) selects the
execution backend. name is a trimmed non-empty open selector resolved through
rfgen.executors; parallelism is a positive integer used by the Spark
backend and ignored by the synchronous local executor. dataproc is required
exactly when name="dataproc_serverless" and rejected for every other name.
DataprocServerlessConfig(*, staging_uri, service_account, project="rf-foundation-models", region="us-central1", image_uri=None, labels={}, network=None, subnet=None, spark_properties={}, extra_packages=[]) supplies the shipped remote backend settings.
staging_uri must start with gs://; strings must not be blank; network
and subnet are mutually exclusive. The remote execution lifecycle is
documented in Generation and Dataproc
Serverless.
extra_packages names local .whl or .zip files, built on the submitting
machine, that carry a use-case package’s entry points onto the Dataproc
worker. Each entry is rejected at config-parse time if it is blank, names a
remote reference, does not end in .whl or .zip, or collides with another
entry’s staged filename; whether the file actually exists, is a valid zip
archive, and declares an rfgen.* entry-point group is checked at submission
time instead, before the batch is created. See Shipping plugin
packages.
Component configuration models¶
These models are all supported from the concise rfgen.config import path.
They remain generic framework configuration, not application policy.
Emitter configuration¶
EmitterZooConfig, EmitterFamilyConfig, FingerprintConfig, and
LoRaConfig describe the emitter pool and optional device-impairment priors.
The zoo defaults to no families; composition rejects an empty pool when it
needs an emitter. A family requires a closed family, non-empty classes, a
positive weight, and an optional non-blank rfgen.emitters selector.
Channel configuration¶
ChannelConfig and ChannelChainEntry describe the live ordered transform
list. Entries use a closed transformation, an optional non-blank selector,
and parameters. Validation permits at most one propagation-group entry and
requires monotonic transform order.
Scene and receiver configuration¶
SceneConfig, DensityConfig, EventDurationConfig, ReceiverConfig,
MultiRXConfig, ReceiverBackgroundConfig, SceneAssetsConfig,
SceneGeometryConfig, GeometryPoseConfig, RTSolverConfig, and
StatisticalSolverConfig define the signal grid, event density and duration,
receiver topology, optional geometry assets, and propagation solver. Positive
physical dimensions and compatible receiver and geometry choices are
validated at construction.
Label and annotation configuration¶
LabelConfig, LabelerSpec, AnnotatorConfig, LLMConfig, and
AnnotationRequestConfig select label production and library-level annotation
adapters. Plugin selectors must be non-blank; annotation identifiers are
non-empty, temperature is in [0, 2], and token counts are positive. The
separate unified annotation lifecycle uses AnnotationConfigV1 and
run_annotation; see Generate, then annotate.
Augmentation configuration¶
AugmentationConfig supplies an optional non-blank rfgen.augmentations
selector and parameter mapping. It is resolved only by
GenerationConfig.build_augmentation().
Storage and execution configuration¶
StorageConfig defaults to backend: signal_dataset and requires a destination
path. Its only other field is assets_path; compression, chunk_samples,
dataset_filename, and record_axis are refused by name. The
top-level ExecutorConfig selects the local or registered distributed
executor; DataprocServerlessConfig supplies the settings for the shipped
remote backend.
Scene-renderer configuration¶
A projection names the renderer that produces what it observes.
params.renderer_selector resolves through rfgen.scene_renderers, and
params.renderer validates against that class’s ParamsModel. A renderer
receives exactly its configured parameters.
Models |
Core fields and validation boundary |
|---|---|
|
The zoo defaults to no families; composition rejects an empty pool when it needs an emitter. A family requires a closed |
|
|
|
Scene settings define signal-grid values, emitter density, placement parameters, receiver topology, optional geometry assets, and the selected propagation solver. Positive physical dimensions and mutually compatible receiver/geometry choices are validated at construction. See the configuration schema for every field and solver-specific constraint. |
|
Closed time/frequency strategy enums plus the non-empty open |
|
Primary and additional non-blank |
|
Library-level annotation requests have non-empty record/template/run identifiers. LLM providers and models are non-blank; temperature is |
|
Optional non-blank |
|
Non-empty credentials-provider selector and parameter mapping; resolved only by |
|
Bounded generic audit thresholds ( |
|
Required reference-machine metadata used by documented reproducibility and timing checks. |
Validation boundary¶
Closed-set fields use StrEnum values from rfgen.core.enums; open plugin
selectors remain strings and resolve through these exact Python entry-point
groups at instantiation:
EmitterFamilyConfig.selectorand emitter names →rfgen.emitters;ChannelChainEntry.selector→rfgen.channels;ObservationProjectionConfig.params.renderer_selector→rfgen.scene_renderers;LabelConfig.nameandLabelerSpec.name→rfgen.labelers;AugmentationConfig.selector→rfgen.augmentations;CredentialsConfig.provider→rfgen.annotation.credentials;PlacementConfig.grid_source→rfgen.grid_sources;open time- and frequency-placement selectors →
rfgen.time_placementandrfgen.freq_placement;SceneGeometryConfig.geometry_ingest→rfgen.geometry_ingests; andObservationProjectionConfig.selector→rfgen.observation_projections.
GenerationConfig validates cross-field constraints such as a
valid signal grid, compatible storage backend/path pairs, and required scene
geometry assets. The configuration API validates framework primitives only;
external callers own their own dataset expansion, output policy, and
validation policy.
For every field, default, and YAML example, see the configuration schema. For a runnable workflow, see Generate a local dataset.