Enums¶
Closed-set string enumerations live in rfgen.core.enums as StrEnum types. They appear as the type of every config field whose set of valid values is fixed and owned by the framework: placement modes, antenna geometries, model families, label layouts, and so on.
This page appears first in the API Reference because these enums are primitive vocabulary for later pages. Core Types and Config reference enum members, but the enums themselves do not depend on framework data types.
Why StrEnum and not Literal[str, ...]:
YAML / Hydra stays clean. A
StrEnummember is a string at runtime, sosegmentation_mode: multi_labelin a Hydra config deserializes correctly into SegmentationMode.MULTI_LABEL.Pydantic validates at the boundary. Unknown values are rejected at config load with a clear error, identical to
Literal[...].No magic strings inside the framework. Plugin code references SegmentationMode.MULTI_LABEL symbolically. IDE autocomplete works; renaming a value is a single-file change instead of a global grep.
Single source of truth. Each enumeration is defined once in
rfgen.core.enumsand imported wherever it appears.
Open-set names are different: registered plugin names like the name field on ChannelConfig and ExecutorConfig are str, not enums, because anyone can ship a new plugin with pip install and the registry resolves the name at runtime. See background/design-decisions for the full rule.
YAML / Hydra usage¶
YAML has no enum literal syntax, so configs always use the string value of the enum member. Pydantic v2 coerces by value at validation time, and because StrEnum members are their string values at runtime, the round-trip is transparent:
# configs/labels/multi_label.yaml
labels:
segmentation_mode: multi_label # the string value of SegmentationMode.MULTI_LABEL
segmentation_tie_break: lower_emitter_index
from rfgen.config import LabelConfig
from rfgen.core.enums import SegmentationMode
cfg = LabelConfig.model_validate(yaml_dict["labels"])
cfg.segmentation_mode # SegmentationMode.MULTI_LABEL
cfg.segmentation_mode is SegmentationMode.MULTI_LABEL # True
cfg.segmentation_mode == "multi_label" # True (StrEnum members are strings)
Two pitfalls to avoid:
Do not write
segmentation_mode: MULTI_LABEL(the member name). Pydantic looks up by value, not by name, so"MULTI_LABEL"is rejected.Do not wrap the enum in a Hydra
_target_:block. That pattern is for instantiating classes (channels, emitters), not enum values. The plain-string form is the standard Pydantic + Hydra idiom.
In Python code and in prose, prefer the symbolic form (SegmentationMode.MULTI_LABEL) over the magic string. In YAML, the string is the only form.
Scene composition¶
class rfgen.core.enums.ChannelApplicationMode¶
class ChannelApplicationMode(StrEnum):
SCENE = "scene"
PER_EMITTER = "per_emitter"
Selects where the propagation channel runs relative to the per-emitter IQ summation in SceneConfig.
SCENE(default): each emitter is frequency-shifted into the wideband scene buffer, all N emitter signals are summed, and a backend that explicitly supports scene mode runs once on the composite IQ.AWGNChannelsupports this mode. Configure Sionna withPER_EMITTER: current SionnaRT execution is forced per-emitter by its geometry requirement, while the statistical Sionna backends fail at application if selected in scene mode. See Sionna integration and the Scene Composition Algorithm.PER_EMITTER: propagation is realized independently per emitter, applied per-emitter at scene SR, and the results are summed after. N propagation calls per scene. Required for distributed-TX ray-traced scenes (e.g. V2X intersections, distributed uplinks) where TX positions are spatially diverse enough that each TX has a meaningfully different propagation path.
In YAML, use the string value: channel_application: scene or channel_application: per_emitter.
See Reference / Scene Composition Algorithm § Channel application mode for the full mode-comparison table.
class rfgen.core.enums.DensityMode¶
class DensityMode(StrEnum):
FIXED = "fixed"
RANGE = "range"
POISSON = "poisson"
Selects how the DensityConfig draws an emitter count per scene. See Concepts / Scenes / Composition.
class rfgen.core.enums.FrequencyPlacementStrategy¶
class FrequencyPlacementStrategy(StrEnum):
IID_UNIFORM = "iid_uniform"
STRATIFIED = "stratified"
CLUSTERED = "clustered"
REALISTIC_DENSITY = "realistic_density"
ISM_REALISTIC = "ism_realistic"
FORCED_OVERLAP = "forced_overlap"
Selects how the scene composer places each emitter in frequency. Python code should use members such as FrequencyPlacementStrategy.IID_UNIFORM; YAML uses the value, such as iid_uniform.
class rfgen.core.enums.TimePlacementStrategy¶
class TimePlacementStrategy(StrEnum):
IID_UNIFORM = "iid_uniform"
EVENT_RADAR_PRI = "event_radar_pri"
EVENT_PERIODIC_BEACON = "event_periodic_beacon"
EVENT_BURST = "event_burst"
EVENT_BURST_SELF_EXCITING = "event_burst_self_exciting"
EVENT_FHSS_HOP = "event_fhss_hop"
Selects how the scene composer places each emitter in time. The values
correspond one-to-one to the names registered under
[project.entry-points."rfgen.time_placement"], so a configuration string
selects the strategy without translation. IID_UNIFORM draws independent
start times; the five EVENT_* strategies model a protocol’s own cadence — a
radar PRI train, a periodic beacon, a burst process, a self-exciting burst
process, and an FHSS hop schedule.
class rfgen.core.enums.SceneOverlapPolicy¶
class SceneOverlapPolicy(StrEnum):
REJECT = "reject"
ALLOW = "allow"
FORCE = "force"
Selects how the composer handles time-frequency overlap between placed emitters. The policy is selected by SceneGeometryConfig’s overlap_policy field, which defaults to ALLOW. There is no per-draw overlap probability: ALLOW accepts an overlap wherever placement produces one.
class rfgen.core.enums.ArrayGeometry¶
class ArrayGeometry(StrEnum):
ULA = "ula"
URA = "ura"
ARBITRARY = "arbitrary"
Antenna array geometry kind for MultiRXConfig.geometry. The geometry kind picks the array layout family; per-array element counts and spacings live on the sibling array-shape fields. MultiRXConfig rejects setting geometry together with a non-empty receivers list (the geometry-XOR-receivers validator).
class rfgen.core.enums.SceneGeometryBackend¶
class SceneGeometryBackend(StrEnum):
NONE = "none"
SIONNA_RT = "sionna_rt"
MITSUBA = "mitsuba"
Scene-geometry backend selector for SceneGeometryConfig.backend. Member values are lowercase to match YAML and the rest of the framework’s closed-set config strings. Legacy "SionnaRT" and "Mitsuba" spellings still coerce for backward compatibility. Picking SIONNA_RT requires SceneConfig.assets.scene_geometry_uri; the SionnaRT-requires-assets validator on SceneConfig enforces this and raises with loc=("assets", "scene_geometry_uri").
Channels¶
class rfgen.core.enums.Group¶
class Group(IntEnum):
TX = 1
CHANNEL = 2
Pipeline group tag used by ChannelPipeline to validate chain partition membership. Both members are pre-sum: TX runs per emitter, CHANNEL is the propagation slot. See ChannelPipeline for the validation contract.
The enum has two members because the receiver is no longer a pipeline group. Receiver 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: stored transformation-log entries carry them as pinned constants, and re-issuing them to a new member would make an archived log ambiguous. Ask Transformation.is_receiver instead of testing for a receiver group.
rfgen.core.enums.RECEIVER_TRANSFORMATIONS and RECEIVER_TRANSFORMATION_ORDER¶
RECEIVER_TRANSFORMATION_ORDER: Final[tuple[Transformation, ...]]
RECEIVER_TRANSFORMATIONS: Final[frozenset[Transformation]]
RECEIVER_TRANSFORMATION_ORDER is the canonical receiver order, read off the Transformation ordinals: RX_LO_FREQUENCY_ERROR, RX_MIXER, IF_FILTER, RESAMPLER, LNA_NOISE, ADC, RX_PHASE_NOISE, RX_IQ_IMB, AGC. It is the ordering authority for communications chains: a ChannelConfig’s receiver entries must appear in this order, monotonic non-decreasing, because the two resampler selectors share ordinal 33 and are legal adjacent. It is deliberately not the authority for the radar capture seam, whose stage order is the RadarFrontendParams construction order.
RECEIVER_TRANSFORMATIONS is the same nine members as a frozenset, named explicitly rather than derived from an integer range, so adding a member is a deliberate act. It backs Transformation.is_receiver.
The capture/hardware split those nine used to carry as two Group members survives as ReceiverStagePlane (CAPTURE, HARDWARE) on the stage families, which is what positions the joint receiver-background injection between the two planes. See Receiver stages.
class rfgen.core.enums.Transformation¶
class Transformation(IntEnum):
# TX impairments
DAC = 11
PA = 12
TX_PHASE_NOISE = 13
TX_IQ_IMB = 14
CFO = 15
# Channel propagation
PROPAGATION = 21
# Receiver, capture plane
RX_LO_FREQUENCY_ERROR = 30
RX_MIXER = 31
IF_FILTER = 32
RESAMPLER = 33
LNA_NOISE = 34
# Receiver, hardware plane
ADC = 41
RX_PHASE_NOISE = 42
RX_IQ_IMB = 43
AGC = 44
Transformation tag pinned on every concrete BaseChannel subclass and every receiver stage family as a ClassVar[Transformation]. The integer value is an implementation detail used by ChannelPipeline to validate monotonic non-decreasing ordering within each chain partition.
Two properties read the encoding:
Property |
Returns |
Notes |
|---|---|---|
|
|
|
|
|
A caller holding an arbitrary transformation must branch on is_receiver before reading .group. Guarding with getattr(transformation, "group", None) does not work: getattr’s default covers a missing attribute, never an exception raised from inside a property.
The 30s/40s split is the receiver plane marker rather than a group. Its authority is ReceiverStagePlane on the stage families; see Receiver stages.
Emitters and channels¶
Link direction is not an enum¶
Cellular link direction for the Sionna propagation backends
(SionnaUMa,
SionnaUMi,
SionnaRMa) is
StatisticalSolverConfig.direction, typed
Literal["uplink", "downlink"]. "uplink" means UE transmits and BS
receives; "downlink" is the reverse. The value is passed to the upstream
Sionna scenario class unchanged. There is no rfgen.core.enums.Direction.
class rfgen.core.enums.EmitterFamily¶
class EmitterFamily(StrEnum):
COMMS = "comms"
RADAR = "radar"
DRONE = "drone"
IOT = "iot"
ADSB = "adsb"
CELLULAR = "cellular"
PNT = "pnt"
Coarse emitter family used by EmitterFamilyConfig. New families require a framework version bump; emitter plugins within a family do not. PNT waveform and interference implementations remain installable use-case plugins rather than core emitters.
class rfgen.core.enums.LoRaBackend¶
class LoRaBackend(StrEnum):
GR_LORA_SDR = "gr-lora-sdr"
LORA_PHY = "lora-phy"
LoRa-emitter backend selector consumed by the scene composer when it instantiates a LoRa emitter from the configured zoo. "gr-lora-sdr" selects the default LoRaSdrEmitter (gr-lora_sdr GNU Radio OOT module); "lora-phy" selects the pure-Python fallback LoRaPHYEmitter (loraphy library).
class rfgen.core.enums.PAModel¶
class PAModel(StrEnum):
RAPP = "rapp"
SALEH = "saleh"
Power-amplifier nonlinearity model on FingerprintConfig. See Reference / Fingerprint Math.
SNR has no distribution selector¶
There is no SNRDistribution enum and no ChannelConfig.snr_distribution
field; ChannelConfig is extra="forbid", so writing one is refused. SNR is
set per channel — AWGNChannel(snr_db=...) — and
ChannelConfig.snr_db_range bounds
what a configuration may request.
Annotations¶
class rfgen.core.enums.AnnotationType¶
class AnnotationType(StrEnum):
CAPTION = "caption"
One member. qa, reasoning, scene_report and contrastive were removed
with the templates that produced them: they drew their numbers from a
projection that discarded units, and a label that cannot state a bandwidth in
hertz is not a training label.
A configuration naming a removed value fails with a sentence rather than a bare
enum error, from rfgen.core.enums.RETIRED_ANNOTATION_TYPES. scene_report is
the one that was renamed rather than dropped: its declared-evidence template is
now caption.declared.v1.
Labels¶
class rfgen.core.enums.SegmentationMode¶
class SegmentationMode(StrEnum):
SINGLE_LABEL = "single_label"
MULTI_LABEL = "multi_label"
Segmentation mask layout selector for LabelConfig.segmentation_mode and SegmentationLabeler.
class rfgen.core.enums.SegmentationTieBreak¶
class SegmentationTieBreak(StrEnum):
LOWER_EMITTER_INDEX = "lower_emitter_index"
Single-label overlap policy for LabelConfig.segmentation_tie_break and SegmentationLabeler.
Storage¶
class rfgen.core.enums.StoreMode¶
class StoreMode(StrEnum):
READ = "read"
WRITE = "write"
APPEND = "append"
Declared, and consumed by nothing this build ships. It described the open modes
of the retired pluggable store hierarchy; BaseDatasetStore.open(uri) takes no
mode, and the surviving store is immutable by construction — shards are written
once and a readable root is published last, so there is no truncating or
appending open to ask for. The members are kept because the enum is exported,
not because a store reads them.
class rfgen.core.enums.StorageBackend¶
class StorageBackend(StrEnum):
SIGNAL_DATASET = "signal_dataset"
Store selector on StorageConfig.
signal_dataset is the only store rfgen ships, so the enum has one member.
Unlike the other enums on this page, backend is an open selector: a
string that is not a known member passes through validation unchanged, so a
third-party store registered under the rfgen.dataset_stores entry-point group
is reachable without a schema change. The enum names the shipped store; it does
not close the field.
class rfgen.core.enums.GeometryAssetKind¶
class GeometryAssetKind(StrEnum):
SIONNA_BUILTIN_SCENE = "sionna_builtin_scene"
MITSUBA_XML_BUNDLE = "mitsuba_xml_bundle"
OPENGERT_MITSUBA_XML_BUNDLE = "opengert_mitsuba_xml_bundle"
MATERIAL_DB = "material_db"
ANTENNA_PATTERN = "antenna_pattern"
DEEPMIMO_EXPORT = "deepmimo_export"
USD_STAGE = "usd_stage"
USDZ_PACKAGE = "usdz_package"
Typed geometry-related asset categories carried on
GeometryAssetRef.kind.
SIONNA_BUILTIN_SCENE and MITSUBA_XML_BUNDLE/OPENGERT_MITSUBA_XML_BUNDLE
are the two kinds SionnaRT resolves into a loadable Sionna scene (see
GeometryAssetRef);
MATERIAL_DB and ANTENNA_PATTERN identify material-database and
antenna-pattern blobs; DEEPMIMO_EXPORT identifies a DeepMIMO dataset
export. USD_STAGE names a .usd, .usda, or .usdc root layer and
USDZ_PACKAGE names a .usdz package.
A USD asset is nameable, digestible, and refused. It is not loadable.
The installed Mitsuba has no USD plugin of any spelling, so there is nothing
for an adapter to call, and the framework ships a declared boundary with a
precise refusal rather than a converter nobody has qualified. Naming a USD
stage as world geometry gets you a correct digest, a correct kind in stored
provenance, and a geometry_format_not_ingestible refusal naming the
configured ingest, the kind it was handed, the URI, and the kinds that ingest
does read. The refusal runs before any engine work, and it runs on the path a
configuration can actually take: scene.assets.scene_geometry_uri naming a
.usda under the sionna_rt backend is accepted, reaches SionnaRT.apply,
and meets this refusal there rather than an earlier, vaguer one.
Not loadable is not the whole sentence: rfgen writes USD. The asymmetry is
the point of the seam rather than a gap in it. A minted ScenePlan is exported
as a .usda stage by rfgen export-plan, which is a shipped command an
operator runs against a configuration or a written plan artifact. What that
stage carries is the plan, not a world: no geometry, no materials, and no world
asset content, joined back to the generated records by rfgen:planHash. So a
USD_STAGE kind names something this framework can produce and cannot consume,
and the two halves never meet: nothing exported here is offered to an ingest,
and the refusal above is unchanged by the exporter’s existence. See
Scene for the exporter and its contract.
Which kinds an engine reads is a declaration rather than a branch in a
loader: rfgen.engine.ingest.GeometryIngest carries an ingests set, the
shipped SionnaMitsubaIngest declares SIONNA_BUILTIN_SCENE,
MITSUBA_XML_BUNDLE, and OPENGERT_MITSUBA_XML_BUNDLE, and the caller checks
membership before handing the reference over. DEEPMIMO_EXPORT is
deliberately outside that set: a DeepMIMO export is not a Mitsuba scene, and
before the declaration existed it would have been handed to the XML parser.
An out-of-tree converter registers under the rfgen.geometry_ingests
entry-point group and is named in scene.geometry.geometry_ingest.
How a URI becomes a kind¶
rfgen.scene.asset_refs.scene_geometry_asset_kind decides by scheme first and
then by the suffix of the URI’s path component, so a query string cannot
change the answer. Suffixes are matched case-insensitively, so world.USDA
classifies as USD_STAGE:
URI shape |
Kind |
|---|---|
opens with |
|
path ends |
|
path ends |
|
path ends |
|
anything else |
|
The last row is the fall-through, and it is what keeps every configuration written before the USD rows existed on the kind it already had.
Suffix sniffing is a weak rule. Reading bytes to identify the format is
unavailable for gs://, s3://, and https:// by construction, so the escape
hatch is the optional plan.scene_geometry_kind template field, which is
accepted only for a URI whose path carries none of the recognized suffixes.
On a URI the table classifies positively, a kind that agrees is a redundant
restatement and a kind that disagrees is refused with
scene_geometry_kind_conflicts_with_suffix rather than silently winning: both
facts are authored in one file, so a disagreement is a typo either way, and a
local asset is digested from its bytes without reference to its kind, which
means one file declared two ways would reach the world cache under one digest
naming two different ingest paths. Declaring MATERIAL_DB or ANTENNA_PATTERN
as world geometry is refused with scene_geometry_kind_not_a_world.
What a digest covers: digest_scope_for(kind, uri)¶
rfgen.scene.asset_refs.digest_scope_for is a pure function returning
"root_file", "whole_asset", "distribution", or "uri_identity". It is
published rather than stored, so the scope is recoverable from any reference a
corpus already holds without a single stored byte moving. It is total over
every kind and every supported scheme.
What the answer is about. It describes the scope a content-addressed
reference to that (kind, uri) pair carries: a statement about the digest
rule the scheme selects, not a prediction about one stored reference. There is
one branch where the two part company: a local file:// asset whose bytes could
not be read where the reference was minted takes the URI-identity stand-in and
is marked content_addressed=False, so that reference’s digest covers the URI
alone whatever this function says about its kind. The function cannot see that,
and deliberately so: it performs no I/O, and an answer that depended on whether
this process can read that path would be the non-reproducibility minting
refuses. Read ref.content_addressed for the distinction; the honest reading
of a root_file or whole_asset answer is “if this reference is
content-addressed, this is what its digest covers”.
The scheme decides first, for every kind alike:
URI scheme |
Digest |
Scope |
|---|---|---|
|
|
|
|
|
|
|
|
per kind, below |
Then, for a local file:// asset:
Kind |
Scope |
What the digest covers |
|---|---|---|
|
|
the named file only |
|
|
the XML file; the |
|
|
same, an OpenGERT export is Mitsuba XML |
|
|
the root layer; sublayers, references, and payloads are outside the digest |
|
|
every byte of the package file |
|
|
the file, which names nothing outside itself |
|
|
the same |
|
|
the named descriptor, beside companion arrays |
The root_file rows describe shipped behavior and are stated because they are
easy to over-read. Editing a building’s geometry in a .ply without touching
the Mitsuba XML that names it produces a different world under an unchanged
digest, and the world cache, keyed on the digest, will serve the stale scene.
USD_STAGE inherits exactly that limitation, because a USD root layer composes
its sublayers and references the same way.
whole_asset for USDZ_PACKAGE means every byte of the package file, and
that equals the composed stage for a conforming package, which is what
UsdUtils.CreateNewUsdzPackage and any DCC export produce. It is not a
conformance check: OpenUSD will happily open a hand-built archive whose root
layer references a path outside the package, and for such a package
whole_asset over-claims. Verifying self-containment means resolving every
composition arc, which needs an asset resolver this framework does not ship.
See Also¶
background/design-decisions § Closed enums use StrEnumfor the convention rule and the rationale.reference/api/configfor every config class that consumes these enums.